Test Driven Development in .NET Part 3: Test fixtures, categories, the sad path
April 1, 2013 1 Comment
This post will continue our discussion on TDD in .NET with NUnit. Here we’ll look at several smaller topics before moving onto larger chunks to avoid one big chatty post. In particular we’ll be looking at the following:
- Using TestFixture to run multiple tests
- Test categories
- Testing for ‘bad’ input: the Sad Path
- Specialised assertions
Have many tests at once
As your test suite grows you may run into a lot of repetitive code. If you’d like to test a method with a lot of different values then one solution is to copy and paste the first test and just replace the input parameters – goes clearly against the Don’t Repeat Yourself principle.
To demonstrate how you can pass in several different parameters to the system under test we’ll start a new project.
Open Visual Studio and create a Blank Solution with a name you like. Add a Class Library to the solution named Calculator.Domain. Add another Class Library called Calculator.Tests. Remove the Class1.cs files from each project. Add a folder called Calculator_Tests to the .Tests project. Add a class called When_adding_two_integers.cs. Add a reference to NUnit as described in this blog post. Decorate the class with the TestFixture attribute:
[TestFixture] public class When_adding_two_integers { }
Add the following Test to the class:
[Test] public void Then_sum_is_correct() { SimpleCalculator simpleCalculator = new SimpleCalculator(); CalculationResult calculationResult = simpleCalculator.Add(4, 7); Assert.AreEqual(11, calculationResult.Outcome); }
The code will of course not compile as we don’t have these classes and properties ready yet. Using the techniques we saw previously create them in the Calculator.Domain project. Run the test (Ctrl +R, A) and you should get a failing test as the Add method is not yet implemented. Recall, this is the first phase – Red.
Locate the CalculationResult class that was created and change the type of the Outcome property to int:
public class CalculationResult { public int Outcome { get; set; } }
Next go to SimpleCalculator.cs and implement the Add method:
public CalculationResult Add(int inputOne, int inputTwo) { return new CalculationResult() { Outcome = inputOne + inputTwo }; }
Run the test again and you should get a passing test.
Testing the Add method with only two different parameters may not be enough. You want to test it with small numbers, large numbers, negative numbers etc. One naive way would be to add a second test:
[Test] public void Then_sum_is_still_correct() { SimpleCalculator simpleCalculator = new SimpleCalculator(); CalculationResult calculationResult = simpleCalculator.Add(1, 2); Assert.AreEqual(3, calculationResult.Outcome); }
First we’ll factor out the SimpleCalculator initialisation:
[TestFixture] public class When_adding_two_integers { private SimpleCalculator _simpleCalculator; [SetUp] public void AssignVariables() { _simpleCalculator = new SimpleCalculator(); } [Test] public void Then_sum_is_correct() { CalculationResult calculationResult = _simpleCalculator.Add(4, 7); Assert.AreEqual(11, calculationResult.Outcome); } [Test] public void Then_sum_is_still_correct() { CalculationResult calculationResult = _simpleCalculator.Add(1, 2); Assert.AreEqual(3, calculationResult.Outcome); } }
The next step is to make sure that _simpleCalculator.Add can be called with many different values without copy-pasting the Test methods over and over again. The TextFixture attribute has an overloaded constructor which allows this, but first we’ll need to prepare a couple of things. Add the following private fields to the class:
private int _firstParameter; private int _secondParameter; private int _result;
Also, add the following constructor:
public When_adding_two_integers(int firstParameter, int secondParameter, int result) { _firstParameter = firstParameter; _secondParameter = secondParameter; _result = result; }
Remove the Then_sum_is_still_correct() test method. Change the Then_sum_is_correct() method to the following:
[Test] public void Then_sum_is_correct() { CalculationResult calculationResult = _simpleCalculator.Add(_firstParameter, _secondParameter); Assert.AreEqual(_result, calculationResult.Outcome); }
The TextFixture attribute allows us to pass in parameters to the constructor. Add the following attributes above the class declaration:
[TestFixture(1,1,2)] [TestFixture(2, 2, 5)] [TestFixture(3, 2, 2)] [TestFixture(4, 4, 8)] [TestFixture(5, 6, 11)] [TestFixture(12, 10, 21)] [TestFixture(13, 14, 27)] [TestFixture(0, 0, 0)]
Yes, all of them. Look at the signature of the test class constructor: it requires three integers. You’ll see that we’re passing in three integers in the TextFixture object: the firstParamater, the secondParameter and the result values. The values are assigned to the local _firstParameter, _secondParameter and _result fields and their values are used in the Then_sum_is_correct() method. So the first attribute [TestFixture(1,1,2)] will be translated to ‘add 1 and 1 and assert that the result is 2’ in the Then_sum_is_correct() method. All TestFixture rows will be run by the test runner. Note that we’ve deliberately introduced a couple of errors, such as the second one [TestFixture(2, 2, 5)]. Run the tests now and you should see 3 failing and 5 passing tests:
Correct the values in the TestFixture constructor as necessary to get 8 passing tests. So, we’ve got 8 tests for the price of one, that sounds like a good deal.
Category
For organisational purposes you can group the tests using the Category attribute which accepts a string parameter. Add the following attribute above the first TestFixture attribute:
[Category("Calculator.Domain")]
Run the tests again and you’ll see no change. Go to the Group By icon in the Test Explorer and select Traits:
You’ll then see that the tests are organised according to their category:
This is useful when you have many more test fixtures which can be grouped. This adds another level in the test hierarchy.
The sad path
So far we’ve tested very simple values where we subconsciously assumed that the user will provide ‘good’ inputs, i.e. inputs that the Add method can handle. This is called the Happy Path. However, what if one of the parameters is int.Max? Adding something to int.Max will give unexpected results.
It is important to keep in mind that we should also test inputs that our code should not deal with. Add a subfolder called ‘SadPath’ within the ‘Calculator_Tests’ folder. Add a class called When_adding_integer_to_maximum.
Start with the following skeleton:
[Category("Calculator.Domain")] [TestFixture] public class When_adding_integer_to_maximum { private SimpleCalculator _simpleCalculator; [SetUp] public void AssignVariables() { _simpleCalculator = new SimpleCalculator(); } [Test] public void Method_should_throw_not_supported_exception() { } }
The method name describes what we would like to see: the Add method should throw a NotSupportedException when passing in at least one int.Max value instead of returning a dubious result.
We’ll use another attribute called ExpectedException. As the name implies it decorates tests where we expect the tested method to throw an exception. So the test passes if the exception is thrown and fails otherwise. Update the Method_should_throw_not_supported_exception() test to the following:
[Test] [ExpectedException(typeof(NotSupportedException))] public void Method_should_throw_not_supported_exception() { _simpleCalculator.Add(int.MaxValue, 3); }
In other words the Add method is expected to throw a NotSupportedException otherwise the test fails. Run the test… …and this particular test will fail as it did not throw the exception:
Remember: according to the Red-Green-Refactor cycle this is a good sign.
Go to the body of the Add method and update it as follows:
public CalculationResult Add(int inputOne, int inputTwo) { if (inputOne == int.MaxValue || inputTwo == int.MaxValue) { throw new NotSupportedException("Maximum values not supported."); } return new CalculationResult() { Outcome = inputOne + inputTwo }; }
Re-run the tests and you’ll see that the test passes.
Specialised assertions
It’s worthwhile to simply type ‘Assert.’ in Visual Studio and check the available choices. You’ll find lots of interesting methods, such as IsNull, IsNotNull, LessOrEqual, Greater etc. However, there are some specialised cases that you won’t find in here but have to look elsewhere:
- StringAssert
- CollectionAssert
It’s easy to guess what these are for: strings and collections. If you wish to make sure that a string parameter fulfils some condition then StringAssert has methods such as DoesNotEndWith or DoesNotContain.
In case a Collection type of object, such as a List needs to be inspected then CollectionAssert has methods such as IsSubsetOf or IsEmpty.
Thus there is a wide range of cases you can write tests for.
This was a rather short entry on a couple of smaller topics in TDD that didn’t really fit in a larger post. In the next post we’ll continue looking at the Refactor phase.
to work under NUnit 3.0, change it to the following:
Assert.That(() => _simpleCalculator.Add(int.MaxValue, 3), Throws.TypeOf());
https://stackoverflow.com/questions/33895457/expectedexception-in-nunit-gave-me-an-error