When asking people what is the purpose of writing unit tests we usually get following answer:
“To verify that the code actually does what it is supposed to do.”
Among other responses we will find that unit tests help to validate that changes are not breaking existing functionality (regression), or that practising TDD will guide the design. But are those the only purposes? Well, there is more. Because unit tests are executing our code, they can show how it is working. We can use them as a specification of the code. Well crafted tests, which have explaining names and are easy to read, create a live specification of the module, which is always up to date.
Whenever we need to analyse a class, whether because we are new to it or we are coming back, we can use reports from unit tests to get the understanding how the class is working and what is it’s contract.
To build a specification from unit tests, we need to keep them organised and apply proper naming convention.
Test class names
First of all, there is no need to keep all unit tests related to given class in one unit test class. We can, and actually should, have more than one unit test class per tested class. Good convention is to have a class per functionality.
Let’s consider an example of recoverable policy which retries service call on communication errors. Whenever the connection drops or times out we want to wait some time and retry the operation, using exponential back off. Here we have few functionalities which should be tested:
- retrying on supported communication error
- rethrowing unsupported exceptions
- retrying logic
To organise our test suite let’s create a folder for all unit tests related to tested class. This will be name of tested class postfixed with word “Tests”. The postfix is important to avoid conflicts between the name of the class which we are going to tests and the name of the assembly containing tests. In out example it will be :
Unit tests classes are named after the functionality, such as:
WhenHandlesError. The class name partially describes test Arrangement, i.e. what are the cases we are testing.
Test method names
The method name may optionally describe further test Arrangement. It also describes Assertion. Example method names for the
WhenOperationFailsWithCommunicationException cases can be:
BecauseOfTimeoutThenRetires– which performs a test of the case when operation failed due to server time out
BecauseOfConnectionDropsThenRetires– for the case when remote server dropped connection
The naming convention is to elaborate more on Arrangement (
BecauseOfTimeout) and then explain what is the result (
ThenRetires). The first part can be skipped if all Arrangements are contained in class name. The second part is required and should not be omitted, same as method body should always have assertions.
Note, that with test method names there is no need to keep them concise as is the case with normal methods. They are not referenced anywhere and serve better its purpose when are expressive.
Once the naming convention is applied, we can use report from unit test runner to generate the specification. Below is a snapshot from ReSharper test runner:
I am grouping tests by Namespaces to create hierarchy which can be read like sentence:
CommunicationErrorRecoveryPolicyTests -> When operation fails with communication exception because of 502 then retries. When operation fails with communication exception because of time out then retries it.
To find out what exceptions are covered by the policy I will run associated tests (all contained in
CommunicationErrorRecoveryPolicyTests folder) and look at generated report.
We can even go one step further, and autogenerate HTML documentation from unit test reports. This could be run by CI server after each build and saved among artefacts.
Accompanying source code
The accompanying source code can be found on github at: https://github.com/mariuszwojcik/SpecificationByExample. For examples related to this article check
Properly organising test suites adds another aspect to unit testing. Good, expressive naming convention allows for building a specification of the code. It also helps with future refactorings and unit tests maintenance. Using that simple method allows for increasing teams agility and productivity by helping developers quickly understand purpose of tests and workings of tested classes.