Monday 27 October 2014

Other Test Naming Conventions

The length of my last post “Unit Testing Evolution Part II – Naming Conventions” was already massively over budget and it was largely about how I name tests within my own C++ codebase. In my professional programming career I’m currently doing mostly C# and so am bound by the tools that have already been chosen, i.e. NUnit. Here there is still a fair bit of creativity with using multiple nested classes and method names to create an overall structure that avoids relying solely on overly long method names. Given that a test framework like NUnit can also easily be used for other forms of testing, such as component and integration level tests, I’ve found that a little more structure helps deal with the additional complexity that can arise one you start dealing with more dependencies or the need to check multiple bits of state.

Unit Tests

When you look closely at the way, say, NUnit displays the full names of failed tests you can see that it includes the names of any nested classes as well as the method name. We can exploit this by breaking the test name into pieces and using more nested class names to create sub-namespaces, for example:

public static class StringTests
{
  public static class trimming_should_
  {
    [Test]
    public void remove_leading_whitespace()
    {  } 

    [Test]
    public void remove_trailing_whitespace()
    {  }
}

Note the trailing underscore on the nested class name provides a marginally nicer output:

StringTests.trimming_should_.remove_leading_whitespace
StringTests.trimming_should_.remove_trailing_whitespace

In this example we have used the top-level class as a namespace to categorise the tests in the module, and then used nested static classes as another namespace for the “feature” under test (trimming). In this instance we could probably have used a real namespace for the top-level static class, but that class often isn’t static and so you would end up with two slightly different test styles (e.g. indentation) which can be irritating.

Component/Integration Tests

In a unit test you often have only very simple “arrange, act, assert” needs, but as you move up the testing pyramid into component and integration tests you usually have a little more setup, more scenarios and more state or actions to assert. Consequently it can be beneficial to add more levels of nested classes to group common scenarios together (the “when xxx”), and at the same time also split the asserts out into separately named methods (the “then xxx”) to make it clearer what the assertion is telling you.

public class ConnectionPoolTests
{
  // Setup
  . . .
  public class when_a_connection_is_acquired_
  {
    [Test]
    public void then_the_returned_connection_is_open()
    {  }

    [Test]
    public void then_the_connections_in_use_count_is_incremented()
    {  }
  }
}

While in a Cucumber test you would start with a “then”, and the remaining asserts would begin with “and”, that doesn’t really work when an arbitrary test fails, however you might find it makes reading the test’s source code easier instead.

Setup == Given

In an NUnit test you have the SetUp and TearDown helper functions that execute around the test method. The SetUp helper is akin to the “Given” in a Cucumber style test and so you can name it something more imaginative than just “SetUp”. It doesn’t make an arbitrary failing test any easier to diagnose but it does allow you to document the test setup better:

public class WebProxyTests
{
  [SetUp]
  public void given_a_proxy_to_the_remote_service_
  {  }

  public class when_I_make_a_valid_request_
  {
    [Test]
    public void then_I_receive_a_successful_response_code
    {  }

    [Test]
    public void then_the_body_contains_the_data
    {  }
  }
}

Performance

One consideration you need to bear in mind is that structuring your tests this way makes the “arrange” and “act” aspects get executed more frequently because each test case is only asserting part of the outcome. If either of these steps are lengthy it can drag out the overall test suite run time which is probably undesirable.

No comments:

Post a Comment