Chapter 2. Unit testing practices

 

Chapter 11 from Good Code, Bad Code by Tom Long

This chapter covers

  • Effectively and reliably unit testing all the behaviors of a piece of code
  • Ensuring that tests are understandable and that failures are well-explained
  • Using dependency injection to ensure that code is testable

Chapter 10 identified a number of principles that can be used to guide us toward writing effective unit tests. This chapter builds on these principles to cover a number of practical techniques that we can apply in our everyday coding.

One of the key principles in chapter 10 was the key features that good unit tests should exhibit. The motivation for many of the techniques described in this chapter directly follow from these, so as a reminder the key features are as follows:

  • Accurately detects breakages — If the code is broken, a test should fail. And a test should fail only if the code is indeed broken (we don’t want false alarms).
  • Agnostic to implementation details — Changes in implementation details should ideally not result in changes to tests.
  • Well-explained failures — If the code is broken, the test failure should provide a clear explanation of the problem.
  • Understandable test code — Other engineers need to be able to understand what exactly a test is testing and how it is doing it.
  • Easy and quick to run — Engineers usually need to run unit tests quite often during their every day work. A slow or difficult to run unit test will waste a lot of engineering time.

11.1   Test behaviors not just functions

11.1.1   One test case per function is often inadequate

11.1.2   Solution: concentrate on testing each behavior

11.2   Avoid making things visible just for testing

11.2.1   Testing private functions is often a bad idea

11.2.2   Solution: prefer testing via the public API

11.2.3   Solution: split the code into smaller units

11.3   Test one behavior at a time

11.3.1   Testing multiple behaviors at once can lead to poor tests

11.3.2   Solution: test each behavior in its own test case

11.3.3   Parameterized tests

11.4   Use shared test setup appropriately

11.4.1   Shared state can be problematic

11.4.2   Solution: avoid sharing state or reset it

11.4.3   Shared configuration can be problematic

11.4.4   Solution: define important configuration within test cases