"I never make stupid mistakes. Only very, very clever ones."
–John Peel
It is very difficult to find stupid mistakes, but it's even more daunting when you are trying to figure out the clever ones. Debugging an application to know how to fix a problem is very expensive and time-consuming. Automated unit tests provide an extremely effective mechanism for catching regressions, especially when combined with test-driven development; it creates a test safety net for the developers.
This chapter covers the concepts of unit testing, quality of unit tests, external dependencies, and test doubles.
The Working with unit tests section introduces you to test automation and describes the characteristics of a good unit test.
The Understanding test doubles section explores the concept of external dependency and provides examples of test doubles. The following test doubles are explored:
A common understanding of unit testing is the testing of the smallest possible part of software, such as a single method, a small set of related methods, or a class.
In reality, we do not test methods; we test a logical unit and its behavior instead. Logical units can extend to a single method, to an entire class, or a collaboration of multiple classes.
For example, a standard calculator program can have an add method for adding two numbers. We can verify the add behavior by invoking the add method, or we can design the calculator program to have a simple calculate API, which can take two numbers and an operation (add, subtract, divide, and so on). Depending on the operand type (integer, double, and so on), the calculator may delegate the calculation to a collaborator class, such as a double calculator or a long calculator. We can still unit test the add behavior, but multiple classes (units) are involved now.
A unit test verifies an assumption about the behavior of the system. Unit tests should be automated to create a safety net so that the assumptions are verified continuously and a quick feedback can be provided if anything goes wrong.
The following are the benefits of test automation:
A unit test should exhibit the following characteristics:
testUnauthorizedAccess()
or rather when_an_unauthorized_user_accesses_the_system_then_raises_secuirty_error()
. The latter is more readable and expresses the intent of the test.As per the preceding best practices, a test should be executed as fast as possible. Then what should you do if you need to test data access logic or file download code? Simple, do not include the tests in an automated test suite. Consider such tests as slow tests or integration tests. Otherwise, your continuous integration cycle will run for hours. Slow tests should still be automated. However, they may not run all the time, or rather they should be run out of the continuous integration feedback loop.
You cannot automate a unit test if your API class depends on slow external entities, such as data access objects or JNDI lookup. Then, you need test doubles to isolate the external dependencies and automate the unit test.
The next section covers test doubles.
18.221.123.73