The “rules” of TDD do not mean you shouldn’t do your tests properly

Okay, disclaimer first: I am not an expert in unit testing at all. I haven't even used TDD in any bigger project yet. I am still learning all of this.

When I started to learn TDD, I encountered some "rules" that everyone seems to obey. But it looks like sometimes they are taken too strict or are just not properly understood and therefore applied incorrectly. Here are some of these "rules" and my understanding of them.

Don't test Getters/Setters
This rule means that you don't need Unit tests for your simple Getters/Setters unless they have logic. Let's look at a class and unit tests:

class MyPOCO
{
    public string SimpleString { get; set; }
}

[TestFixture]
public class UnitTests
{
    [Test]
    public void UnneccessaryTest()
    {
        var testclass = new MyPOCO();
        testclass.SimpleString = "Test String";
        Assert.AreEqual("Test String", testclass.SimpleString);
    }
}

So we have a simple POCO that holds a string. However, as the string has no logic whatsoever, the unit test is unnecessary - we can safely assume that this will never break. It's 100% .net Framework stuff here, so if this unit test ever fails, the world is likely to end a minute or two later. So no need to test this string Getter/Setter. But you still want to test it if it comes from a Business Object.

For example, this here warrants a unit test:

public static class MyBusinessClass
{
    public static MyPOCO GetSomething()
    {
        return new MyPOCO() {SimpleString = "data"};
    }
}

[TestFixture]
public class UnitTests
{
    [Test]
    public void TestBusinessClass()
    {
        Assert.AreEqual("data",MyBusinessClass.GetSomething().SimpleString);
    }
}

As you see, we test the Getter of SimpleString. But in reality, we are testing that MyBusinessClass returns the expected value. That does seem logical and natural, but I've seen people not testing the return values because of the "No need to test Getters" rule. Again: This is about testing the Getter of SimpleString, this is testing the return value of MyBusinessClass.

Unit Tests are independent from each other
This rule says that Unit tests should not impact each other and should make no assumptions about other tests. For example, this is bad:

public static class MyVictim
{
    public static int SomeInt { get; set; }
}

[TestFixture]
public class BadUnitTests
{
    [Test]
    public void TestOne()
    {
        // Imagine a lot of complicated business logic that would
        // justify this test, but the important thing is that we
        // set MyVictim.SomeInt to a known value.
        Assert.DoesNotThrow(() => MyVictim.SomeInt = 150);
    }

    [Test]
    public void TestTwo()
    {
        Assert.AreEqual(150, MyVictim.SomeInt);
    }
}

As you see, TestOne modifies MyVictom.SomeInt to set it to 150. TestTwo checks that MyVictim.SomeInt really is 150. This is assuming that these two tests run in order. But if the test runner decides to run TestTwo first or to run them in parallel, it will fail (or in case of parallel testing it sometimes will and sometimes will not). But that does not mean that you should be copy/pasting your code in your Unit Tests! Independent Tests does not mean this:

public class MyClassUnderTest
{
    private int _someInt;
    private bool _someBool;

    public MyClassUnderTest(string param1, string param2, int param3, long param4, bool param5)
    {
        _someInt = param3;
        _someBool = param5;
    }

    public void Initialize(string something)
    {
    }

    public bool BusinessFunctionOne()
    {
        return _someBool;
    }

    public int BusinessFunctionTwo()
    {
        return _someInt*2;
    }
}

[TestFixture]
public class CopyPastedUnitTests
{
    [Test]
    public void TestOne()
    {
        var testclass = new MyClassUnderTest("1", "x", 10, 100, true);
        testclass.Initialize("bla");
        Assert.IsTrue(testclass.BusinessFunctionOne());
    }

    [Test]
    public void TestTwo()
    {
        var testclass = new MyClassUnderTest("1", "x", 10, 100, true);
        testclass.Initialize("bla");
        Assert.AreEqual(20,testclass.BusinessFunctionTwo());
    }
}

As you see, the Initialization Code of MyClassUnderTest is copy/pasted between the two unit tests. Now try change a parameter in the constructor and see all your tests break at once. Also note how the test class needs a separate Initialize-method to be called, so you need two lines to spin it up.

You can still keep OO Principles/DRY without having to make your tests dependent on each other! For example, you can check if your unit test framework has a Setup function that is called for every test. That allows you to move out shared source into a common function:

[TestFixture]
public class CopyPastedUnitTests
{
    private MyClassUnderTest testclass;

    [SetUp]
    public void InitializeTestclass()
    {
        testclass = new MyClassUnderTest("1", "x", 10, 100, true);
        testclass.Initialize("bla");   
    }

    [Test]
    public void TestOne()
    {
        Assert.IsTrue(testclass.BusinessFunctionOne());
    }

    [Test]
    public void TestTwo()
    {
        Assert.AreEqual(20,testclass.BusinessFunctionTwo());
    }

    [Test]
    public void TestThree()
    {
        var anothertestclass = new MyClassUnderTest("1", "x", 10, 100, false);
        anothertestclass.Initialize("x");
        Assert.IsFalse(anothertestclass.BusinessFunctionOne());
    }
}

This will create a new TestClass for each test, but that is using a common function. TestThree can still spin up it's own if it does need a different version. You could also create a private method within the Unit Test class that takes some parameters and returns a MyTestClass that the Tests can then use.

Bottom Line: Making the tests independent from each other does not mean you can't or shouldn't use the DRY principle. Test Classes are still normal classes that can have non-test variables and helper functions.

Unit Test code is still code and it should still be taken seriously. Don't let your unit tests become an unmaintanable mess. Always make sure that you are checking the right things with the least amount of code.

Comments (1)

Dillie-OSeptember 22nd, 2009 at 20:19

Good stuff! I'm just starting out TDD myself, so this gives me a few pointers in the right direction. Thanks!