Monday, 14 September 2009

Parameterized Tests with TestCase in NUnit 2.5

For some time, NUnit has had a RowTest attribute in the NUnit.Extensions.dll, but with NUnit 2.5, we have built-in support for parameterized tests. These reduce the verbosity of multiple tests that differ only by a few arguments.

For example, if you had test code like this:

[Test]
public void Adding_1_and_1_should_equal_2()
{
    Assert.AreEqual(2,calculator.Add(1,1));
}

[Test]
public void Adding_1_and_2_should_equal_3()
{
    Assert.AreEqual(3, calculator.Add(1, 2));
}

[Test]
public void Adding_1_and_3_should_equal_4()
{
    Assert.AreEqual(4, calculator.Add(1, 3));
}

You can now simply refactor to:

[TestCase(1, 1, 2)]
[TestCase(1, 2, 3)]
[TestCase(1, 3, 4)]
public void AdditionTest(int a, int b, int expectedResult)
{
    Assert.AreEqual(expectedResult, calculator.Add(a, b));
}

The NUnit GUI handles this very nicely, allowing us to see which one failed, and run them individually:

NUnit Parameterized Tests

TestDriven.NET doesn’t give us the ability to specify an individual test case to run (hopefully a future feature), but it will show us the parameters used for any test failures:

TestCase 'SanityCheck.CalculatorTests.AdditionTest(1,2,3)' failed: 
  Expected: 3
  But was:  2
...\CalculatorTests.cs(19,0): at SanityCheck.CalculatorTests.AdditionTest(Int32 a, Int32 b, Int32 expectedResult)

TestCase 'SanityCheck.CalculatorTests.AdditionTest(1,3,4)' failed: 
  Expected: 4
  But was:  2
...\CalculatorTests.cs(19,0): at SanityCheck.CalculatorTests.AdditionTest(Int32 a, Int32 b, Int32 expectedResult)

Another very cool feature is that you can specify a TestCaseSource function, allowing you to generate test cases on the fly. One way I have used this feature is for some integration tests that examine a folder of legacy test data files and create a test for each file.

There are a few options for how to provide the source data. Here I show using a function that returns an IEnumerable<string>:

[TestCaseSource("GetTestFiles")]
public void ImportXmlTest(string xmlFileName)
{
    xmlImporter.Import(xmlFileName);
}

public IEnumerable<string> GetTestFiles()
{
    return Directory.GetFiles("D:\\Test Data", "*.xml");
}

Now when NUnit loads the test assembly, it runs the GetTestFiles function, to get the test case parameters, allowing you to run them individually if necessary.

TestCaseSource Function

There is one gotcha. If your TestCaseSource function takes a long time to run, you will need to wait every time NUnit (or TestDriven.NET) reloads your unit test DLL. So my example of examining a directory for files could negatively impact performance if there are too many test cases (I discovered this after loading 14000 test cases from a network drive).

Post a Comment