TDD + Test Pyramid

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) Thanks to libraries like testing-library https://testing-library.com/. Backend devs are now moving in this direction as well https://www.youtube.com/watch?v=QL0HBeIAny0

Write tests. Not too many. Mostly integration.

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)