When writing my article AMQP Hello World as an Exploratory test I realised that the test is written in a different way then many tests I read. I felt that I should explain why I choose a different design when writing tests since I think this would benefit many projects.
The code below is taken from the example provided in the AMQP article and can be found at my github repository.
My main driver when writing tests is readability. A test should read pretty much like a well written requirement specification. It will detail the requirements for set up before testing can start. It will then details the given state of the application, moving on to the state when something happens and then verify that expectations were met. Before finishing the application needs to exit gracefully.
When I work with tests in Java I tend to use JUnit 4. JUnit 4 does not need naming conventions, such as setUp, testXyz and tearDown. This is all driven with annotations, which frees up the name space and allows the developer to instead name the methods according to what they do.
Below is the set up method for my test:
The method name clearly states what the method does. The annotation clearly states which role the method plays in the test life cycle. This makes the test code easier to read and debug.
When working with the actual test methods I use names that clearly identifies what the test verifies. Below is a test methods from the example. The method name points out what the method tests. The content is then written in a given-when-then pattern to detail the steps that are required.
This way of working has a couple of side effects that are quite useful. When adding more tests, either as new test methods or as new test classes, it is easy to spot similarities which can be shared in between tests. This in turn helps to enable the creation of a domain language for the tests of the application. It is important though not to forget that tests should first and foremost be readable. Creating to much abstraction makes it harder to follow the test code.
The given-when-then methods clearly defines the steps taken to perform the tests but does not slow the reader down with large amounts of implementation details. This makes the tests a good source of documentation. It further more makes it very easy to see if a test failed due the state being changed before or during the operation that is under test.
The tear down method is, just as the set up method named according to what it does rather then its function in the test life cycle. Its function in the test cycle is defined by its annotation.
I find that this style of test design makes the tests much easier to read, maintain and understand. They also work much better as documentation when trying to understand generalised well written code.