TDD

Write Less, Test More (Part 1)

Just like my software development skills, my approach to writing tests over the years has changed, but it’s only recently I’ve started to use more than the basic Assert functionality of testing frameworks.

This post explains an approach I used to test a particular module that came about through TDD and how to test a number of scenarios, by writing just one test method.

The problem

I want a class that, when given a sentence, will return a collection of string containing all words. This collection should only have alpha characters in it – i.e. spaces and punctuation are to be ignored.

My tests

As I’m dealing with a collection I think it makes sense that my method should cope with an empty sentence, a sentence with one word and a sentence with n words. At least one of these n word sentences should be peppered with some non alpha characters.

A reasonable approach might be to setup my test class, then write my first test to test the ‘0’ case. This could look like:

[Fact]
public void GivenAnEmptyStringWhenFindAllWordsExpectAnEmptyList()
{            
    var expectedResult = new string[0]; 

    var result = _wordFinder.FindAllWordsInLowerCase(string.Empty);

    Assert.Equal(expectedResult, result);
}

(Where _wordFinder is the class under test. This test asserts that an empty collection is returned from FindAllWordsInLowerCase method).

Run the test – it’ll fail. Go off create a WordFinder class, with the method FindAllWordsInLowerCase(string input), write the code to return an empty string list

Re-run the test and hopefully the test will pass.

Great. Next test is the scenario with one word. Forget DRY; copy and paste previous test, rename it, change the inputs and expected output and rerun.

[Fact]        
public void GivenOneWordWhenFindAllWordsExpectOneItemInLowerCase()
{
    var expected = new List<string> { "one" };
            
    var result = _wordFinder.FindAllWordsInLowerCase(OneWord);

    Assert.Equal(expected, result);
}

Write some code to get this to pass, then, copy and paste test class again, alter inputs and expected outputs… you get the idea.

This will work and is an approach I’ve used for a while. The outcome is great – we’ll have a class with very high coverage that was developed just as our tests informed it. For me though, I prefer to be a bit more DRY than this.

Cue…. Xunit Theories

In a nutshell, an XUnit Theory, is a means to perform a data driven test. Data is provided in an [InlineData(…)] attribute. Each InlineData attribute applied to a Fact, will result in  a different test.

Define your test method and define your theory’s data and you can run multiple inputs and test the expected outcomes through a lot less methods – one in this case! Putting this into practice, our two previous tests now look like:

 [Theory]
 [InlineData("", new string[0])]
 [InlineData("One", new[] { "one" })]        
  public void FindAllWordsInLowerCaseTheory(string input, IEnumerable<string> expectedOutput)
  {
     var result = _wordFinder.FindAllWordsInLowerCase(input);

     Assert.Equal(expectedOutput, result);
  }

And when run with ReSharper, produces an output that looks like:

resharp21

Unfortunately it can’t output expectedOutputs cleanly when it’s a collection, but we have a test that’s DRY and easier to read from a non technical perspective. Potentially a non developer could even provide these inputs and outputs, lending itself to BDD very nicely.

You can also achieve a similar result with nUnit, using it’s [DataSource] attribute.

As we are using XUnit, I suspect it may be possible to modify the output behavior, so that it can display the expected collection of strings better. Not in scope for this post though.

So finally, when all our test scenarios are created our code looks like:

[Theory]
[InlineData("", new string[0])]
[InlineData("One", new[] { "one" })]
[InlineData("One, two", new[] { "one", "two" })]
[InlineData("ONE,4--;@@ ::tWo", new[] { "one", "two" })]
[InlineData("paulthecyclist tested; PaulTheCyclist failed @#$@! PaulTheCYclist Coded, paulthecyclist tested => PaultheCyclist passed!", new[] { "paulthecyclist", "tested", "paulthecyclist", "failed", "paulthecyclist", "coded", "paulthecyclist", "tested", "paulthecyclist", "passed" })]
public void FindAllWordsInLowerCaseTheory(string input, IEnumerable<string> expectedOutput)
{
    var result = _wordFinder.FindAllWordsInLowerCase(input);

    Assert.Equal(expectedOutput, result);
}

This produces five tests. I think it’s easier to read than having a test class with five separate methods and certainly less code to write/copy and paste.

There is an even more fun way to write your tests using XUnit though, which addresses the readability of the output along with other things, that I’ll cover in part 2.

Advertisements