One of the biggest problems with unit tests is poor readability. Bad naming convention, long methods, hard to understand Arrangement and Assert parts are making unit tests one of the hardest code to read and refactor. In previous article,
Unit Tests as code specification, I presented the way to increase readability of test method names and use them to create code specification. Now I would like to tackle the problem of unreadable test methods.
Most of unit tests methods start with test arrangement. It usually takes a form of setting up mocks and initialising local variables. It’s not uncommon to start the test with code similar to the one below:
_testService.Expect(i => i.Foo()).Throw(new WebException("The operation has timed out")).Repeat.Once();
_testService.Expect(i => i.Foo()).Return(9).Repeat.Once();
The example prepares _testService
to throw a WebException
the first time the method Foo is called and then return 9 when called again. There are few problems with this set-up. It requires careful reading and analysis of the code to understand the arrangement. If there are more set ups and you get confused about them, you will need to analyse it again. In the presented example, some information is concealed – the WebException
represents a time out exception thrown by Google AdWords service, which is in opposition to CommunicationException
thrown by web services such as Bing Ads.
The above example is fairly simple, but the situation gets worse when there is a need to set-up few mocks, or there are parameters to be passed into mocked method.
Lets see how above example could look when using DSL:
_testService
.SimulateGoogleAdWords()
.FailWithTimeOut().Once()
.Then().Return(9);
We are using Domain Specific Language to say that test is for Google AdWords, where service throws time out when called first time and then returns 9 after retry. This version is easy to read and remember, carries the information that we are testing the case with Google AdWords, and it is easy to reference back to it.
The implementation of DSL is done using an extension methods on mocked object. Sample code associated with this article uses Rhino Mocks, but it can easily be converted to use other mocking frameworks.