Unit Testing Checklist: Keep Your Tests Useful and Avoid Big Mistakes
A unit test is a set of methods launched frequently to validate your code. It is usually a good idea to write code in this order:
- Write a class API
- Write a method to test the API
- Implement the API
- Launch the unit tests
Why write unit tests? They validate current and future implementations. They measure code quality. They force you to write testable, loosely coupled code. They’re cheaper than manual regression testing. They build confidence in your code. They help teamwork.
Why use a checklist? Unit testing can be harder than actual implementation. Unit testing forces you to really think things through. But unit tests should be simple, direct, and easy to read and maintain. You also need to know when to stop writing tests and start writing the implementation.
Use this checklist to be sure your tests are really useful and to the point. Remember: the checklist helps you avoid big mistakes, but you need to make sure of the following:
□ My test class is testing only one class.
o You are testing a class API to be sure the public contract is respected.
□ My methods are testing only one method at a time.
o Be sure not to test private methods! They are hidden implementation details, not API.
□ My variables and method names are explicit.
o For example, store an expected value in an expectedFoo variable instead of just foo. If you test many combinations, use composed variable names like inputValue_NotNull, inputValue_ZeroData, inputValue_PastDate, etc. (according to your application’s coding convention).
□ My test cases are easy to read by humans.
o Future maintainers should be able to read your tests before reading the implementation. This will help them understand a class API before tweaking or debugging it.
□ My tests respect the usual clean code standards.
o There should be no flow control in a test method (switch, if, etc.). A good test is just a very straightforward sequence of setup/validate instructions. If necessary, use sub-methods to factorize and make your tests easier to read. In case of multiple scenarios, use multiple test methods (one for each case).
o For example, a test method should fit on screen without scrolling – 1 to 20 lines long. If the method is longer, consider writing multiple test methods for each case instead of jamming them together.
□ My tests are also testing expected exceptions.
o In java, use @Test(expected=MyException.class).
□ My tests don’t need access to a database.
o Or if a test does need database access, then it must be a mocked, “fire and forget” temporary database that you fill with test cases for every new test method (use the Setup/Teardown methods to prepare it).
□ My tests don’t need access to network resources.
o You can’t rely on third parties like network or device presence to validate a method (use mocks).
□ My tests control side effects, limit values (max, min) and null variables (even when they throw exceptions).
o You want to make sure these problem cases never occur, even when the test won’t be used during maintenance.
□ My tests can be run at any time, at any place without needing configuration or human intervention.
□ My tests pass for the current implementation and are easy to evolve.
o Tests really exist to support code evolution. If they are too hard to maintain or too light to refine the code, then they are a useless burden. (Many developers avoid unit testing for this reason.)
□ My tests are concrete.
o In Java: don’t use Date() as input for a method you are testing, but build a concrete date out of Calendar (don’t forget to force the timezone). Other example: use name = “Smith”; instead of name = “name”; or name = “test”;
□ My tests use a mock to simulate/stub complex class structure or methods.
o Remember to test only one class API at a time.
o Never test third-party libraries through your own classes. Libraries should come with their own tests (this is actually a good way to choose a library).
□ My tests are never @ignored or commented out. Never. Ever.
□ My tests help me validate my architecture.
o If you can’t test a method or a class, your design is not agile enough.
□ My tests can run on any supported platform, not just the targeted platform.
o Don’t expect a particular device or hardware configuration. Otherwise, your tests will make migration tougher and you will be incentivized to disable them.
□ My tests are lightning fast!
o Slow tests shouldn’t drag you down. Speed encourages you to launch your tests often. It also helps to reduce building time on Continuous Integration systems.
o Use a test runner that allows you to launch one test at a time while you are writing it. Use “delay” or “sleep” with caution – i.e., only in some edge cases, like waiting for notifications or clock-based methods.