Goal of unit testing is fundamentally different than with other kinds of tests, such as integration or feature tests. Many resources on the web already cover basic principles and benefits of unit testing, so there is no need to go into much detail here.
Before moving on to guidelines, let’s quickly recap on:
|What unit tests do for you||What unit tests DON’T do for you|
|Detect issues early in the development cycle||Guarantee bug-free code|
|Provide a safety net when refactoring||Improve quality of the codebase|
|Ease integration of components||Eliminate the need for QA|
|Document how classes and methods should be used|
Most important things you should test are: business logic, utilities and helpers.
You should NOT test vendor libraries and framework components. For example, if:
- Database engine stores/updates data successfully
- REST client sends a request to the server
Listcontains an item after calling
List.Add(item)(unless it’s your own list implementation!)
Vendors write their own tests to make sure their library works, so there is no need for you to do it.
Code coverage is an indicator of whether your code was executed by unit tests, not if it works properly.
I’ve worked on a project where 95% of the code were tool-generated classes for a custom language parser. There was no need to test them, so the code coverage was low. Even though our code was covered, bosses weren’t happy.
By running example files through the parser, we boosted code coverage to more than 90%, but it added no value except for pretty numbers in the reports.
If you have many different assertions inside a unit test that fails, it won’t be obvious what exactly went wrong. Consider splitting large tests into multiple smaller ones.
- A unit test should be focused on a single functionality
- Tests should be easy to read and self-contained
Note: Forking logic means more tests, not more assertions. If a method can take more than one path with an
switchor by other means, every path should be tested with separate tests.
A test needs to be completely isolated from other tests (not require other tests to run in order for it to pass).
If arrays are involved, a good starting point is to test with 0, 1, and 3 items. Those are common edge cases, and you can add more if required.
Writing tests first, or at least thinking about testability when writing code goes a long way:
- Prefer short methods that do only one thing
- Prefer pure methods (ones that do not touch shared state)
- Accept only what you actually need as parameters to a method
- Separate common code into helper classes
Treat your tests as regular production code, and apply best practices when writing them. Unit tests should be maintained, refactored, documented, reviewed, and regularly executed to make sure they always pass.