As we saw last week, parametrised tests help us to reduce the number of test methods we have to write and still keep the coverage high. However, writing the test parameters multiple times is error prone, especially when it is important that we test all values. Let us look on how NUnit can help us with this problem.
The [TestCaseSource] attribute
With the [TestCaseSource] attribute we can tell NUnit that our data for the parametrised test comes from a property or a method. Instead of a [TestCase] for every test, we write one singe [TestCaseSource]:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
[TestCaseSource(nameof(TestCases))] public void Add_with_small_numbers_works(int a, int b, int expectedResult) { var result = this.Add(a, b); Assert.AreEqual(expectedResult, result); } private static object[] TestCases = { new object[] {1, 1, 2}, new object[] {1, 2, 3}, new object[] {2, 1, 3}, new object[] {2, 2, 4}, new object[] {1, 3, 4}, new object[] {3, 1, 4}, }; |
There are 3 important points you must follow when you create a test case source:
- It must be static.
- It must return an IEnumerable or a type that implements IEnumerable
- The returned items must be compatible with the method signature
NUnit does a lot of work in the background. If something goes wrong, read the documentation to see if you missed something.
Use test data from another class
You can reuse your test data outside of the class you wrote it. In my class ExternalTestData I have a property and a method that provides the test cases:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
public static class ExternalTestData { public static object[] TestCases = { new object[] {1, 1, 2}, new object[] {1, 2, 3}, new object[] {2, 1, 3}, new object[] {2, 2, 4}, new object[] {1, 3, 4}, new object[] {3, 1, 4}, }; public static List<TestDto> TestCasesAsDto() { var data = new List<TestDto>(); data.Add(new TestDto(1,1,2)); data.Add(new TestDto(1,2,3)); data.Add(new TestDto(2,1,3)); data.Add(new TestDto(2,2,4)); data.Add(new TestDto(1,3,4)); data.Add(new TestDto(3,1,4)); return data; } } public class TestDto { public int A { get; set; } public int B { get; set; } public int ExpectedResult { get; set; } public TestDto(int a, int b, int expected) { this.A = a; this.B = b; this.ExpectedResult = expected; } } |
In our test class we can reference the test data by putting the name of the class in front of the property / method:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
[TestCaseSource(nameof(ExternalTestData.TestCases))] public void Add_data_in_other_class(int a, int b, int expectedResult) { var result = this.Add(a, b); Assert.AreEqual(expectedResult, result); } [TestCaseSource(nameof(ExternalTestData.TestCasesAsDto))] public void Add_data_from_method_other_class(int a, int b, int expectedResult) { var result = this.Add(a, b); Assert.AreEqual(expectedResult, result); } |
Conclusion
Parametrised tests, especially in combination with TestCaseSource, are a great tool to keep the overhead of writing test methods down. They may not look like much work, but as soon as you need to change them, you hope that you used parametrised tests and only need to change a few lines of code. Now would be a good time to start using parametrised tests if you not already do.