In my Zero-Friction TDD series, I focus on establishing a set of good habits that can potentially make you more productive while writing tests TDD style. While being able to quickly write good tests is important, this is not the only quality on which you should focus.
Maintainability, not only of your production code, but also of your test code, is important, and the DRY principle is just as applicable here.
Consider a test like this:
[TestMethod]
public void SomeTestUsingConstructorToCreateSut()
{
// Fixture setup
MyClass sut = new MyClass();
// Exercise system
// ...
// Verify outcome
// Teardown
}
Such a test represents an anti-pattern you can easily fall victim to. The main item of interest here is that I create the SUT using its constructor. You could say that I have hard-coded this particular constructor usage into my test.
This is not a problem if there's only one test of MyClass, but once you have many, this starts to become a drag on your ability to refactor your code.
Imagine that you want to change the constructor of MyClass from the default constructor to one that takes a dependency, like this:
public MyClass(IMyInterface dependency)
If you have many (in this case, not three, but dozens) tests using the default constructor, this simple change will force you to visit all these tests and modify them to be able to compile again.
If, instead, we use a factory to create the SUT in each test, there's a single place where we can go and update the creation logic.
public void SomeTestUsingFactoryToCreateSut()
MyClass sut = MyClassFactory.Create();
The MyClassFactory class is a test-specific helper class (more formally, it's part of our SUT API Encapsulation) that is part of the unit test project. Using this factory, we only need to modify the Create method to implement the constructor change.
internal static MyClass Create()
IMyInterface fake = new FakeMyInterface();
return new MyClass(fake);
Instead of having to modify many individual tests to support the signature change of the constructor, there's now one central place where we can go and do that. This pattern supports refactoring much better, so consider making this a habit of yours.
One exception to this rule concerns tests that explicitly deal with the constructor, such as this one:
[ExpectedException(typeof(ArgumentNullException))]
public void CreateWithNullMyInterfaceWillThrow()
IMyInterface nullMyInterface = null;
new MyClass(nullMyInterface);
// Verify outcome (expected exception)
In a case like this, where you explicitly want to deal with the constructor in an anomalous way, I consider it reasonable to deviate from the rule of using a factory to create the SUT. Although this may result in a need to fix the SUT creation logic in more than one place, instead of only in the factory itself, it's likely to be constrained to a few places instead of dozens or more, since normally, you will only have a handful of these explicit constructor tests.
Compared to my Zero-Friction TDD tips and tricks, this particular advice has the potential to marginally slow you down. However, this investments pays off when you want to refactor your SUT's constructor, and remember that you can always just write the call to the factory and move on without implementing it right away.
Remember Me
a@href@title, b, em, i, strike, strong
Page rendered at Saturday, February 04, 2012 7:53:19 PM (Romance Standard Time, UTC+01:00)
Disclaimer The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.