The interpretation of “unit” in unit testing to mean a class is wrong. This and an unfortunate misunderstanding of Kent Beck’s “run unit tests in isolation” still drive developers to test their classes in extreme isolation with undesirable consequences that are then attributed to test-driven development (TDD) as being unsustainable.
When we make our test units too small, we have to rely on mocks and stubs to stand for collaborators that are needed by the classes being tested. As the size of our test suite grows, so do the number and usage of mocks and stubs. There are two major problems with those.
First, when we use them in tests, we reveal the implementation details of the classes that depend on them. This is because we write expectations and verifications based on a very detailed knowledge of how the mocks and stubs are used.
Second, unlike real objects that change organically in response to changes in their collaborations, mocks and stubs are resistant to change due to the very specific ways that they are set up for test cases.
The first problem is a compromise that developers usually accept in exchange for testability. Also, many consider it to be harmless because encapsulation is still preserved.
On the other hand, the second problem is what hurts developers most and drives many to abandon TDD. The scale of the problem manifests itself once the test suite has reached a considerable size. At that point, any slight change in the nature of the collaboration between classes requires changes to numerous tests that use mocks and stubs to fake the collaboration, with many expectations and verifications suddenly needing revision. Since the task is usually tedious, and mocks and stubs do not have actual value in production, there is little motivation to spend time on maintaining the tests. Eventually the practice of TDD within the team disappears altogether.
Ideally, mocks and stubs would only be used for testing integration between components instead of collaboration between classes, but if their use cannot be avoided, their effect can at least be minimised. The most obvious way to do this is to lower the number of expectations that are set up in the test suite. By grouping tests according to the mocks and stubs that they use and the expectations that are needed, respectively, the number of test cases that need to be changed when collaborations change will be less.
There are other ways to make TDD more efficient, such as good discipline in the design of test cases, but one improvement that can be applied immediately is to reduce the use of mocks and stubs. And the way to do that is to exercise collaboration of actual classes in tests.