Most experts seem to agree that writing tests is a prerequisite for professionalism. Michael C. Feathers defines legacy code as “code without tests”. Robert “Uncle Bob” Martin claims: “it has become infeasible for a software developer to consider himself professional if he does not practice test driven development.”
However many developers feel that they are slowed down by writing and maintaining even more code (the actual code and the tests).
How often did you:
– Need to fix broken tests?
– Ask yourself “why should I test this simple code?” just to discover a bug there a few weeks later?
– Need to throw away a large set of tests because the tested code was refactored?
Most of these issues are the result of misconceptions most developers have about unit testing. In this article I want to highlight the top 3 ones I experienced in the last years:
Misconception No. 1: Unit testing is a method by which individual units of source code are tested
Correct: Unit test is a test running in isolation (from other unit tests)
According to Wikipedia unit testing “is a method by which individual units of source code … are tested to determine if they are fit for use”. This statement is plain wrong! We should not test code but rather test functionality. Behavior-driven development was developed by Dan North as a response to this issue. Kent Beck the inventor/re-discoverer of TDD himself never bound unit-test to a unit of code, but to a unit of functionality. His definition of a unit test is simply a test run in isolation from other unit tests. This means that we can execute any tests in any order or even in parallel without the tests interfering with each other, which is a prerequisite for fast feedback.
Instead of writing a test-class per class we are testing and mocking all the collaborators we should instantiate the whole object graph and use the public API (the functions called via the user-interface or service calls) to verify it works correctly, just mocking the UI and the database (and other things that break test-isolation).
The result are much fewer, much more valuable tests that usually don’t break then refactoring, which brings us to the next point:
Misconception No. 2: The main purpose of unit tests is to verify correctness of code
Correct: The main purpose of unit tests is to enable refactoring
Only a small percentage of bugs are found by tests written by software developers (around 20%). This alone is far too low to weight against the effort necessary to develop and maintain tests.
The real benefit is that once we have Kent-beck-style unit-tests in place we can refactor the code without the fear of breaking something. Since we test against the public API and not against implementation details, we no longer have an excuse for not cleaning up and simplifying the code.
The results of higher code quality are far fewer bugs, better extensibility and faster deliveries.
Misconception No. 3: Unit-testing means that we need to test every class
Correct: During the refactoring phase in TDD you are not allowed to write tests
If you are simply refactoring your code you are not adding new behavior, you simply rearrange your code. This means that if you test against a public API (which didn’t change) all the refactored code should be tested as well. There is no need to add tests against extracted classes since they are simple an implementation detail. If you still add tests against these classes they become obsolete as soon as you do any further refactoring.
You still can have tests for internal classes as a help during the refactoring but once you are ready you can delete them to reduce the maintenance efforts.