TDD + Test Pyramids
TDD = Test > Code (make it work) > Refactor (make it simple) Test Pyramid = lots of small/unit tests, few integration, fewer e2e
What tends to happen when we couple them:
Unit Test > Code > Tests are tightly bound to the implementation, it's hard to refactor, it works, we move on
The trend has shifted in the past few years towards lifting tests up (ex: https://kentcdodds.com/blog/the-testing-trophy-and-testing-classifications (opens in a new tab)) Thanks to libraries like testing-library https://testing-library.com/ (opens in a new tab). Backend devs are now moving in this direction as well https://www.youtube.com/watch?v=QL0HBeIAny0 (opens in a new tab)
Write tests. Not too many. Mostly integration. (opens in a new tab)
Pain points with TDD+Pyramid can be:
- Unit tests are tightly bound to the implementation => code is hard to change
- Unit tests don't reflect the user's behavior
- False positive: Changing implementation details might break the test, not the app
- Early abstractions (needed to express the test)
- Low value unit tests (implementation details)
- Lots of unit tests = lots of mocks => code is hard to change
- 1 test per function/method/file/component approaches leads to unnecessary tests
- Trivial code gets tested
- "this should call this with these parameters n times" (nothing real gets tested)
- Slower development cycles
- "Refactor" gets misunderstood as "hide behind an abstraction", adds more complexity
- We feel good for creating tests, our feedback loop is flawed
- It's hard to be the guy who deletes tests
Those are mostly Test Pyramid issues, TDD acts like a catalyst that speeds up the process.
When tests are placed high enough to play a users' role, TDD doesn't have these issues.
It's also great at:
- Rubber Duck Programming, test plays the duck role
- Helps brute force your way through complexity (I don't know what I'm doing, but I can keep throwing code at tests until it passes)
- Actually writing tests ("test last" tends to skip writing tests and move on, just like TDD skips refactoring)