A Unit Tests Checklist Poster

Maher Malaeb
4 min readSep 28, 2019

--

A simple list of 10 rules to follow when writing unit tests, presented in a poster format

Checklists are an excellent tool to ensure that a specific process is being applied. They are designed to be read fast, and to reduce the amount of human error happening when applying a process. Checklists are heavily used in the health-care industry and aviation systems for example where mistakes are critical.

When it comes to software unit tests, although mistakes are usually less critical, tests can still benefit from a checklist to ensure a standard is always applied. The checklist for unit tests is a set of 10-amedments that each test must adhere to. I present the 10 rules of unit testing in a poster than can be used as a reference to always check before submitting unit tests to the code line.

You can also download: full size A3 poster | full size A4 poster

Expressive names

Writing good test names is key to help future readers of the code understand what the test is doing. They also serve as code documentation to understand its functionality. There are many conventions to write unit tests. The convention I recommend comes from the behavior driven development (BDD) discipline and it goes as follows

Given <a current state>
When <something happen>
Then <a specific result is expected>
// example of a test name:
given_valid_passport_when_ticket_is_paid_then_check_in_to_flight

This convention will help write tests that deliver business value.

Offline

Tests should not depend on external resources like the availability of a database or a REST service to be executed and to pass. If such dependencies are present than tests will take longer to execute, and they will fail if an error occurred in the external service. Instead of depending on external resources, it is recommended that such resources are mocked in unit tests.

Order independent

Multiple tests in a test class should not depend on each other. Tests should be self-contained and always pass regardless of the order they are launched in. Many test runners execute tests in random order so having dependencies between tests will introduce random failures

Platform independent

Tests should pass when executed on all supported platforms. For example, the same unit test that pass on the Windows machine of the developer should pass on the Linux machine when the Continuous integration server launches the tests.

Pass consistently

In a given state of code, the test should either always pass or always fail. There should be no randomness which makes it difficult to identify and debug issues. For example, a main cause of randomness in unit tests is when we use timeouts to test asynchronous code. Asynchronous test libraries can be very useful to avoid such situations

Fast

Codebases will hold thousands and thousands of unit tests. Those tests will also be launched thousands of times in the development process. So even Milliseconds gain in each test time can make a huge difference on the feedback time of out test suite. For that reason, it is recommended that a unit test execution be in the order of Milliseconds to make the test suite scalable.

High coverage

Although code coverage is not a complete indicator about the quality of our unit tests, it is a good starting point. Usually when code coverage is around 80% and the tests are complying to the other principles, we can guarantee higher quality unit tests.

Mock Cautiously

Mocks and stubs are an excellent tool that should be in every developer’s toolkit when writing unit tests. It can help isolate units from their dependencies and test specific behaviors of an application assuming a specific stable state. But if mocks are overused one will end up testing implementation details which will create test technical debt in the long run. Refactoring will be much harder because every refactoring will break many unit tests which are not easy to fix.

When noticing the creation of many mocks and testing implementation details, then one should consider refactoring and applying single responsibility principle to the class under test and it should help resolve this issue.

Test behavior

Each production class has a purpose. Only the purpose of the class should be tested and not how the purpose is achieved. In other words, we should be testing the API of the class and not the implementation details. To insure this is applied, one should never change the visibility of methods and fields just to be able to access them inside tests.

When noticing that it is impossible to test without changing visibility, then it is a sign that there is something wrong in the design of the class. Refactoring the code instead of exposing more methods and fields should be considered.

Reproduce Bugs

When fixing a bug that was detected, the first thing to do is write a failing unit test that reproduces the bug. This guarantees that the bug will never happen again. Then after writing the test fix the bug in the production code.

--

--