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 :)