Unit tests are an indispensable part of the development process. Unfortunately, some programmers experience a hard time when writing good code for their tests. This is not because they lack knowledge, but because they introduce expensive complexity within their code, which makes their test code ineffective. While there is no single definition of clean test code, there are some things that are universal. For instance, clean tests should contain an easily readable code. If the test code is easy to read, then it is easy to understand how it works and consequently, easier to find problems should the test fail.
THE FIRST PRINCIPLE
In his amazing and magnificently written programming book — Clean Code, the prolific Robert C Martin, commonly known as ‘Uncle Bob’, shares some eye-opening experiences for anyone looking to improve their coding skills. When it comes to tests, he identifies three rules of test-driven development every programmer should follow. He also describes the F.I.R.S.T principles for clean tests. These are:
- Fast: The best tests are fast enough. If tests run slowly, you’ll probably be discouraged from running them frequently.
- Independent: Always strive to avoid test interdependence. At no time should a test depend on the state of preceding tests. This allows you to run tests individually, which is great for debugging in the event of a test break.
- Repeatable: Ideally, every test should be repeatable in different environments without being erroneous. If the tests do not rely on a database or network, then it should work in any environment as it only depends on the code to be tested. When such a test works in one environment and fails in another, then chances are the test or the method is set up wrongly.
- Self-validating: Each test should have a single Boolean output- either a pass or a fail. You do not have to check through the log files to identify whether the test result was valid or not.
- Timely: Unit tests should be created in a timely manner. In TDD, the ideal time for creating test code is before writing the production code.
One more thing- Each test should have its own function
If you want to run multiple tests, for example, each test should have its own function. This is where the one test-one function notion comes into play. Consider the anatomy of this simple test:
Lumping the two tests together is confusing because you’re not sure whether the output from test one affects test two. Secondly, the starting state of test two is not explicit. The test runs regardless of the state test one finishes. There’s so much ambiguity to think about. Now think of a situation where you lumped about 10 items in a single test and something went wrong. It will be hard to identify which of the 10 items failed.
To fix this, we can set up two functions for each test as shown below
In this test, we have an explicit starting state for test B. Notice how this makes the tests easier to read and analyze.