Writing parameterized unit tests in C# — Run the same test with various inputs
Unit testing is often (unfortunately) an afterthought or something done in haste. However, it’s important that testing be impactful and maintainable. One of the best ways to do that is to write a single test which will support multiple test cases through parameters. This also allows the tests that we write to be more robust as we can run through more inputs than we would otherwise.
Say, for example, we want to implement a unit test for a basic FizzBuzz. For this example I’m using the following specification:
Write a function that accepts a single
int
as input. For multiples of ‘3’ print “Fizz”, for the multiples of ‘5’ print “Buzz”, and for any other number, simply print the number itself.
Now, we’re going to write a unit test for this but we’re going to parameterize it. For these examples we’re going to use MSTest (Supported since MSTest V2 June 17, 2016) but the same functionality is supported in other frameworks like XUnit and NUnit. In C# with MSTest it would look something like this:
[TestMethod]
[DataRow(1)]
[DataRow(2)]
[DataRow(4)]
[DataRow(7)]
[DataRow(8)]
[DataRow(11)]
public void FizzBuzz_Numeric(int input)
{
Assert.AreEqual(input.ToString(), UnderTest.FizzBuzz(input));
}[TestMethod]
[DataRow(3)]
[DataRow(6)]
[DataRow(9)]
[DataRow(12)]
public void FizzBuzz_Fizz(int input)
{
Assert.AreEqual("Fizz", UnderTest.FizzBuzz(input));
}[TestMethod]
[DataRow(5)]
[DataRow(10)]
[DataRow(20)]
public void FizzBuzz_Buzz(int input)
{
Assert.AreEqual("Buzz", UnderTest.FizzBuzz(input));
}[TestMethod]
[DataRow(15)]
[DataRow(30)]
[DataRow(45)]
public void FizzBuzz_FizzBuzz(int input)
{
Assert.AreEqual("FizzBuzz", UnderTest.FizzBuzz(input));
}
Each of these take a decent representation of the inputs and make sure they match the expected outputs.
Another way of doing this is to write fewer tests but to also pass in the expected value, like so:
[TestMethod]
[DataRow(1, "1")]
[DataRow(2, "2")]
[DataRow(3, "Fizz", DisplayName = "Multiple of 3: 3 ")]
[DataRow(4, "4")]
[DataRow(5, "Buzz", DisplayName = "Multiple of 5: 5 ")]
[DataRow(6, "Fizz", DisplayName = "Multiple of 3: 3 ")]
[DataRow(10, "Buzz", DisplayName = "Multiple of 5: 10 ")]
[DataRow(11, "11")]
[DataRow(15, "FizzBuzz", DisplayName = "Multiple of 3 & 5: 15 ")]
public void FizzBuzz(int input, string expected)
{
Assert.AreEqual(expected, UnderTest.FizzBuzz(input));
}
Note that these test results include a separate line for each set of inputs. I’ve also added friendly display names to several of them that are reflected in the test explorer. (Note: Be careful if you provide display names to not duplicate the names! It won’t display them separately!)
Alternatives and trade-offs
There are other ways of doing this. Most of which have a big downside, or may be viable depending on your use-case.
The most obvious way is to write the values into the function itself. This would work. However, there’s a slight readability difference since in the example above, each [DataRow(...)]
is quite obviously a unique test case. The bigger advantage to the parameterized test, is that Visual Studio will treat each [DataRow(...)]
as technically a unique test. You can see in the image above, each one is displayed and pass/fail separately. The parameterized test method written technically counts as a unique test for each set of inputs.
Another way is to make a new test for each input. However, as you can see by the sheer number of scenarios I’m testing above with trivial effort you can see that it would be a lot of additional boilerplate with no advantage. In fact there’s a severe disadvantage to doing so. In the parameterized test you’ve written the test logic once. If it ever has to change, you only have to correct it once. If you copy/paste a bunch of different test methods you then have to maintain all those distinct test methods. DRY! Don’t Repeat Yourself!
Equivalency in XUnit or NUnit
The same test in XUnit would look like:
[Theory]
[InlineData(1, "1")]
[InlineData(2, "2")]
[InlineData(3, "Fizz")]
// Further cases omitted for brevity
public void FizzBuzz(int input, string expected)
{
Assert.AreEqual(expected, UnderTest.FizzBuzz(input));
}
The same test in NUnit would look like:
[Test]
[TestCase(1, "1")]
[TestCase(2, "2")]
[TestCase(3, "Fizz")]
// Further cases omitted for brevity
public void FizzBuzz(int input, string expected)
{
Assert.AreEqual(expected, UnderTest.FizzBuzz(input));
}