TDD Where Did It All Go Wrong - egnomerator/misc GitHub Wiki
TDD, where did it all go wrong? (video here)
- The reason to test is a new behavior, not a method or a class
- Write dirty code to get green, then refactor
- No new tests for refactored internals and privates (applies to both methods, classes)
- Both develop and accept against tests written on a port
- Add integration tests for coverage of ports to adaptors
- Don't mock internals, privates, or adapters
- The Problem
- TDD Rebooted
- Red-Green-Refactor
- Clean Code When?
- Refactoring to Patterns
- Ports and Adapters
- Gears
- ATDD
- Behavior Driven Development
- Mocks
- Q&A
- We often write more test code than production code
- Refactoring could commonly break tests--especially tests that use Mocks; have to re-write the Mocks
- the tests were an obstacle and increased effort required
- Sometimes difficult to understand purpose of a test that you haven't seen in awhile
- Large test base can increase cost of the check-in dance
- ref: "Test-Driven Development" by Kent Beck
- TDD as originally intended was great
- perhaps where we've gone wrong is adding later practices on top of it
- the kent beck book address a lot of the issues brought up above
TDD
- Avoid testing implementation details, test behaviors
- a trigger for a new test is a new requirement--not a new method or class
- test the public API--the stable requirements
- how you implement it may change
- the SUT is not a class, it is the "exports" from a module--its facade
- refactoring is key to separating implementation details you don't need to test from behavior you do need to test
- Red
- Green: speed trumps design
- Blue
- this is when we produce clean code, remove duplication, sanitize code smells
- this is when you apply patterns
- you do not write any new tests in this phase
- you do not change behavior in this phase
- you improve the code and stay green
- i.e. you improve the code while maintaining the required behavior
- you might extract out a new class in this phase--DON'T write a test for that class!
- in the analogy, refactoring is like a high gear--implementation is clear, you just write it
- but if you are refactoring significantly, and you feel you need tests to help you, write them
- in the gear analogy, this is shifting down a gear
- when you have finished refactoring, DELETE those tests
- because those tests were about implementation details
You want a testing pyramid, not a testing ice-cream cone
- cone: a mass of manual testing, on a lot of Auto-GUI tests, on less integration tests, on even less unit tests
- this is not scalable or elastic to change
- pyramid: a large base of unit tests, under key integration tests, under minimal UI tests
Hexagonal Architecture
- overall
- POCOs in the middle, surrounded by an adaptor layer, ports in between
- ports layer
- a port is a door
- a port is a use-case boundary--behavior driven--so test this
- adaptor layer around the center made of POCOs
- knows how to talk to the outside world--dbs, networks, etc.
- e.g. web frameworks like web forms
- don't test these directly
- integration tests to cover communication between adaptors and ports
- customers don't care about these front-facing tests
- but they may have been what taught us to focus on behavior in unit tests
Just a clarification:
BDD is a separate discipline from the TDD concept of testing behavior
Don't use mocks to "confirm" implementation details
- this is a common thing senior devs do to "enforce" correct behavior on jr devs
- don't do this
A note on IOC containers--try to avoid them or use them sparingly