OCD TDD

So, one fine morning at work we were writing some unit tests to assert that a query object, given the right “name” and “environment” arguments, will return the correct result.


//Arrange
_environment = "env";
_name = "name";
_expectedResult = new Bar {Name = _name, Environment = _environment, Enabled = true};
var queryMock = new Mock<FooGetQuery>();
queryMock.Setup(x=>x.Execute(_name, _environment)).Returns(_expectedToggle);

//Act
var result = SUT.Foo(queryMock.Object, _name, _environment);

//Assert
result.Name.ShouldBe(_expectedResult.Name);           
result.Environment.ShouldBe(_expectedResult.Environment);

In the spirit of pure TDD we decided to make the test pass with the minimum required code:

public string Foo(IFooGetQuery getQuery, string name, string environment)
{
  return getQuery.Execute("name", "env").ToJson();
}

For whatever (probably stupid) reason, we kept on working in other parts of the app, leaving the hardcoded values in until our integration tests caught the error. The thing is that the unit test passed, but for not the right reasons. It only passed for the specific values that we gave in the arrange part. What about all the other names, environments and all of their combinations? What about the infinite amount of strings that can be passed in any of those arguments? We definitely can’t just duplicate the test just for changing them. The guys in the white clothes and the straight jacket will be arriving shortly after our 1000th copy/paste.

(Note: to be fair Nunit has nice features for trying out a test case with many possible inputs and combinations of values but still nothing truly exhaustive when it comes to random values)

We realized that the way we were framing the question in our minds was wrong. Instead of saying “when I pass name and env as inputs I want to see this result” we should be saying “when passing any name and any environment I want to see the result of the query object with those random values”.

And we also realized we already had a tool in our belt that could solve this problem for us. Autofixtures! We were already using them to instantiate complex objects in a single line with all properties populated but then we thought “why not use them for base types as well?”. So the test ended up like this:

//Arrange
var fixture = new Autofixture();
_environment = fixture.Create<string>();
_name = fixture.Create<string>();
_expectedResult = new Bar {Name = _name, Environment = _environment, Enabled = true};
var queryMock = new Mock<FooGetQuery>();
queryMock.Setup(x=>x.Execute(_name, _environment)).Returns(_expectedToggle);

//Act
var result = SUT.Foo(queryMock.Object, _name, _environment);

//Assert
result.Name.ShouldBe(_expectedResult.Name);           
result.Environment.ShouldBe(_expectedResult.Environment);

And then to make the test pass we have to write this:

public string Foo(IFooGetQuery getQuery, string name, string environment)
{
  return getQuery.Execute(name, environment);
}

Because every time the test runs new random values get generated, we are forced to use the parameters passed to the method and no TDD purist can make it pass in any other way.

If you setup the test like the first case with hardcoded values, you can really make it pass by either hardcoding the values in the actual call or passing the method parameters. With the approach of randomizing everything we basically make sure that small edge case will never get past the unit test stage. Plus as far as test readability goes, it really states “any string/number/object” instead of “the string/number/object”.

I appreciate that this might seem like an overkill for some but this is where your common sense comes in and you make a decision based on your experience and current situation. You can go too far the other way and make your tests harder to write and read so use this wisely! Let me know about your thoughts in the comments and happy coding :)


To view or add a comment, sign in

More articles by Michael Tomaras

  • The journey towards quality

    I am pleased to be able to say I was a member of the Tombola team for a whole year and sad to have to leave right where…

    1 Comment
  • Visualise and create build chains in TeamCity, the easy way

    We all like nicely organised and efficient deployment pipelines (at least the really OCD among us do :). There are the…

  • CI your database with Redgate tools

    How we got here We all know there are all kinds of people around with all kinds of interests and that inadvertently…

    1 Comment
  • The importance of finishing work

    The premise So, we’ve arrived at this sweet spot as a team and are able to break down work effectively into small…

  • Meet everyone

    You’ve just started at your new job. You’ve got your new laptop, on your new desk, with two extra new screens and a…

  • The curious incident of the configuration file in the system32 folder

    The premise There are some little devOps details that you don’t read so often in programming blogs because, well…

  • Neat little trick to debug your build agent’s operations

    Ever been in the situation where you’re using a CI server (like TeamCity in our case) and the build step is failing…

  • Great Developers

    I came across this post by David Starr recently and while reading it I could hear the voices in my head going “Yes…

Explore content categories