Fortunately most developers now realise the importance of unit testing, so if were all writing tests how do we make sure were all writing good ones.
On the face of it it may seem that the addition of any unit tests must be a good thing right? Well actually no, badly written unit tests can be counter productive and do more harm then good. Badly written tests are unlikely to be maintained and the results are often not trusted, you should never hear anyone say "Yeah that one always fails".
So how do we tell good tests from bad?
Triple AAA Standard
A good unit test should consist of three clear sections.
First we Arrange, we create the conditions that represent the situation were trying to test. This may involve setting up mock objects to return certain values or setting certain data on the class under test (CUT).
If this arrangement phase is difficult to achieve it should be treated as a smell that the CUT is too tightly coupled to its dependencies, has a lot of internal state or that its APIs have side-effects.
Next we Act, we exercise the CUT by using the API were trying to test.
This section of the test should be small, a unit test should be focused on testing one and only one aspect of the CUT, if this cannot be done without multiple API calls again this is a smell of internal state and side-effects being at play.
Lastly we Assert, we verify that what we wanted to happen actually happened.
Again this section should be relativity small if our test is limited to testing only aspect of the class.
By following this triple AAA pattern we can
- Maintain The Tests: In each test it can clearly be seen what conditions are being tested, which API of the CUT is being tested and what the expected outcome is, this makes it much easier to fix a broken test if something changes in the API or the CUT's implementation.
- Read The Tests: It should be easy to read tests written using the AAA pattern making it easier to identify missing coverage, this can also help make the tests act as documentation for the CUT.
- Identify Smells In The Design: As we've already said an inability to be able to follow the AAA pattern is indicative of a smell, for example if you see Assert steps in the Act phase ask yourself why the design of the CUT makes that necessary (incidentally this is a lot less likely to happen if your employing TDD).
- Identify Bad Tests: Tests that shouldn't qualify as unit tests will jump out at you with large Act or Assert sections demonstrating that there doing too much.
Flying FIRST Class
We should never have any fear about running unit tests we should be happy to run them on every commit. For this reason they should be Fast, if the tests take an excessively long time to run we might decide to not run them on every build. If we do that how much bad code or bugs are going to make it into the build before we spot that theirs a problem?
Each test should only ever fail for one reason. Each test should be Isolated, both from external factors relating to the CUT's dependencies but also from each other. The purpose of unit tests is to test the functionality of a single class not a group of classes.
Unit tests should be Repeatable you should get the same results every time they run with no intermittent failures. If this isn't true it means either the CUT cannot always be trusted to do its job, its behaviour is non-deterministic or that the tests do not adequately control the environment in which the tests are being conducted.
Tests should be Self-Verifying, the results of the tests need to be trust-worthy. If all tests are green we should be happy to ship if any test is red we should be prepared to hold back until the problem is investigated and fixed. The instant we don't trust green tests or we ignore red tests were deciding to throw the dice on every build, unit testing should not be seen as a chore they should be how we verify the health of the code base.
Test should be written in a Timley manner they should be written before the CUT so that the tests drive development (TDD) we should be writing code to meet the tests not writing tests to meet the code as this proves very little about whether the code is doing what it is supposed to do now or in the future.
What this adds up to is that unit testing shouldn't be an afterthought in your development it should be front and centre.
You should take as much pride in how you right your tests as how you write your code, what those tests tell you about your code base should be clear and unambiguous and you should trust that message.
As your code base develops the tests should be run early and often, they should go forth and multiply and no broken test should be left behind.