Coplien gives lots of very good advice, but from the base assumption that the reader is using classes as the atomic unit of composition.
The trouble with that assumption is twofold:
- Classes are not the best atomic units of composition. In many non-Java languages, functions very often fill that role, and well-contained functions (e.g. pure functions) are very easy to unit test in isolation from the program context. Indeed, the definition of unit tests is to test units of code in isolation from the rest of the program (the context).
- If you can’t meaningfully test a unit of code in isolation, you need a functional test (what Jim calls a system test, from the user/business value perspective). Most of the advice in his 20+ page paper on the topic is bent toward getting people to recognize when a system test is required instead of a unit test. Using classes as the atomic unit of composition, that happens much more frequently.
I disagree with him on the assumption that it’s a good idea to structure programs in a way that there are more system tests than unit tests. In my experience, that’s a code smell indicating that there’s too much tight coupling in the code.
That unit tests are hard to write is the least of your problems when there is too much tight coupling. You need to fix it for the sake of agility and maintainability. A good TDD process forces looser coupling and protects you from getting into that situation to begin with, assuming you have the wisdom to recognize when mocking is a code smell.
I also strongly disagree that it’s a good idea to turn unit tests into assertions. Assertions cause runtime failures when the code making the assertion is exercised at runtime.
Unit tests are best used as immediate developer feedback on every program change, and should not require the developer to wait for the whole program to compile and boot up before the developer can exercise the pass/fail condition. Runtime assertions can’t adequately substitute for that role.
We have a fundamentally different model of the role that unit tests should play. You can see further evidence of that when he states that processing power means that system tests should be fast enough to replace unit tests on modern systems.
I’ve never seen entire functional test suites run in under 3 seconds. But that’s what’s required to keep up the unit testing TDD flow.
Due to the much lower cost of unit tests vs functional tests, much better coverage becomes practical, and better coverage correlates with fewer bugs in the system.
Jim rightly warns against tests which hold little business value, but if the only way NASA could test a heat shield is to launch a rocket into space and see if it survives reentry, they would waste a tremendous amount of business value.
The business value of most unit tests is to decrease the cost of coverage and system maintenance in many ways, not the least of which is to help shape better system design in the first place.
Coplien has made many great contributions to the software development field, but his writing on unit testing demonstrates a fundamentally different philosophy of software design for which his advice makes sense, but in the context of mixed paradigm code where functional programming is common and pure functions widely used, his conclusions don’t hold.
The best way to learn the value of TDD and unit tests is to use them daily long enough for the lessons to sink in. It’s a process that can take a couple of years — a bit like any complex skill, like playing a musical instrument or learning your first programming language.
Coplien has lots of experience with testing, but to learn TDD properly, you first have to unlearn a lot of ideas that get in the way of doing it properly, and those ideas often have less to do with testing than they do with software design. It requires a reinvention of your definition of atomic building blocks for application design.
For some people, that ship has sailed, and they’ve already dismissed or minimized the role of unit tests, and they’re unlikely to change their minds.
Many of those people are very smart and very accomplished. But their way of thinking about software is fundamentally incompatible with the way I think about software.
I’m not willing to sacrifice the benefits I get from my way of thinking, and it’s likely the same for them, as well.