If you’ve never done Test Driven Development or aren’t even sure what this "crazy TDD stuff” is all about than this is the series for you. Over the next 30 days this series of posts take you from “I can spell TDD” to being able to consider yourself a “functional” TDD developer. Of course TDD is a very deep topic and truly mastering it will take quite a bit of time, but the rewards are well worth it. Along the way I’ll be showing you how tools like JustCode and JustMock can help you in your practice of TDD.
In the last post I showed you how from time to time it is necessary to change our code to enhance readability, make maintenance easier or to optimize the codes performance. This practice is called “Refactoring.” Normally making these kinds of changes can be a nerve-wracking experience for developers as they can’t be certain that their changes aren’t breaking something else. However, having a suite of unit tests the exercise your business code enables you to refactor your code without worry; as long as your tests pass you know that your code still satisfies your business needs. In addition to our code, sometimes our unit tests themselves need some refactoring. This post explains how to refactor your unit tests and demonstrates a few NUnit features that will help us with this endeavor.
Previous Posts in this Series Day Nine – Refactoring Basics
It’s not only important to periodically refactor our business logic, we also periodically need to look at our test. Remember: tests are code too and need to be treated with the same care and respect. By way of example, let’s take a look at our current tests:
1: using System;
2: using System.Linq;
3: using NUnit.Framework;
5: namespace ThirtyDaysOfTDD.UnitTests
8: public class StringUtilsTest
11: public void ShouldBeAbleToCountNumberOfLettersInSimpleSentence()
13: var sentenceToScan = "TDD is awesome!";
14: var characterToScanFor = "e";
15: var expectedResult = 2;
16: var stringUtils = new StringUtils();
18: int result = stringUtils.FindNumberOfOccurences(sentenceToScan, characterToScanFor);
20: Assert.AreEqual(expectedResult, result);
24: public void ShouldBeAbleToCountNumberOfLettersInAComplexSentence()
26: var sentenceToScan = "Once is unique, twice is a coincidence, three times is a pattern.";
27: var characterToScanFor = "n";
28: var expectedResult = 5;
29: var stringUtils = new StringUtils();
31: int result = stringUtils.FindNumberOfOccurences(sentenceToScan, characterToScanFor);
33: Assert.AreEqual(expectedResult, result);
38: public void ShouldGetAnArgumentExceptionWhenCharacterToScanForIsLargerThanOneCharacter()
40: var sentenceToScan = "This test should throw an exception";
41: var characterToScanFor = "xx";
42: var stringUtils = new StringUtils();
44: stringUtils.FindNumberOfOccurences(sentenceToScan, characterToScanFor);
(get sample code)
A quick look at this code reveals a common refactoring target; code duplication. If you look on lines 16, 29 and 42 you can see that I am constantly creating a new instance of the StringUtils class in each test. In general I like to follow what’s known as the DRY Principle. DRY stands for Don't Repeat Yourself. In this case I’m clearly repeating my code that creates an instance of StringUtils when I don’t need to. Luckily NUnit provides a mechanism to help us make this code a litter DRYer.
Before we get into changing the tests though, we need to ask ourselves a question: How do I know changing these tests isn’t going to break them? When we refactored our business logic we had unit tests to ensure that the result of our refactoring still met the needs of the business as defined by the unit tests. As long as our tests passed, we knew the business logic still met our needs. The corollary is true; if we’ve truly practiced TDD, meaning that we’ve only written code based on tests (our tests are written first) then we can use our business logic to validate our tests. This means we can refactor our tests and as long as the tests still pass, and we haven’t changed the business logic in the meantime, our tests are still valid.
What I would like to do is put the code to that creates the instance of StringUtils in a common place that is used by all the tests. This clearly is a step towards enhancing code reuse and will make my tests cleaner and easier to work with. The true impact of this will be shown when we start discussing mocking in the next post in this series. Normally I would abstract out this functionality to a separate reusable method (actually, in this specific case I would use a DI framework) but NUnit provides a better way.
Among the features in NUnit are the ability to define SetUp methods. A Setup method in NUnit is defined by using the SetUp attribute as shown here. When a SetUp method is defined in an NUnit test fixture it is run before each test in the test fixture class is run:
1: private StringUtils _stringUtils;
4: public void SetupStringUtilTests()
6: _stringUtils = new StringUtils();
I’ve defined a private instance variable of type StringUtils and created a method called SetupStringUtilTests in which I am assigning an instance of StringUtils to the _stringUtils instance variable. The next step is to refactor my individual tests to use this instance variable instead of a per test local instance:
2: public void ShouldBeAbleToCountNumberOfLettersInSimpleSentence()
4: var sentenceToScan = "TDD is awesome!";
5: var characterToScanFor = "e";
6: var expectedResult = 2;
8: int result = _stringUtils.FindNumberOfOccurences(sentenceToScan, characterToScanFor);
10: Assert.AreEqual(expectedResult, result);
14: public void ShouldBeAbleToCountNumberOfLettersInAComplexSentence()
16: var sentenceToScan = "Once is unique, twice is a coincidence, three times is a pattern.";
17: var characterToScanFor = "n";
18: var expectedResult = 5;
20: int result = _stringUtils.FindNumberOfOccurences(sentenceToScan, characterToScanFor);
22: Assert.AreEqual(expectedResult, result);
27: public void ShouldGetAnArgumentExceptionWhenCharacterToScanForIsLargerThanOneCharacter()
29: var sentenceToScan = "This test should throw an exception";
30: var characterToScanFor = "xx";
32: _stringUtils.FindNumberOfOccurences(sentenceToScan, characterToScanFor);
Running our tests demonstrates that they still pass after this change (Figure 1):
Figure 1 – Our refactored tests all still pass
This has definitly made our tests more readable. It’s also enabled us to encapsulate the code that creates the instance of StringUtils in one place. That means if that if that logic ever changes we have one method that has to be updated, not n tests. Using the SetUp attribute enables us to define one method that runs before each test. In cases where our class under test maintains some state that needs to be destroyed and rebuilt before each test, this is a good option. But our StringUtils class doesn’t have any internal state, so there’s no need to keep re-creating it for every test. In this case we can use the NUnit attribute TestFixtureSetUp to define our setup method. This attributes works in a similar way to SetUp, but instead of running once for each test, it runs once for each instance of test fixture class. In our case our test fixture class has three tests. Whereas SetUp would have run three times (once for each test in the fixture), TestFixtureSetUp runs once and all three tests use the same instance of StringUtils created by the setup method.
Since we don’t need to create a new instance of StringUtils for each test, I’m going to refactor the test fixture to use the TestFixtureSetUp attribute:
2: public void SetupStringUtilTests()
4: _stringUtils = new StringUtils();
We want to run the tests again to ensure that our latest refactor hasn’t broken anything (Figure 2):
Figure 2 – Our tests continue to pass
One last note about SetUp and TextFixtureSetUp: NUnit expects only one instance these to be in a test fixture. You can have one of each in your test fixture, but you can’t have more that one of each. For example, if you have two methods decorated with the SetUp attribute the code will compile, but NUnit will not be able to run your tests.
Our unit tests are code just like our business code. And just like our business code it occasionally needs to be reviewed and improved. Refactoring our tests is just as important as refactoring your business code and should be part of our normal code review process.
Continue the TDD journey:
Copyright © 2017, Progress Software Corporation and/or its subsidiaries or affiliates. All Rights Reserved.
Progress, Telerik, and certain product names used herein are trademarks or registered trademarks of Progress Software Corporation and/or one of its subsidiaries or affiliates in the U.S. and/or other countries. See Trademarks or appropriate markings.