DevTernity Conference
AI-assisted summary of 🚀 TDD, Where Did It All Go Wrong (Ian Cooper) — original source. Check the original for full context.
Source published:
Key ideas
- Widespread industry misunderstandings of Kent Beck's original TDD rules led to test suites outgrowing production code by ratios of 2:1 or 3:1, slowing delivery velocity down by 20 to 50 percent. (6:04)
- The fundamental anti-pattern in modern TDD is treating classes and individual methods as the unit of test isolation, which results in brittle, over-mocked tests that break during internal refactorings. (26:02)
- True unit isolation in TDD refers to the execution isolation of tests from one another, with tests triggering exclusively from requirements against public module facades or ports. (35:01)
- In the Red-Green-Refactor cycle, developers should write crude transaction scripts in the Green phase to achieve speed, reserving architectural pattern design strictly for the Refactoring phase without adding new unit tests. (39:57)
Detailed notes
## Background Published on YouTube by the DevTernity Conference channel on December 20, 2017, this presentation features veteran software developer Ian Cooper. According to the page description, the talk examines how widespread misunderstandings of Kent Beck's original 2002 TDD principles—particularly around what constitutes a 'unit' and a public interface—have produced brittle, bloated test suites that impede refactoring rather than supporting it.
## TL;DR Ian Cooper argues that the software industry has largely distorted Test-Driven Development into an anti-pattern by testing individual classes and methods rather than observable system behaviors. By treating classes as isolation units, developers end up over-specifying mock interactions, causing test suites to break whenever internal implementations are refactored. Cooper revisits Kent Beck's original rules, demonstrating that tests should target stable module facades or ports in a hexagonal architecture, with isolation applying to test execution rather than class boundaries. He emphasizes separating behavioral exploration from structural cleanup by acting like a rapid 'duct-tape programmer' during the green phase, and reserving refactoring for architectural patterns without adding new tests.
## Atmosphere & style The presentation carries the candid, reflective tone of a battle-tested software architect delivering hard-earned corrections to an industry dogmatized by bad habits. Cooper speaks with measured humor and pragmatism, openly acknowledging the collective burnout caused by slow builds, morning blame games, and brittle mocks. Watching the talk feels like an overdue reality check: a calm deconstruction of industry sacred cows aimed at relieving developers of excessive testing friction and restoring joy and agility to everyday coding.
## Bulletin - [06:04](https://www.youtube.com/watch?v=EZ05e7EMOLM&t=364s) Widespread industry misunderstandings of Kent Beck's original TDD rules led to test suites outgrowing production code by ratios of 2:1 or 3:1, slowing delivery velocity down by 20 to 50 percent. - [26:02](https://www.youtube.com/watch?v=EZ05e7EMOLM&t=1562s) The fundamental anti-pattern in modern TDD is treating classes and individual methods as the unit of test isolation, which results in brittle, over-mocked tests that break during internal refactorings. - [35:01](https://www.youtube.com/watch?v=EZ05e7EMOLM&t=2101s) True unit isolation in TDD refers to the execution isolation of tests from one another, with tests triggering exclusively from requirements against public module facades or ports. - [39:57](https://www.youtube.com/watch?v=EZ05e7EMOLM&t=2397s) In the Red-Green-Refactor cycle, developers should write crude transaction scripts in the Green phase to achieve speed, reserving architectural pattern design strictly for the Refactoring phase without adding new unit tests.
## Key takeaways - [06:04](https://www.youtube.com/watch?v=EZ05e7EMOLM&t=364s) Early TDD adoption created sprawling test suites that often ballooned to three times the volume of production code, creating severe maintenance drag. - [08:11](https://www.youtube.com/watch?v=EZ05e7EMOLM&t=491s) Tests heavily reliant on mocks assert implementation details rather than observable behavior, breaking whenever internal structures are refactored. - [10:36](https://www.youtube.com/watch?v=EZ05e7EMOLM&t=636s) The anti-testing backlash, including Fred George's Programmer Anarchy and Silicon Valley's spike-and-stabilize approach, emerged as a reaction against testing friction. - [14:38](https://www.youtube.com/watch?v=EZ05e7EMOLM&t=878s) Business-facing automated acceptance tools like FitNesse, Cucumber, and SpecFlow largely failed because stakeholders rarely read the tables and suites stayed red for weeks. - [19:01](https://www.youtube.com/watch?v=EZ05e7EMOLM&t=1141s) DHH's critique 'TDD is dead' and Kent Beck's response highlighted that developer frustration stemmed from distorted secondary interpretations rather than Beck's original methodology. - [24:16](https://www.youtube.com/watch?v=EZ05e7EMOLM&t=1456s) The only valid trigger to write a developer test in authentic TDD is implementing a requirement for observable behavior, never the addition of a new class or method. - [26:02](https://www.youtube.com/watch?v=EZ05e7EMOLM&t=1562s) The 'System Under Test' should represent a classical module facade or architectural port, allowing internal classes to be refactored or extracted freely without altering tests. - [31:58](https://www.youtube.com/watch?v=EZ05e7EMOLM&t=1918s) Dan North developed Behavior-Driven Development (BDD) to correct the widespread mistake of testing methods on classes instead of observable system behaviors. - [35:01](https://www.youtube.com/watch?v=EZ05e7EMOLM&t=2101s) Kent Beck defined unit isolation as test independence—ensuring tests can run in any order without interference—not isolating classes from their collaborators using mocks. - [39:57](https://www.youtube.com/watch?v=EZ05e7EMOLM&t=2397s) During the Green phase of Red-Green-Refactor, speed trumps design; developers should act like duct-tape programmers to prove behavior before structuring code. - [43:23](https://www.youtube.com/watch?v=EZ05e7EMOLM&t=2603s) Refactoring is the sole phase intended for introducing design patterns and eliminating code smells, and developers must never write new tests during refactoring. - [45:10](https://www.youtube.com/watch?v=EZ05e7EMOLM&t=2710s) Extracting internal classes during refactoring must not trigger new unit tests; exposing implementation details to tests via public modifiers defeats encapsulation. - [53:07](https://www.youtube.com/watch?v=EZ05e7EMOLM&t=3187s) Inverted testing pyramids relying heavily on Selenium and end-to-end browser automation create extreme maintenance burdens and morning blame cultures. - [55:18](https://www.youtube.com/watch?v=EZ05e7EMOLM&t=3318s) In Hexagonal Architecture, developer tests should exercise port interfaces against domain POCOs/POJOs, while integration tests verify outbound adapter configurations against external systems. - [57:59](https://www.youtube.com/watch?v=EZ05e7EMOLM&t=3479s) Fine-grained scratchpad unit tests written while exploring complex algorithms in lower gears should be deleted once integrated behind the public port API. - [01:01:08](https://www.youtube.com/watch?v=EZ05e7EMOLM&t=3668s) Mocks should be strictly reserved for unmanageable process boundaries and external integration adapters, not used between internal domain classes.
## Detailed walkthrough ### [00:00](https://www.youtube.com/watch?v=EZ05e7EMOLM&t=0s) Introduction and the Speaker's Perspective
Ian Cooper opens the session by taking an informal show of hands from the audience to gauge their experience with the talk itself, test-driven development (TDD), test-first versus test-after development, and the use of mocking techniques. A large portion of the room indicates they use TDD and mocking. Directing attention to his introductory slide, Cooper emphasizes that while biographical details are mostly mundane, one key principle matters: conference speakers are not inherently cleverer or smarter than the engineers sitting in the audience. He strongly encourages developers to take the stage at conferences and give back to the software engineering community. He explains that his own journey into speaking was somewhat accidental; when.NET was newly released, there were very few resident experts, meaning anyone wanting a local meetup or user group had to step up and build it themselves.
Clarifying his current role at Huddle, an enterprise SaaS company, Cooper makes it clear that he is not there as a consultant trying to sell a product or service. His talk reflects purely hard-won personal experience. His primary goal is to address the widespread and growing resistance to TDD, rethink how practitioners interpret core tenets like "Red, Green, Refactor," pinpoint where industry habits diverged from original teachings, and explore architectural concepts such as Ports and Adapters, the "gears" of testing, and behavior-driven approaches.
- Cooper has been practicing TDD since approximately 2004, having been part of an early-adopter.NET community in London that shared close ties and cross-fertilization with a local Extreme Programming (XP) user group. - The talk was initially conceived around 2013, after Cooper had accumulated roughly a decade of running and maintaining test suites. - Long tenures at companies allow an architect to witness their own architectural decisions and mistakes circle back, contrasting with short two-year stints where developers ship code, depart pridefully, and leave the long-term maintenance burdens to their successors.
[04:24](https://www.youtube.com/watch?v=EZ05e7EMOLM&t=264s) Early Pushback and the Misunderstanding of Experience
Reflecting on the years 2005 and 2006, Cooper recalls having completely "drunk the Kool-Aid" on TDD. When peers resisted the practice, his initial impulse was dismissive, assuming skeptics simply failed to grasp its brilliance. However, the resistance was not coming from incompetent developers; it was voiced by deeply respected, highly proficient engineers who wrote exceptional code yet openly questioned whether TDD was the proper way to build software. Rather than listening attentively, Cooper found himself repeating core TDD dogma as if speaking to a child, hoping that reiterating the steps would trigger an epiphany.
The divide became particularly glaring during a conversation with one of these admired senior developers. The engineer remarked that he finally understood why TDD was useful, stating that it offered valuable guardrails for junior developers learning to structure clean code. However, the developer followed this with a revealing question: “Surely you don't really expect the experienced developers on the team to be doing TDD, right?” Cooper was left bewildered, unsure of why an approach he considered universally transformative was viewed by seasoned peers as an elementary crutch.
[06:04](https://www.youtube.com/watch?v=EZ05e7EMOLM&t=364s) Bloated Test Suites and the Velocity Dilemma
Cooper recounts delivering a backend service that ran in production for an entire year without a single reported defect. While the team credited TDD and their comprehensive test coverage for this achievement, Cooper could not shake an uneasy metric: the test code was roughly three times the volume of the production code. Seeking clarity on Twitter, the wider developer community confirmed that having two to three times as much test code as production code was standard practice.
This lopsided code ratio created tangible organizational friction. Project managers regularly pleaded to skip test writing in order to ship features to market faster, willing to trade a handful of production bugs for a shorter timeline. While developers defended their workflow by arguing that tests were an inseparable part of writing software itself, the operational reality was undeniable: writing this style of test suite slowed delivery down by 20 to 50 percent compared to development without tests.
```mermaid flowchart TD accTitle: The TDD Friction Cycle accDescr: Diagram showing how test bloat leads to slow delivery, refactoring friction, and management pushback.
A["Extensive Mock-Heavy Tests<br/>(2x-3x Production Code)"] --> B["Reduced Delivery Speed<br/>(20%-50% Slower)"] B --> C["Management Demands<br/>to Drop Testing"] A --> D["Tests Coupled to<br/>Implementation Details"] D --> E["Refactoring Breaks Mocks"] E --> F["Test Suite Becomes an Obstacle<br/>to Healthy Architecture"] ```
[08:11](https://www.youtube.com/watch?v=EZ05e7EMOLM&t=491s) The Refactoring Paradox and the Breakdown of Mocks
The most severe flaw surfaced when engineers attempted to perform routine refactorings. When new feature requirements made the existing internal architecture awkward, developers sought to clean up the code organization without altering external system behaviors. Instead of providing a safety net, large swathes of tests broke—particularly those relying heavily on mocks that tightly specified exact call orders, invocation counts, and precise parameters. Because these tests asserted how internal components interacted rather than what the system produced, any internal structural change invalidated the assertions.
This behavior directly contradicted the foundational literature of agile software engineering. Cooper highlights that Martin Fowler and Kent Beck defined refactoring as altering internal software structure without changing its observable behavior. Yet developers found their tests constantly failing during purely structural revisions. Instead of preventing the codebase from decaying into a "big ball of mud," the test suites became a rigid, expensive obstacle to routine maintenance. As Cooper sharply puts it:
> “When we wanted to change our code, actually the tests were an obstacle to that change, because they increased the amount of effort we had to put into making any changes; we had to fix all these tests that were breaking when we changed our implementation details.”
By 2009 and 2010, the broader software development community began reacting against this friction, with emerging methodologies explicitly advising teams to avoid TDD because of its measurable drag on development speed.
[10:36](https://www.youtube.com/watch?v=EZ05e7EMOLM&t=636s) The Anti-Testing Backlash: From "Programmer Anarchy" to the Duct-Tape Coder
Ian Cooper describes how alternative software movements began pushing aggressively against automated testing and TDD. He points to Fred George and his concept of "Programmer Anarchy": for those familiar with *This Is Spinal Tap*, Fred George proposed that if Extreme Programming (XP) was good, teams should turn the XP dial up to 11 and embrace extreme extreme programming. Under this model, companies would eliminate nearly every non-developer role—firing QA and product managers entirely—and leave developers talking straight to customers. George advocated building tiny microservices of around a hundred lines of code (the original definition of the term) without writing any automated tests at all. The philosophy held that because each service was so small, developers were bound to get it right; if changes were needed later, they could simply throw the service away and rewrite it from scratch.
```mermaid flowchart TD accTitle: Divergent paths in software development speed accDescr: Contrasting the slow feedback cycle of overloaded test suites against the rapid production delivery of lean and duct-tape approaches.
subgraph Disciplined["Overloaded TDD Approach"] D1["Write Extensive Tests & Mocks"] D2["Slow Run Times & Brittle Suites"] D3["Delayed Production Delivery"] D1 --> D2 --> D3 end
subgraph Maverick["Lean / Duct-Tape Approach"] M1["Write Direct Implementation"] M2["Skip Tests Entirely"] M3["Ship Immediately to Production"] M1 --> M2 --> M3 end ```
Similar skepticism arose through practices like "spike and stabilize" and lean software development, which became the standard approach in Silicon Valley startups. These groups argued that early on, teams might be building the wrong product entirely, meaning speed of feedback and customer interaction mattered far more than comprehensive suites:
> “TDD is the kind of practice you do later on when you're scaling; upfront tests, it slows you down too much.”
This cultural friction was embodied by the classic "duct-tape programmer"—the archetype popularized by Joel Spolsky. Every company has an annoying "Bob" whom the business and product teams adore because he provides instant solutions. Whenever a critical requirement drops, management hands it straight to Bob, who pushes it out the door immediately. While the rest of the engineering team complains that Bob writes maintainable garbage and leaves behind code that everyone else must spend months rewriting, management praises Bob for being exceptionally receptive and productive. While careful engineers were still grinding through their sprawling test suites, the duct-tape coder had already shipped functioning software to production and captured all the organizational credibility.
- Fred George’s "Programmer Anarchy" proposed running without QA or product managers, relying on ~100-line microservices with zero tests that could be discarded and rewritten on demand. - Lean startup philosophy warned against building large test suites before product-market fit was established. - The "Bob" archetype consistently won favor with management by delivering quick code, while principled teams got bogged down maintaining test suites.
---
[13:21](https://www.youtube.com/watch?v=EZ05e7EMOLM&t=801s) Brittle Tests and the Mockery of Mocking
Cooper recounts the immense frustration developers faced when returning to broken tests after refactoring implementation details. Teams frequently encountered red tests without having any clear idea what the tests were supposed to be verifying, or why a failure had occurred:
> “You go back and find the test is red and you're looking at it and you're going, I have no idea what this test is for, and I have no idea whether the fact that it's red is good or bad at this point in time.”
Developers would inspect tests that seemingly added two numbers together to produce an inexplicable result, completely unable to determine how it ever passed in the first place.
The issue became catastrophic when test suites were heavily saturated with mocks. Teams would examine a failing test only to discover that nothing substantive was actually under test except the mocks themselves. An engineer had constructed elaborate scaffolding specifying exactly how internal collaborator objects interacted, without exercising any genuine domain or business logic. When refactoring rendered these mock interactions obsolete, the tests failed purely because the internal implementation changed. Engineers eventually threw up their hands in exhaustion, concluding that the only sensible option was to delete the incomprehensible tests altogether.
---
[14:38](https://www.youtube.com/watch?v=EZ05e7EMOLM&t=878s) The FitNesse Nightmare: Customer Tests Nobody Read
Cooper admits, to his own embarrassment, that one of his past teams fully adopted FitNesse, spending enormous effort generating HTML fixture tables in natural English to specify business requirements. The dream sold by tools like FitNesse, Cucumber, and SpecFlow was that customers and stakeholders would browse high-level HTML pages, see requirements documented in plain language, push a button, watch the tests run green, and gain total confidence that the software worked as specified.
In practice, developers despised writing these tests. They began iterations with a suite of red customer tests that stayed red for almost the entire development cycle, only turning green near the very end once the implementation was complete:
> “That meant essentially your FitNesse test suite was quite often, well correctly, red much of the iteration, so you could never really be sure if the tests were red is that because it's yet to be implemented or is that because essentially we broken it.”
Teams grew accustomed to seeing red test runs and assumed failures were simply uncompleted work, letting genuine regressions slip past undetected.
Meanwhile, business customers showed zero interest in looking at the HTML tables. Whenever the engineers tried to corral customers to look at the screen, the response was always an evasive brush-off: "Put something in my calendar guys, and we'll talk about this at some point in the future." The customer-facing artifacts went completely ignored by the exact people for whom they had been designed.
---
[16:56](https://www.youtube.com/watch?v=EZ05e7EMOLM&t=1016s) Process Friction and Apportioning Blame
The check-in process degraded into a slow, frustrating ritual. Before checking code into the repository, developers were required to pass not only their unit test suites but also the sluggish FitNesse tests:
| Stage | Developer Experience | Consequence | |---|---|---| | **Test Execution** | Check-in builds took so long developers left their desks to make coffee | Broken mental flow; required 20 minutes to get back into "the groove" | | **Test Maintenance** | Developers ignored persistently red test results to keep shipping code | Regressions accumulated until delivery stalled for fire-fighting | | **Team Accountability** | Teams appointed a rotating role to track down who broke the build | Culture devolved into blame-shifting and plausible deniability |
Developers began evading the suites, adopting a strategy of plausible deniability by claiming a break was caused by someone else's check-in rather than their own. The team actually institutionalized this failure by designating a person whose explicit responsibility was to figure out who turned the tests red and assign blame to force them to fix it:
> “The fact that we actually had a step in our process which essentially was somebody decides who's to blame for the tests being red today and then they go to have to go and fix them implies that no one really cared about those tests because they'd reached that point of being red the whole time.”
Cooper highlights a general lesson: if a technical practice is truly effective, a team will naturally rally behind it over time; if developers are continuously pushing back and complaining that they see zero value in the tests they are forced to write, the process itself is deeply flawed and needs to be rethought.
---
[19:01](https://www.youtube.com/watch?v=EZ05e7EMOLM&t=1141s) DHH’s "TDD is Dead" and Kent Beck’s Reaction
About a year after Cooper first delivered this talk, David Heinemeier Hansson (DHH), the creator of Ruby on Rails, published his famous 2014 blog post titled *"TDD is dead. Long live testing."* DHH’s core grievance stemmed from being repeatedly told by the TDD community that Rails was poorly architected because it failed to comply with their dogmatic rules for test-friendly frameworks.
Under the Active Record pattern used in Rails, persistence operations live directly on domain entities (such as calling a save method directly on a `User` model). Test purists criticized this design because it prevented a clean separation between the domain model and persistence mechanisms, preventing developers from testing entities without touching the database. DHH pointed out that he had originally enjoyed writing unit tests that talked straight to the database, but he had grown weary of being continuously scolded by purists insisting he was doing TDD incorrectly. Frustrated by artificial design contortions driven purely by test dogma, DHH declared that he was abandoning TDD altogether while retaining more practical testing methods.
His post triggered massive waves throughout the software industry, eventually prompting Kent Beck to write a sarcastic response titled *"RIP TDD."* Beck's dry humor, Cooper notes, may not have translated well to the broader audience, but it underscored the escalating civil war between dogmatic TDD practitioners and developers who felt TDD had lost its way.
[20:24](https://www.youtube.com/watch?v=EZ05e7EMOLM&t=1224s) Where Did It All Go Wrong? The George Best Analogy
Ian Cooper recalls how the debate around test-driven development escalated after David Heinemeier Hansson (DHH) wrote his provocative critiques of TDD. Following DHH's initial blast, Martin Fowler stepped in to mediate an online video series between DHH and Kent Beck. DHH had actually seen the original version of Cooper's talk presented at NDC and reached out to note that they were thinking along very similar lines; ultimately, Beck, DHH, and Fowler arrived at conclusions strikingly aligned with the points Cooper makes. Cooper notes that for anyone curious about the broader debate, that mediated dialogue remains well worth watching. But the core question lingering over the industry is simple: where did it all go wrong for TDD, and can developers correct their course?
To capture the spirit of that question, Cooper recounts a classic piece of British folklore about the legendary 1970s Northern Irish footballer George Best. Best was notoriously flamboyant, living a wild playboy lifestyle. The story goes that a hotel bellboy was wheeling a cart laden with caviar and champagne into Best’s luxury suite. Upon opening the door, the bellboy found Best lounging across the bed completely naked alongside two supermodels. The bellboy stopped in his tracks, looked down at Best amidst all this extravagant indulgence, and asked in earnest bewilderment: “George, where did it all go wrong?” Cooper tells the audience to take the title of his talk in exactly that light: he is not an enemy of TDD, but rather an ardent fan who wants to figure out how such a brilliant, luxurious idea managed to stray so far off track.
[22:01](https://www.youtube.com/watch?v=EZ05e7EMOLM&t=1321s) Rediscovering Kent Beck's Original Wisdom
Cooper’s central premise is that TDD itself already holds the solutions to every pathology modern developers encounter while using it. The tragedy is that teams abandoned Kent Beck's original vision, published in his seminal 2002 book *Test Driven Development: By Example*. Over the years, practitioners layered dogmatic patterns and habits onto the practice that actually contradicted Beck’s guidance. When Cooper asks the room who practices TDD, a large show of hands goes up; when he asks who has actually read Beck’s book, far fewer hands remain raised. Most developers admit they picked up TDD second-hand from colleagues or through later books that distorted the underlying principles.
Cooper shares his personal journey: when he began hitting serious roadblocks and friction with TDD in his own career, he pulled Beck's book off the shelf and re-read it from cover to cover. He realized that Beck had accumulated decades of practical experience before writing the book, and almost every pitfall—including issues around mocking, which many developers mistakenly believe arose much later in the software lifecycle—had already been anticipated, analyzed, and solved. As Cooper emphasizes, reading the book with years of battle scars made him finally comprehend what Beck had meant all along:
> “The original approach to TDD as described by Kent Beck works; later patterns and practices we have loaded onto TDD made in many cases a mistake and have led us away from what was originally intended in our mistake.”
[24:16](https://www.youtube.com/watch?v=EZ05e7EMOLM&t=1456s) Triggering Tests from Requirements, Not Methods
The single most foundational mistake made in modern development shops is testing implementation details instead of testing behaviors. In the standard, distorted TDD cycle practiced today, a developer decides they need to write a method on a class, so they write a unit test to govern whether that specific method succeeds or fails. The trigger for generating a test has degenerated into merely adding a method to a class. Cooper insists that this mechanical habit is completely alien to the ethos of TDD.
The only valid trigger for authoring a test in genuine TDD is the arrival of a requirement to implement. Tests exist to verify user-visible capabilities—such as adding an amount to a customer’s bank account or uploading a new file to a collection—not to verify that an arbitrary helper function named `foo` multiplies two numbers together. A developer should write tests at the level of the requirement itself, leaving the software free to assemble whatever internal classes and helper functions it needs to fulfill that promise.
```mermaid flowchart TD accTitle: TDD Test Trigger Comparison accDescr: Contrasting modern faulty TDD triggered by class methods with authentic TDD triggered by requirements.
subgraph Modern["Modern Distorted TDD Cycle"] M1["Add method to class"] --> M2["Write isolated unit test for method"] M2 --> M3["Brittle tests coupled to implementation"] end
subgraph Authentic["Original Kent Beck TDD Cycle"] A1["New requirement / behavior needed"] --> A2["Write test against module API / facade"] A2 --> A3["Refactor freely beneath stable contract"] end ```
> “The trigger in TDD for creating a new test is that you have a requirement you want to implement; it is nothing more, it is nothing less.”
[26:02](https://www.youtube.com/watch?v=EZ05e7EMOLM&t=1562s) The System Under Test: APIs, Modules, and the Pitfalls of Mocking
When Cooper tells developers to test the API of their software, he clarifies that he does not exclusively mean a REST, HTTP, JSON, or GraphQL endpoint. Rather, he refers to the classical computer science definition of an application programming interface: the publicly exposed exports of a module that hide internal implementation details behind an encapsulating facade. The contract exposed to consumers represents the stable, unchanging requirement of the software. How that requirement is actually fulfilled internally remains volatile and subject to change as developers discover cleaner designs. Testing the stable facade preserves test longevity, whereas testing internal classes guarantees friction.
```mermaid flowchart LR accTitle: Module Encapsulation and Test Target accDescr: Testing interacts solely with the public module facade while implementation classes remain internal and unmocked.
subgraph TestHarness["Test Suite"] T["TDD Test"] end
subgraph ModuleBoundary["Module Boundary"] F["Public Facade / Exported API"] subgraph Internal["Hidden Implementation Details"] C1["Domain Class A"] C2["Domain Class B"] C3["Helper Service"] end end
T -->|"calls public contract"| F F --> C1 C1 --> C2 C2 --> C3 ```
In agile workflows, requirements typically arrive as stories or use cases, often formulated in Given/When/Then acceptance criteria (e.g., given an account holds £100, when another £100 is deposited, then the balance is £200). The true system under test (SUT) is that behavior exposed via the module facade, not an individual class. Because languages like C# and Java force developers to write code inside classes, developers conflated the testing "unit" with a single language class. That conflation triggered a disastrous chain reaction: developers believed every single class had to be isolated from its collaborating classes using heavy mocking frameworks. Mocking internal dependencies produced over-specified, brittle tests that mirror internal plumbing and make refactoring impossible.
Cooper emphasizes that refactoring is the essential mechanism in TDD that allows developers to spin off internal helper classes without needing to write dedicated unit tests for them. He acknowledges an escape hatch: if an internal algorithm is exceptionally intricate and hard to get right, a developer can write exploratory tests directly against it to aid development—provided they delete those tests once the implementation is integrated under the public API test suite. This foundational realization that tests should describe behavior rather than implementation was also the precise spark that led Dan North to introduce Behavior Driven Development (BDD).
- **The Module as a Black Box:** Historically, a unit test meant testing an entire module against its public contract, keeping everything inside the black box hidden from the test suite. - **Over-Specified Tests:** Mocking internal collaborators forces tests to possess intimate knowledge of method signatures and call counts, destroying the benefits of information hiding. - **The Escape Hatch Rule:** Probing tricky internal details with temporary tests is acceptable while debugging or designing, but those tests should not become permanent liabilities in the build pipeline.
> “The definition of unit has become too much fixated on being a class; people talk about isolating that class and isolating the class's dependencies, and it leads into all sorts of problems because then you start mocking heavily, which leads you into totally over-specified tests.”
[31:58](https://www.youtube.com/watch?v=EZ05e7EMOLM&t=1918s) The Misunderstanding: Tests vs. Behaviors
Ian Cooper addresses the widespread confusion that surrounds Test-Driven Development (TDD). He points out that the very word *test* is what throws people off. If that term had been framed as "behavior" from the start—developing your system using behaviors—developers would have embraced the practice properly. Cooper notes that Dan North arrived at this precise insight around 2005 when creating Behavior-Driven Development (BDD). North observed that people were misunderstanding Kent Beck's intent because Beck occasionally used classical testing terminology like "test" and "unit test," even though the phrase Beck typically used throughout his 2002 book was "developer test."
North recognized that developers were driving their suites by looking at an internal class and deciding they needed to test a specific method on that class. Instead, they should have been identifying an observable behavior in the system and writing code to prove that this behavior actually works. Beck himself was always talking about behaviors—behaviors that demonstrate a component works or computes a report correctly. Cooper emphasizes that Beck's own examples in the book describe requirements, not methods: adding amounts in two different currencies and converting the result using a given set of exchange rates, or multiplying a price per share by a number of shares to calculate an amount.
```mermaid flowchart TD accTitle: Testing Methods versus Testing Behaviors accDescr: Comparison illustrating the wrong approach of testing internal methods versus the correct TDD approach of testing observable system behavior.
subgraph Wrong["Wrong Approach: Method-Driven"] A["Class Method"] --> B["Write test for method"] B --> C["Coupled to implementation details"] end
subgraph Right["Kent Beck's Original Intent: Behavior-Driven"] D["System Requirement / Behavior"] --> E["Write test as first API consumer"] E --> F["Resilient, refactor-friendly design"] end ```
The true purpose of TDD has always been designing the public-facing API from the perspective of an outside consumer:
> “The test becomes the first consumer of your code that essentially says what should this look like and how should it work right that is the objective a test driven development”
- TDD tests should represent user requirements and business behaviors rather than mirror class-level methods. - Writing the test first allows the developer to tell a story about how the API should feel and function for external callers.
---
[35:01](https://www.youtube.com/watch?v=EZ05e7EMOLM&t=2101s) The True Unit of Isolation: Test vs. Class
Cooper tackles what he considers the most damaging misconception in the entire TDD ecosystem: what the word "unit" actually means. When Kent Beck talked about unit tests, he had a very specific definition in mind: you should be able to run all your tests together in a suite without the execution of one test impacting any other test.
> “The unit of isolation is the test and a lot of mistakes have been made because people believe the unit of isolation is the class under test it is not”
Because developers wrongly assumed the class was the unit of isolation, an entire anti-pattern of over-mocking arose. Developers convinced themselves that every class had to be tested in complete isolation, requiring mocks for all collaborators and dependencies. Cooper firmly rejects this: the test must be isolated from other tests, not the class isolated from other classes.
In classical software engineering terminology, a "unit test" historically referred to testing an entire module (such as a.NET assembly) in isolation, while integration tests evaluated multiple modules communicating, and system tests evaluated all modules together. When Beck adopted "unit test," he meant testing a module or cohesive behavior, not an isolated class file.
```mermaid flowchart LR accTitle: Unit of Isolation in TDD accDescr: Diagram illustrating that isolation applies between individual tests in a suite, not between an individual class and its collaborators.
subgraph TestSuite["Test Suite Execution"] T1["Test A"] T2["Test B"] T1 <-.->|"Must NOT affect (True Isolation)"| T2 end
subgraph SystemUnderExecution["System Under Test"] C1["Public Entry Class"] --> C2["Collaborator Class"] C2 --> C3["Helper / Entity"] end
T1 --> SystemUnderExecution ```
Cooper explains that while Beck did advise caution around external resources like databases and file systems, it had nothing to do with class isolation: - **Shared fixtures and test pollution:** Touching a database can cause one test to write rows that subsequent tests inadvertently read, creating order-dependent failures unless using rapid teardowns or in-memory replacements. - **Execution speed:** A developer test suite needs to finish in a couple of minutes to provide rapid feedback; slow I/O can be swapped out purely for execution speed. - If there is no shared fixture or pollution problem, Cooper insists it is completely valid for a unit test to interact with a real file system or database.
---
[38:28](https://www.youtube.com/watch?v=EZ05e7EMOLM&t=2308s) Brittle Tests and the Role of Mocks
Focusing on individual methods produces test suites that are brittle, difficult to maintain, and opaque. When tests describe method interactions rather than system behaviors, future developers cannot understand what the code was intended to accomplish. If a behavior-driven test turns red after a code change, you have a clear choice: either the requirement still holds and something broke, or the business requirement has evolved, and the test's behavioral assertion must be updated.
Conversely, testing classes in isolation using mocks forces developers to expose internal implementation details to the test suite:
> “And if I essentially therefore know too much about the implementation details then I've coupled my tests to my implementation and I can't change one without changing the other”
When mocks are heavily configured to expect specific internal method calls, any internal refactoring causes the mocks to fail even if the outward behavior remains correct. The tests become tightly coupled to the code's private structure, preventing changes rather than enabling them.
---
[39:57](https://www.youtube.com/watch?v=EZ05e7EMOLM&t=2397s) Red, Green, Refactor and the "Duct Tape" Programmer
Cooper moves into the mechanics of making TDD work, polling the audience on their familiarity with "Red, Green, Refactor." While many developers run the green and red phases, far fewer routinely perform the refactoring phase.
```mermaid flowchart LR accTitle: Red Green Refactor Flow accDescr: The three stages of TDD showing the quick transition from failure to hacky pass, followed by clean design.
R["1. RED: Write failing test<br/>(Prove it fails correctly)"] --> G["2. GREEN: Hack a solution<br/>(Duct-tape / Transaction script)"] --> B["3. REFACTOR: Clean it up<br/>(Extract classes, patterns)"] --> R ```
Cooper breaks down the discipline required in each phase:
1. **Red:** The test must be shown to fail in the absence of an implementation. Because there are no tests for the tests themselves, failing first is the only way to prevent false-positive assertions that pass unconditionally. 2. **Green:** The sole goal is to make the test pass as rapidly as possible, "committing any sins basically necessary in the process." Cooper urges developers not to design elegant classes or apply architectural patterns at this stage. Instead, write a simple procedural transaction script—line after line of code—or copy and paste from Stack Overflow.
> “You want to be the duct tape programmer you want to be that really annoying guy on your team who writes bad code emulate him do what he is doing to get out the door fast”
The objective of the Green phase is purely cognitive: discovering and verifying how to solve the problem and satisfy the requirement. Once the test passes, the developer transitions to the third step—refactoring—where the crude, duct-taped code is restructured into clean, well-engineered designs.
[43:23](https://www.youtube.com/watch?v=EZ05e7EMOLM&t=2603s) Red-Green-Refactor: Speed Trumps Design in the Green Phase
Ian Cooper returns to the core mechanics of Test-Driven Development, pointing out that when you are trying to make a failing test pass, your focus must be singularly narrow. Citing Kent Beck, he stresses: “For this brief moment speed trumps design I want to get there as fast as I can.” Beck's insight is grounded in cognitive limits: a developer simply cannot tackle two hard problems simultaneously. You cannot figure out how to solve the domain problem while simultaneously trying to architect pristine, elegant code. If you attempt both, you inevitably hit one of two failure modes: you will over-engineer the implementation far beyond the requirements of the test, or you will freeze up in analysis paralysis.
The first imperative is simply to get the code working and make the test turn green. Producing clean code is intentionally deferred to the next phase: the refactoring step. Clean code is not abandoned; developers are not meant to remain reckless duct-tape programmers all day. Instead, refactoring is the dedicated phase where code is re-engineered. Beck emphasized duplication as the primary driver: you spot duplication, remove it, and the architecture begins to emerge cleanly. Martin Fowler's work on refactoring complements this by directing developers to hunt for specific code smells that reveal structural flaws, while Joshua Kerievsky showed that this refactoring phase is precisely when design patterns should be introduced. Because you already possess a functional solution, you can clearly judge whether a pattern is genuinely appropriate or merely unwarranted complexity.
```mermaid flowchart LR accTitle: The Two-Step Problem Solving Dynamic in TDD accDescr: Diagram contrasting the Green step prioritizing speed with the Refactoring step prioritizing clean code and pattern application.
subgraph GreenPhase["1. Green Phase (Speed)"] G1["Write failing test"] --> G2["Solve problem quickly"] G2 --> G3["Make test pass<br/>(Ignore design purity)"] end
subgraph RefactorPhase["2. Refactor Phase (Clean Code)"] R1["Inspect working code"] --> R2["Remove duplication & smells"] R2 --> R3["Apply patterns safely<br/>(Do NOT write new tests)"] end
GreenPhase --> RefactorPhase ```
[45:10](https://www.youtube.com/watch?v=EZ05e7EMOLM&t=2710s) Safe Moves: Why You Must Never Write Tests During Refactoring
Cooper delivers what he considers the single most vital rule of the refactoring stage: do not write any new tests. Refactoring is, by definition, a sequence of safe structural transformations that alter internal implementation without altering external behavior. Because the behavior remains identical, it is already completely covered by the test written at the start. If that test verified that adding two amounts across different currencies produces a correct balance, any internal rearrangement that preserves that result is safe. Code coverage tools can assist here to guard against accidentally introducing speculative conditionals or new behaviors during cleanup; if a genuine new behavior is needed, it must wait for its own red-green cycle.
The practical consequences of honoring this boundary are profound: you write far fewer tests, and you move dramatically faster. When developers mistakenly treat every newly extracted internal class as an excuse to write an entire suite of unit tests, velocity grinds to a halt, giving management the impression that TDD cannot keep up with lean delivery. Classes extracted as implementation details (using internal, package-private, or private visibility depending on whether you are in C# or Java) should remain hidden behind the module boundary. Tests must not couple to internal classes. If internal classes are completely invisible to the test suite, developers remain completely free to rip them out, rewrite them, or replace them tomorrow without a single test failing.
- Only introduce a new test if your refactoring accidentally uncovers an entirely new concept that forms part of the module's exposed public contract. - In those rare cases where an emerging concept belongs in the public API, mocking can temporarily decouple the current work until that secondary interface is formally implemented. - Coupling to internal classes is toxic: “Coupling is worse enemy for you than DRY coupling is what kills all software.” - Refactoring implementation details becomes effortless once tests target behavior exclusively; Cooper notes how his own team overhauled the entire internal modeling of an aggregate without breaking a single existing test.
[49:04](https://www.youtube.com/watch?v=EZ05e7EMOLM&t=2944s) Encapsulation, Code Smells, and Refactoring to Patterns
Addressing common bad habits, particularly in the.NET ecosystem, Cooper makes an urgent plea to preserve information hiding: “Test behaviour don't test implementations.” A glaring warning sign is surfacing implementation classes as `public` purely to allow unit tests to instantiate them. Similarly, developers should never abuse configuration settings like `[InternalsVisibleTo]` in.NET to bypass accessibility barriers and expose internal details directly to a test project. If something is an implementation detail, opening it up to tests defeats encapsulation and invites brittle test coupling.
Cooper recounts how Martin Fowler's book *Refactoring* established recipes for safe changes targeting code smells such as long methods, transaction-script bodies, and feature envy. He humorously points out how third-party tooling once strictly policed this boundary. DevExpress built a refactoring tool for Visual Studio that outright refused to refactor a public method on a public class; their reasoning was that altering a public interface is not a refactoring—it is an outward-facing breaking change. Only after intense market pressure from rival tools like ReSharper did DevExpress eventually introduce an explicitly labeled “unsafe refactoring” button.
```mermaid flowchart TD accTitle: Information Hiding and Test Boundaries accDescr: Contrast between anti-patterns that expose internals to tests and the correct pattern that preserves encapsulation.
subgraph AntiPattern["Anti-Pattern: Leaking Internals"] direction TB T1["Test Assembly"] -->|"Coupled via InternalsVisibleTo<br/>or public leaks"| I1["Internal Helper Classes"] T1 -->|"Directly tests"| I2["Private / Internal Methods"] end
subgraph BestPractice["Best Practice: Stable Public Contract"] direction TB T2["Test Suite"] -->|"Calls only"| P1["Stable Module API / Contract"] P1 -->|"Encapsulates"| H1["Internal Helper Classes"] P1 -->|"Encapsulates"| H2["Implementation Mechanics"] end ```
Echoing Joshua Kerievsky’s *Refactoring to Patterns*, Cooper explains that developers historically became “pattern happy,” indiscriminately scattering Gang of Four design patterns into greenfield code and leaving behind bloated, over-engineered architectures. The proper time to introduce a design pattern—whether a Command or a Template Method—is during the refactoring step. Because you have already written a functioning (albeit crude) implementation, you possess full visibility into the problem's shape and can confidently determine whether a pattern provides genuine structural clarity.
- Fowler’s *Refactoring* formalized safe, repeatable recipes that guarantee code will not suffer behavioral breakage when structural extractions occur. - Modifying an external signature is a breaking API change, not an internal refactoring. - Kerievsky highlighted that design patterns applied preemptively create accidental complexity; applied during refactoring, they solve proven structural pain.
[53:07](https://www.youtube.com/watch?v=EZ05e7EMOLM&t=3187s) The Fragile Inverted Testing Pyramid and UI Automation
Turning to enterprise realities, Cooper laments the deeply flawed testing structures commonly seen in production organizations. Instead of a healthy testing pyramid, organizations frequently build an inverted pyramid topped by an enormous, costly wave of manual QA regression testing that is run immediately prior to every deployment. Below that manual wave sits a heavy suite of automated end-to-end browser tests—typically maintained by specialized automation engineers using Selenium—followed by a scattering of integration tests, and ending with an anemic handful of true developer tests at the bottom.
Cooper characterizes this inverted structure as an outright disaster. First, manual testing sweeps before every release are slow, exceedingly expensive, and non-repeatable. Second, driving business behavioral tests through end-to-end UI tools like Selenium introduces severe test fragility. If a designer updates the user interface to sport a modern 2017 look rather than an outdated 2008 aesthetic, not a single piece of business behavior has altered. Yet, because the end-to-end automation scripts depend directly on DOM elements, widget layouts, and button selectors, changing the visual appearance shatters the entire test suite.
| Level in Broken Pyramid | Execution Type | Primary Flaw / Cost Factor | |:--- |:--- |:--- | | **Manual Verification Wave** | Human testers following scripts | Expensive, slow, completely unrepeatable before releases. | | **End-to-End UI Automation** | Selenium browser scripts | Extremely fragile; simple UI/widget restyling breaks tests despite identical behavior. | | **Integration Layer** | Mixed component checks | Often sparse, slow, and unfocused. | | **Developer Tests** | Unit / Module tests | Highly neglected, leaving core logic under-tested at the foundational level. |
[54:28](https://www.youtube.com/watch?v=EZ05e7EMOLM&t=3268s) The Testing Pyramid and the Morning Blame Game
Ian Cooper warns against relying on slow, end-to-end integration suites that run overnight and create dysfunctional organizational friction every morning. When automated test suites take all night to run, teams inevitably end up playing the blame game the next day. Cooper reflects candidly on this antipattern: “If you have find yourself in the situation where you have somebody whose job every morning is to determine who's to blame the test failing, you are doing it wrong.” He emphasizes with self-deprecating humor that he has personally made every single one of these mistakes and is not preaching from an ivory tower of intellectual superiority.
Instead of top-heavy suites, developers must structure their testing according to the classic testing pyramid. The vast majority of code should be covered by rapid, reliable developer tests at the bottom layer. A small, thin cap at the very top tests that user-facing widgets and full systems wire together properly. In between sits integration testing to verify that adapters and boundary components hook up correctly, though Cooper remarks that the traditional middle tier has often been somewhat of a scam.
```mermaid flowchart TD accTitle: The Classic Testing Pyramid accDescr: Pyramid showing UI tests at the top, integration in the middle, and developer tests at the base. subgraph Pyramid ["Testing Pyramid Structure"] Top["Widget / System Tests (Smallest volume)"] Mid["Integration Tests (Adapter verification)"] Base["Developer Tests (Majority of code coverage)"] end Top --> Mid Mid --> Base ```
[55:18](https://www.youtube.com/watch?v=EZ05e7EMOLM&t=3318s) Ports and Adapters (Hexagonal Architecture)
Cooper connects proper TDD boundaries to Hexagonal Architecture—also known as Clean Architecture by Uncle Bob (Robert C. Martin) or Onion Architecture, which all represent independent discoveries of the same core insight. At the core of the system sits domain code built entirely of POCOs (Plain Old C# Objects) or POJOs (Plain Old Java Objects). Cooper jokes that while Python developers might call them "POPOs," one must be careful with that term in the UK because it doubles as street slang for the police. These domain objects must remain entirely devoid of technology concerns: they know nothing about HTTP, web frameworks, or database engines.
On the outside sits the adapter layer, which handles all external technical concerns. Primary inbound adapters encompass things like ASP.NET Web API, Django, Sinatra, Flask, NancyFX, or GUI frameworks like Web Forms. Outbound secondary adapters handle database persistence, messaging brokers like RabbitMQ, or email sending. Between this pure domain core and the technical adapters lies the ports layer—acting like a door or facade in an operating system. Cooper insists that the port is the ideal surface where developer tests must execute: they should talk directly to the ports facade, not to the internal domain classes, because the port reflects the actual boundary through which external actors communicate and through which domain events or persistence calls emerge.
```mermaid flowchart LR accTitle: Hexagonal Architecture Boundaries and Test Targets accDescr: Outside adapters communicate through the port boundary to the pure domain core. Developer tests target the port.
subgraph Outbound ["Inbound & Outbound Adapters"] InHTTP["HTTP / Web API / Django / GUI"] OutDB["Databases / RabbitMQ / Email"] end
subgraph Boundaries ["Ports Layer (Public API)"] InPort["Inbound Port (Facade)"] OutPort["Outbound Port (Repository / Gateway)"] end
subgraph Core ["Domain Core"] Domain["Plain Domain Objects (POCOs / POJOs)"] end
Test["Developer Tests"] -->|"Calls directly"| InPort InHTTP -->|"Drives"| InPort InPort -->|"Invokes"| Domain Domain -->|"Uses"| OutPort OutPort -->|"Implemented by"| OutDB ```
- **Testing Surface:** Developer tests target the port interface rather than internal implementation classes. - **Integration Tests:** Only verify that an adapter is configured correctly to speak with the real external dependency (e.g., database, message bus); do not test third-party vendor code itself. - **System Tests:** Exercise a thin path from the very outside inward to prove the entire pipe connects.
[57:59](https://www.youtube.com/watch?v=EZ05e7EMOLM&t=3479s) Shifting Gears in TDD
Cooper tackles the objection that developers sometimes want TDD to help them discover and explore low-level implementation details when designing tricky logic. Borrowing an analogy from Kent Beck, Cooper describes the practice of shifting gears:
In fourth gear, you follow the standard high-discipline practice: test against the public API/port, implement whatever works to get green, and refactor the internal details while the port tests remain fixed. In fifth gear, the implementation is so obvious and straightforward that you skip writing crude scaffold code and implement the clean solution immediately; however, Cooper cautions, “Be careful here if basically you find that your coverage starts to blow out when you put your tests, shift down to fourth because you're probably writing too much speculative code you haven't got tests for.”
When completely lost on how to reach green, downshift into lower gears (or use an interactive REPL in languages like Python) to write fine-grained, scratchpad unit tests that feel out the design. Crucially, once you discover the path forward and satisfy the behavioral requirement, you must delete those low-level implementation tests: “The tests help guide you, but now they're a burden to somebody else coming along.” Leaving these scratchpad tests behind only cements implementation details, blinding and frustrating the next developer who attempts to refactor the design.
[59:39](https://www.youtube.com/watch?v=EZ05e7EMOLM&t=3579s) Acceptance TDD and the Fall of Translation Layers
Turning to Acceptance Test-Driven Development (ATDD) and tools like Fit, FitNesse, and Cucumber/Gherkin, Cooper walks through the history and his own change of heart. Kent Beck originally warned against ATDD because customers are rarely interested in reading or maintaining automated test suites, making the process prohibitively expensive. Although Beck momentarily softened around 2004 when writing a foreword for an ATDD book, industry pioneers have since abandoned it.
Cooper notes that James Shore, who co-wrote Fit for.NET, explicitly stopped using it because business customers simply do not care to maintain test scripts, turning them into a massive ongoing maintenance burden for developers. Shore concluded that developers should instead talk to customers directly, learn their expectations, and translate that knowledge straight into developer tests. Cooper admits he previously championed Fit and Gherkin to keep developers honest about what "done" meant, but eventually came to a stark realization: “Over time I've come around to realize that probably what we were solving was the problem of how we're doing our unit testing because our unit testing was focusing methods on classes, we had nothing that told us about behaviors.” When developers shift their unit testing to focus on behavioral requirements, the need for cumbersome natural-language parsing layers disappears entirely.
[01:01:08](https://www.youtube.com/watch?v=EZ05e7EMOLM&t=3668s) BDD, Mocks, and Inversion of Control Overuse
Cooper touches on Behavior-Driven Development (BDD), observing that Dan North's original article offered an essential insight: test behaviors, not methods and classes. While BDD has grown into a full-scale agile organizational methodology, its central kernel remains that shift in perspective toward behavior.
On mock objects, Cooper warns that mocks were intended for expensive shared resources that create fixed boundary problems. Instead, developers abused them to isolate individual classes from one another or to micromanage junior developers by verifying precise internal method invocations. Cooper condemns this practice: “It just couples your test to your software, means you can't change it, it's never worth it.”
Cooper delivers a similar critique of Inversion of Control (IoC) containers. Like Object-Relational Mappers (ORMs), IoC containers are drastically overused in the industry. Good frameworks should rely on factories rather than forcing container registrations, and test suites should never rely on setting up complex container wiring: “If you find yourself implementing an IoC registration in your test, something has gone horribly wrong.”
- **Mocks Antipattern:** Using mocks to isolate individual internal domain classes or to enforce internal implementation sequences binds tests rigidly to private structure. - **IoC Containers:** Minimize IoC container usage. Keep tests completely free of dependency-injection container configuration.
[01:02:52](https://www.youtube.com/watch?v=EZ05e7EMOLM&t=3772s) Summary and Wrapping Up
Cooper summarizes the core tenets of the talk in a rapid concluding checklist: - Add tests solely for new behaviors, not for internal methods or individual classes. - Write simple, dirty code to get to green, and then refactor mercilessly. - Do not write tests for private methods or refactored internal classes; the original port tests cover them. - Target tests at the ports of a hexagonal architecture to preserve complete freedom to refactor internal implementations. - Add integration tests exclusively to verify port and adapter configurations against real external systems. - Avoid mocking internal collaborators; reserve mocks solely for unmanageable process boundaries and external adapters.
Having run out of time for a seated Q&A session, Cooper invites attendees to approach the stage directly or chat throughout the conference. He mentions that related versions of this talk are available online from NDC conferences and recommends watching the debate between Kent Beck, DHH (David Heinemeier Hansson), and Martin Fowler on TDD and software architecture.
[01:03:48](https://www.youtube.com/watch?v=EZ05e7EMOLM&t=3828s) Concluding Remarks and Audience Applause
Bringing his presentation to a close, Ian Cooper wraps up the session by acknowledging the ongoing debate surrounding test-driven development and how modern practices strayed from its founding principles. He invites further discussion on where things took a wrong turn before expressing his gratitude to the attendees. Wrapping up his talk, Ian Cooper says, “Conversation about how it's all gone wrong but thanks very much thank you,” after which the presentation concludes to warm applause from the audience.
## Notable quotes > [24:16](https://www.youtube.com/watch?v=EZ05e7EMOLM&t=1456s) The trigger in TDD for creating a new test is that you have a requirement you want to implement; it is nothing more, it is nothing less. — Ian Cooper
> [01:01:08](https://www.youtube.com/watch?v=EZ05e7EMOLM&t=3668s) If you find yourself implementing an IoC registration in your test, something has gone horribly wrong. — Ian Cooper
> [35:01](https://www.youtube.com/watch?v=EZ05e7EMOLM&t=2101s) The unit of isolation is the test and a lot of mistakes have been made because people believe the unit of isolation is the class under test it is not — Ian Cooper
> [49:04](https://www.youtube.com/watch?v=EZ05e7EMOLM&t=2944s) Test behaviour don't test implementations. — Ian Cooper
## Mentioned Ian Cooper, Kent Beck, Martin Fowler, David Heinemeier Hansson (DHH), Fred George, Dan North, Joel Spolsky, George Best, Joshua Kerievsky, James Shore, Robert C. Martin (Uncle Bob), Huddle, .NET, Ruby on Rails, FitNesse, Cucumber, SpecFlow, Fit, Gherkin, Selenium, DevExpress, Visual Studio, ReSharper, ASP.NET Web API, Django, Sinatra, Flask, NancyFX, RabbitMQ, Test Driven Development: By Example, TDD is dead. Long live testing, RIP TDD, Refactoring, Refactoring to Patterns, NDC conferences
## Video description 🗓️ Upcoming developer events: https://dev.events Since Kent Beck wrote the book on TDD in 2002 a lot of words have been dedicated to the subject. But many of them propagated misunderstandings of Kent's original rules so that TDD practice bears little resemblance to Kent's original ideas. Key misunderstandings around what do I test, what is a unit test, and what is the 'public interface' have led to test suites that are brittle, hard to read, and do not support easy refactoring. In this talk, we re-discover Kent's original proposition, discover where key misunderstandings occurred and look at a better approach to TDD that supports software development instead of impeding it. Be prepared from some sacred cows to be slaughtered and fewer but better tests to be written.
[Source](https://www.youtube.com/watch?v=EZ05e7EMOLM)