I want to share with you a debate we had at work today:
Which one of the following is a better test – this one?
[TestFixture]
public class MyClassTests
{
private MyClass myClass;
[SetUp]
public void Initialize()
{
var arg1 = //...
var arg2 = //...
// More initialization logic
myClass = new MyClass(arg, arg2);
}
[Test]
public void MyTest()
{
myClass.Func();
// Assert something on my class
}
}
Or perhaps this test is better?
[TestFixture]
public class MyClassTests
{
[Test]
public void MyTest()
{
var arg1 = //...
var arg2 = //...
// More initialization logic
var myClass = new MyClass(arg, arg2);
myClass.Func();
// Assert something on my class
}
}
Don’t worry I’m not trying to make you find all of the differences between the two tests – the main difference is that the first test uses SetUp to initialize the class under test while the 2nd test initializes the class as part of the test – that’s it.
So which is better? I’d go for the second test – the one without a setup – in a heartbeat!
This may seem strange at first – mainly because the test that uses a different method to setup the object that will be used throughout all of the tests seems to be “more correct”. You may argue that without using a setup method my tests are bound to repeat the same logic (initialization) over and over again – and we all know that “writing the same code twice is writing it once too many” as my university professor used to say.
Well I prefer to write the same code twice as long as it makes my test more readable. I want to be able to look at a failing test and understand why it failed simply by reading it – without traversing the code I’m testing and without debugging it - I want to be able to know what went wrong in a single glance.
Although it seems that the example above is clear enough with or without a setup method consider what would happen if (or shall I say when) we’ll have 10 or 20 or 100 tests on the same file using the same setup method? probably one or two of the following outcomes:
But what about refactoring? If I make the slightest change to the class I’m testing I need to go over many tests and re-write them. What would happen if I add another parameter to the the class’s c’tor or change an existing one?
Well there is a simple solution to that problem that will not hinder the test’s readability:
[TestFixture]
public class MyClassTests
{
private MyClass CreateMyClass()
{
var arg1 = //...
var arg2 = //...
// More initialization logic
return new MyClass(arg, arg2);
}
[Test]
public void MyTest()
{
myClass = CreateMyClass();
myClass.Func();
// Assert something on my class
}
}
That’s right – by using a method that create and initialize the class we make sure that you only need to change a single method and not every test in your suite. When we need to find out the creation logic we can do so by navigating to the Create method – and now there is no chance you’ll forget it exist like you might have with the SetUp method.
And the added benefit is that unlike the testing framework built–in setup this method can receive arguments – so it can really be re-used and in case a completely different logic is needed – just write another function and use it instead.
In fact I only use setup/teardown method – to handle environment creation/destruction in my integration tests. If I need a specific file to be copied and I don’t care how it got there or if I need to setup an database or a registry value – that’s when I use SetUp attribute.
Labels: C#, NUnit, Unit tests