One of the best feature that is in mbUnit since 2004 is RowTest, which is the ability to perform the same test using different input data and expected results.
public void SumTest(int a1, int a2, int result)
Assert.AreEqual(a1 + a2, result);
The cool thing is that each "row" is treated as a different test, and if the test fails for one set of data, the others might not. And this helps pinpointing the data that are making the test to fail.
This week I was adding some unit tests to a project that is managed by TFS, so I decided to use the "core" unit testing framework available with VS2008, and unfortunately it doesn't support RowTests. But it has a similar feature called Data-Driven Unit Test. With this approach it's a bit more complicate to implement the "simple" RowTest scenario, but it allows also to implement more complicate ones.
Let's have a look at how to simulate a RowTest using the Data-Driven approach of MS Test.
A Data-Driven test is a test that is performed once for each of the rows in the datatable configured as source for the test.
This is just the same as RowTest in mbUnit but with a different source for the parameters to use: instead of specifying the parameters as attributes of the test method, you have to write them inside one of the three types of DataSources that are supported by the MS Test framework:
- CSV text file
- XML file
- any DataBase that have a provider for .NET
For this example I'll use a XML file, but in case of more complicate scenarios you might want to use a Database.
The XML file
This it the XML file with one "row" for each set of data that I want the test to run on. Notice that the last row will cause the test to fail (1-1=0, not 1)
First you have to create a normal unit test (either authoring it manually or generating with VS) and then for each test which you want to use the RowTest-like approach for, you have to write a small caller method that read the parameters from the datasource and calls the real test method.
public void SumTest()
int a1 = Int32.Parse((string)TestContext.DataRow["A1"]);
int a2 = Int32.Parse((string)TestContext.DataRow["A2"]);
int result = Int32.Parse((string)TestContext.DataRow["Result"]);
ExecSumTest(a1, a2, result);
private static void ExecSumTest(int a1, int a2, int result)
Assert.AreEqual(a1 + a2, result);
As you see, there are two methods: the second one is the "real" test method, while the first one is there only to read from the datasource and call the real method.
The wrapper method is decorated with 3 attributes:
- TestMethods that tells the test runner that this method must be called during the test run
- DeploymentItem that tells the test runner which files it has to deploy for the test to run (replace ProjectName with the real name of your project)
- DataSource which configures the type of datasource, the connection string, the table name and whether execute the test following the order of the data, or randomly
Then it reads from the DataRow the parameters needed, casts them to the right type (here passing through a string since the XML file returns only strings), and finally calls the real method.
If you want you can select the DataSource to use also in the configuration file of the test or using the wizard available in the property window of the Test List Editor.
Unfortunately, when run this way, you see only one line in the Test Results pane, but each row is accounted separately (in the image below, 2 out of 4 failed since it counts one test per row plus the group of all rows)
But if you click on the line with the test, you go to the details view, that tells you exactly which row made the test fail.
And here you see that row line 3 failed (the test fails since 1-1=0 not 1)
The RowTest approach can be achieved also with MS Test and, even it's more complicate than with mbUnit for simple scenarios, it allows an higher degree of flexibility since it allows the tester to change the set of test parameters without the need to recompile the test.