When I lead the team that built NUnit we implemented SetUp and TearDown similar to the way they were implemented in JUnit. For those who do not know SetUp and TearDown are attributes on methods in a TestFixture that perform common initialization and destruction. Here is an example that demonstrates how and when SetUp and TearDown are called,
[TestFixture]
public class TestFixtureLifetime
{
[SetUp]
public void BeforeTest()
{ Console.WriteLine("BeforeTest"); }
[TearDown]
public void AfterTest()
{ Console.WriteLine("AfterTest"); }
[Test]
public void Test1()
{ Console.WriteLine("Test1"); }
[Test]
public void Test2()
{ Console.WriteLine("Test2"); }
}
When I run this test I get the following output: .
BeforeTest
Test1
AfterTest
BeforeTest
Test2
AfterTest
I used to think that factoring out duplicated code into a common SetUp and TearDown method was good idea. My reasoning was based on one of the tenants of Simple Design; Remove Code Duplication, even from your test code. In order to better demonstrate why my thoughts have changed let's look at a slightly modified MoneyTest from the NUnit samples. MoneyTest has 3 Test methods (IsZero, BagAdd, and BagSubtractIsZero) and a Setup method, (BeforeTest).
[TestFixture]
public class MoneyTest
{
private Money f12CHF;
private Money f14CHF;
private Money f7USD;
private MoneyBag moneyBag;
[SetUp]
public void BeforeTest()
{
f12CHF = new Money(12, "CHF");
f14CHF = new Money(14, "CHF");
f7USD = new Money(7, "USD");
moneyBag = new MoneyBag(f12CHF, f7USD);
}
[Test]
public void IsZero()
{
Money[] bag = { new Money(0, "CHF"), new Money(0, "USD") };
Assert.IsTrue(new MoneyBag(bag).IsZero);
}
[Test]
public void BagAdd()
{
Money[] bag = { new Money(26, "CHF"), new Money(7, "USD") };
MoneyBag expected = new MoneyBag(bag);
Assert.AreEqual(expected, f14CHF.Add(moneyBag));
}
[Test]
public void BagSubtractIsZero()
{
Assert.IsTrue(moneyBag.Subtract(moneyBag).IsZero);
}
}
The problem that I have with SetUp in this case is twofold. The first and primary complaint is that when I am reading each test I have to glance up to BeforeTest() to see the values that are being used in the test. Worse yet if there was a TearDown method I would need to look in 3 methods. The second issue is that BeforeTest() initializes member variables for all 3 tests which complicates BeforeTest() and makes it violate the single responsibility pattern. In order to fix this I would remove the BeforeTest() method and place the specific variables in each test - here is the refactored code:
[TestFixture]
public class NoSetUpMoneyTest
{
[Test]
public void IsZero()
{
Money[] bag = { new Money(0, "CHF"),
new Money(0, "USD") };
Assert.IsTrue(new MoneyBag(bag).IsZero);
}
[Test]
public void BagAdd()
{
Money f7USD = new Money(7, "USD");
Money f12CHF = new Money(12, "CHF");
Money f14CHF = new Money(14, "CHF");
Money f26CHF = new Money(26, "CHF");
Money[] bag = { f26CHF, f7USD };
MoneyBag expected = new MoneyBag(bag);
MoneyBag moneyBag = new MoneyBag(f12CHF, f7USD);
Assert.AreEqual(expected, f14CHF.Add(moneyBag));
}
[Test]
public void BagSubtractIsZero()
{
MoneyBag moneyBag = new MoneyBag(new Money(7, "USD"),
new Money(12, "CHF"));
Assert.IsTrue(moneyBag.Subtract(moneyBag).IsZero);
}
}
In the refactored code the BeforeTest() is removed and each test is forced to do the initialization for what it needs to run. This makes it very easy to read each test as a single entity. Also, it has the side benefit of making it very clear which tests are more complicated than others. Compare the initialization code needed for BagAdd compared to IsZero. In addition the new class has no member variables. This is critical for test isolation. Without member variables there is no mechanism contained within the class for one test to mess with another.
These reasons lead me to never want to use common setup and tear down methods. In fact, I believe that this feature should be removed from the tool. Some people will argue that you cannot build a tool that stops people from writing poor code. However, you can write a tool that prevents people from shooting themselves in the foot.