TDD: Where did it all go wrong?

Ian Cooper reminds what was Kent's original proposition on TDD, what misunderstandings occurred along the way and suggests a better approach to TDD, one that supports development rather impeding it. 63min video & slides. presentation

_For six months, the small dev team I led tried to build a replacement suite of integration tests. This is one reference I collected in 2014 to inform our effort._

# Kent Beck shouldn't have called them "Unit tests" * 'cos they got confused with some industry traditions. * A Unit is just a test that can run in isolation from other tests. * adding a method to a class isn't the trigger for writing a test * new stories are the trigger for writing new tests * unit tests should be more like small acceptance tests

# Red Green Refactor * write tests that specify acceptance criteria for the story _(Eric adds: this is where we learn the business problem)_ * write sloppy code that works to make the tests pass _(Eric adds: this is where we learn one way to solve the problem and usually discover deeper details about the business problem)_ * you gotta refactor _(Eric adds: this is where we gain confidence in the tests to let us refactor the code, and where we prove the tests verify business requirements and not software architecture)_ * don't add any more tests. * introduce design patterns if they help clean up the code * add as many private objects as needed to clean up the code

# Test Data Problems * tests do a lot more setup of data than normal code * that makes tests noisy and leads to long setup methods * test data has low signal to noise ratio * DRY issue: changes to the domain can ripple across the test codebase forcing change to lots of tests * Either Factories proliferate, obscuring their differences in test data * Or each Factory becomes bloated * Factory methods become shared fixture which causes changes to ripple out

# Test Data Solution: Create Test Data Builders

public class InvoiceTestDataBuilder { Recipient recipient = new RecipientTestDataBuilder() .build(); InvoiceLines lines = new InvoiceLines( new InvoiceLineTestDataBuilder().build() ); PoundsShillingsPence discount = PoundsShillingsPence.ZERO; public InvoiceBuilder WithRecipient( Recipient recipient ) { this.recipient = recipient; return this; } public InvoiceBuilder WithInvoiceLines( InvoiceLines lines ) { this.lines = lines; return this; } public InvoiceBuilder WithDiscount( PoundsShillingsPence discount ) { this.discount = discount; return this; } public Invoice Build() { return new Invoice(recipient, lines, discount); } }

object chaining allows: * sensible defaults in the constructor * allows test cases to expose the data affecting the test through fluent interface -- the withFoo()