Tests that Matter

When starting to learn about software development and coding, writing tests seems to be the least fun and something that many try to avoid as much as possible. At some point everyone has been told that writing tests is important, and that TDD is the only way to write good code. Some will try to embrace this and enforce some kind of testing into their workflow while others will ignore it. The main problem is that nobody tells you what the purpose is and often the focus is, in my opinion, wrong.

[Test]
public void MyMethod_Null_ReturnsNull()
{
    var result = _someClass.MyMethod(null);
    result.Should().Be(null);
}

I often find traditional "TDD tests" to be way too focused on the details of the implementation. If the sole purpose of a test is to provide test coverage on a specific line in a method, it will most likely introduce confusion and uncertainty later. Is it really that important that this method returns null if the argument is null? Or was that test written before even knowing what the propose of the method was, and because the first you learn about TDD is that you should start with a failing test?

Why do I write tests?

Tests should provide the following to a code base:

  • Allow safe refactoring
  • Describe a process, function or API
  • Ensure correctness of the solution

This might seem obvious, but I find that often tests are written without a specific goal in mind, which is what I want to highlight. The second point is often ignored, which leads to tests that aren't important and thus causes confusion when looked at by somebody that didn't write it.

I try to think trough what I want to describe with the tests I'm writing, and make it as readable as possible. Tests should be connected to the requirements at hand, being a user story, or some functionality required by a class or method. This way the tests can be used as documentation for the next developer working on the code, as well as ensuring that the code you have written behaves the way intended.

You forgot that tests protect against bugs!

I am not sure if this always holds up. As mentioned, tests written correctly will ensure that the code behaves as intended, but written poorly they might do the opposite. When implementing new functionality or changing old behavior, you will have to read, interpret and change the tests covering the code as well.

[Test]
public void Should_Update_The_State_According_To_The_Requirements()
{
    var initialState = new ObjectOfSomeSort("initial");
    A.CallTo(() => _myDependency.GetSateOfMachine()).Returns(initialState);
    var result1 = _someClass.MyMethod(1);
    result1.Should().Be("initial1");
    result.UpdateState();
    result1.Should().Be("1initial")
    _myDependency.InternalState.Should().Be(null); 
}

The above test is of course meaningless, and the main problem is obviously the code tested more than the test itself. It is supposed show why gibberish tests makes life hard. If I were to change this code, which might have worked for 10 years without problems, there is no way for me to know how the behavior was intended to function. The test might ensure that some critical functionality is working, but it isn't written in a manner that allows me to understand what it is, rendering me unable to preserve it.

Given – When – (Then) * n

This technique is seen in many frameworks and suggested workflows for writing tests. You will find books written specifically about "Behavior Driven Development" which is the formal notion of what I describe in this post (google BDD testing). There are also many good tools which help with creating such tests, one of which Specflow is probably the most known. I will not go into details on neither BDD as a concept or specific tools in this post. I will show a simple technique to get started writing more behavior centric tests.

[TestFixture]
public abstract class Scenario
{
    [TestFixtureSetUp]
    public void SetUp()
    {
        Given();
        When();
    }

    protected abstract void Given();
    protected abstract void When();
}

public class ThenAttribute : TestAttribute { }

Voilà! Create a file with this content and you have your very own BDD testing framework in 9 lines of code. This abstract class will force you to be explicit about what and how you write tests, as well as provide a structure that reads well in the project structure and in the test runner. Essentially each scenario or behavior will be in its own file, so no more 1000 lines long files to scroll through. Every test or "Then"-block will be run as a separate test even if they are defined in the same scenario file.

public class When_invoked_with_a_given_state : Scenario 
{
    public override Given()
    {
        //Setup dependencies to simulate a given state
    }

    public override When()
    {
        //Call method, pass event etc.
    }

    [Then]
    public void Should_publish_a_given_event()
    {
        //Assert that an event of correct type is published
    }

    [Then]
    public void Should_update_state()
    {
        //Assert some repository is updated 
    }
}