🚀 TDD, Where Did It All Go Wrong (Ian Cooper)

🚀  TDD, Where Did It All Go Wrong (Ian Cooper) — DevTernity Conference

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)

中文简报

🚀 TDD:究竟哪里走偏了(Ian Cooper) Ian Cooper 认为,软件行业在很大程度上将测试驱动开发(TDD)扭曲成了一种反模式,过度关注测试单个类和方法,而非可观察的系统行为。由于将类视为隔离单元,开发者往往会过度指定模拟(mock)交互,导致内部实现重构时测试套件极易损坏。Cooper 重温了 Kent Beck 最初的规则,指出测试应当针对稳定的模块门面或六边形架构中的端口,隔离指的是测试执行之间的隔离,而非类边界的隔离。他强调应将行为探索与结构清理区分开来,在绿灯阶段像追求速度的“胶带程序员”一样快速实现,并将重构专门留给架构模式的设计,期间无需添加新的测试。 业界对 Kent Beck 原始 TDD 规则的普遍误解,导致测试代码与生产代码的比例膨胀至 2:1 甚至 3:1,使交付速度降低了 20% 到 50%。 (6:04) 现代 TDD 的核心反模式是将类和单个方法视为测试隔离的单元,这会导致产生高度脆弱、过度模拟的测试,并在内部重构时频繁损坏。 (26:02) TDD 中真正的单元隔离是指测试用例彼此之间的执行隔离,测试应完全由针对公共模块门面或端口的需求来触发。 (35:01) 在红-绿-重构循环中,开发者应在绿灯阶段编写粗糙的事务脚本以追求速度,将架构模式设计严格保留在重构阶段,且无需添加新的单元测试。 (39:57)

Reading summary

Ian Cooper argues that widespread engineering frustration with Test-Driven Development (TDD) stems from abandoning Kent Beck’s original principles in favor of testing isolated class methods and overusing mocks. By coupling test suites directly to volatile implementation details rather than verifying observable behaviors at module boundaries, teams transformed tests from enablers of clean architecture into rigid barriers against refactoring. How Implementation Coupling Stalled Refactoring and Delivery (6:04) Cooper observed that standard TDD suites regularly ballooned to two or three times the volume of production code, imposing a 20 to 50 percent delivery penalty. When teams attempted internal structural cleanups without altering external system behaviors, mock-heavy tests—which asserted precise call orders, invocation counts, and parameters—routinely failed. Instead of guarding against codebase decay, the test suites became costly maintenance obstacles that actively discouraged refactoring. This friction provoked an anti-testing backlash across the industry. Alternative approaches gained traction, including Fred George’s 'Programmer Anarchy'—which favored discarding tests entirely in favor of throwaway, ~100-line microservices—and lean startup models prioritizing rapid customer feedback over upfront automation. Engineering managers routinely favored pragmatic 'duct-tape' programmers who shipped working software immediately, while disciplined teams remained bogged down maintaining brittle test suites. Triggering Tests from Requirements, Not Class Methods (24:16) Revisiting Kent Beck’s 2002 book 'Test Driven Development: By Example', Cooper asserts that the sole valid trigger for writing a test is the arrival of a requirement, not the addition of a method to an internal class. Tests should exercise the exposed public contract or facade of a module, treating internal domain classes as an encapsulated black box that can be freely restructured beneath stable behavior. Cooper also argues that the industry fundamentally misconstrued Beck's concept of a 'unit'. Beck defined the unit of isolation as the test executing independently without polluting other tests through shared external state, not an individual class severed from its collaborating classes. Conflating the unit with a class forced teams into excessive mocking and over-specification—the exact distortion that led Dan North to introduce Behavior-Driven Development (BDD) to refocus developers on observable system behavior. Separating Problem Solving from Code Refactoring (43:23) Cooper emphasizes that developers cannot simultaneously solve business logic and engineer pristine architectures without risking over-engineering or analysis paralysis. In the green phase, speed trumps design: engineers should write crude, procedural transaction scripts or copy code to satisfy the failing test as fast as possible. Clean architecture is intentionally deferred to the refactoring step, where duplication is eliminated, code smells are addressed, and design patterns are applied. Crucially, Cooper insists that developers must never write new tests during the refactoring phase. Because refactoring preserves external behavior by definition, the existing behavioral test suite already covers the changes. Extracted classes should remain private or internal implementation details hidden behind module boundaries, avoiding mechanisms like '[InternalsVisibleTo]' that breach encapsulation and create brittle coupling between test projects and internal plumbing. Testing Architectural Ports While Abandoning Fragile Layers (55:18) To establish sustainable test boundaries, Cooper points to Ports and Adapters (Hexagonal Architecture). Developer tests should execute against inbound port facades rather than invoking internal domain objects or slow outer adapters like databases and HTTP endpoints. This structure avoids the fragility of inverted testing pyramids dominated by brittle end-to-end Selenium UI tests and overnight test runs that foster morning blame-shifting rituals. When grappling with complex internal algorithms, Cooper recommends Beck's metaphor of 'shifting gears': developers can temporarily downshift to write fine-grained scratchpad tests, but must delete them once the public port test passes to prevent cementing internal implementation details. Furthermore, Cooper notes that natural-language tools like FitNesse and Cucumber largely failed because business stakeholders rarely read plain-text fixtures, leaving engineering teams to maintain expensive translation layers that become unnecessary once developer tests focus on behavior. Cooper concludes that TDD remains highly effective when practiced according to its original formulation: driving software design through behavioral requirements verified at architectural ports, while leaving internal classes unmocked and encapsulated. He recommends minimizing Inversion of Control container setup in tests, restricting mocks exclusively to external process boundaries, and abandoning customer translation layers in favor of direct collaboration.

精炼阅读摘要

Ian Cooper 认为,现代软件开发对测试驱动开发(TDD)产生的广泛抵触,本质上源于行业严重偏离了 Kent Beck 最初确立的原则;将测试单元误解为单个类或方法并过度依赖模拟(Mock),使测试与内部实现过度耦合,不仅拖慢了交付效率,更彻底破坏了安全重构的核心承诺。 测试膨胀、重构受阻与反 TDD 浪潮 (6:04) Cooper 回顾指出,团队在严格推行 TDD 过程中普遍遭遇测试代码量达到生产代码 2 至 3 倍的困境,导致整体交付速度比无测试开发放缓 20% 至 50%。沉重的维护负担促使管理层经常要求跳过编写测试以加速交付。更为严峻的是,Martin Fowler 与 Kent Beck 将重构定义为在不改变外部可观察行为的前提下调整内部结构,但在充斥着细粒度 Mock 的测试套件中,开发人员仅调整内部实现便会导致大量测试因调用次数或顺序不符而中断。测试套件不仅未能提供保护,反而成为架构演进的严重阻碍。 这种执行摩擦在 2009 至 2010 年催生了强烈的反弹。Fred George 提出了取消质量保障(QA)和产品经理、完全依赖约百行微服务且不写测试的“程序员无政府主义”(Programmer Anarchy);硅谷初创企业亦普遍采纳“先试错后稳定”(spike and stabilize)的精益模式,推崇无需前期测试的快速交付。Joel Spolsky 所定义的“胶带程序员”(duct-tape coder)因能迅速交付运行软件而屡获管理层青睐。至 2014 年,David Heinemeier Hansson(DHH)发表《TDD 已死》博文,痛陈测试教条对 Rails 等框架设计的扭曲,进一步引爆了开发者社区对 TDD 实际效用的质疑。 以需求行为为测试边界而非类与方法 (24:16) Cooper 强调,行业走入误区的根源在于误将添加类或方法作为编写测试的触发器,并把测试的“隔离单元”错误地等同于面向对象语言中的单个“类”。根据 Kent Beck 在 2002 年著作《测试驱动开发》中的论述,编写新测试的唯一合法触发点是系统需要实现新的业务需求或可观察行为。Dan North 在 2005 年创立行为驱动开发(BDD),核心目的同样是纠正这种将注意力锚定在类方法而非业务行为上的普遍偏差。 在规范的 TDD 理念中,“隔离”的真正客体是测试用例本身,即测试套件中的各个用例在并发或相继执行时不得相互干扰,而非把单个类与其协作对象机械隔绝。传统计算机科学中的“单元测试”通常指针对整个模块导出接口(API)或稳定门面(Facade)的黑盒验证。若将测试绑定在内部协作类上并依赖大量 Mock,会导致测试知晓过多私有实现细节。Cooper 指出,只有为了规避数据库或文件系统等外部共享资源所带来的状态污染或执行延迟时,替换真实依赖才具备合理性;针对系统行为的公共契约编写测试,才能赋予内部重构充分的自由度。 红绿重构机制:先快速实现后安全重构 (43:23) 在执行循环方面,Cooper 强调必须严格遵循“红、绿、重构”三个阶段各自的认知定位。红灯阶段的唯一目标是确认测试确实会因缺乏实现而失败,从而杜绝不会报错的假阳性断言;绿灯阶段则必须贯彻 Kent Beck 提出的“速度压倒设计”准则,开发者应当专注于以最简单直接、哪怕极其粗糙的过程式代码(如事务脚本或快速复制代码)让测试通过,因为人脑难以同时处理“实现业务功能”与“设计优美架构”两重复杂的认知负荷。 只有在测试变绿之后,开发人员才能转入重构阶段。在此步骤中,团队通过消除代码重复、识别坏味道来净化代码,并如 Joshua Kerievsky 所阐述的那样适时引入设计模式。Cooper 明确提出了重构阶段的铁律:绝对不要在重构过程中编写新的测试。既然重构不改变可观察行为,最初编写的行为测试已具备完整覆盖。重构所提取的内部类应维持内部或包私有可见性,严禁滥用如.NET 的 [InternalsVisibleTo] 等配置向测试公开内部细节;保持内部实现对测试不可见,开发人员才能在未来随意推翻或替换具体实现而不造成测试中断。 六边形架构实践:测试端口并消除过度Mock (55:18) 针对企业中底层单元测试薄弱、顶层堆砌脆弱且昂贵的端到端 UI 自动化与人工回归的“倒金字塔”乱象,Cooper 主张重归测试金字塔,并借助六边形架构(端口与适配器架构)理清测试边界。在该架构中,核心领域模型由纯粹的 POCO 或 POJO 构成,不包含 HTTP 或持久化等技术依赖;开发者测试应当直接调用系统的“入站端口”(Inbound Port)门面,而集成测试仅用于验证适配器与外部真实基础设施的连通性配置。 Cooper 进一步反思了过度依赖工具所造成的额外负担。使用 Mock 强制验证内部领域对象的调用顺序不仅毫无价值,更会死锁软件结构;控制反转(IoC)容器也不应入侵测试配置。同时,James Shore 等先驱与 Cooper 自身的实践表明,FitNesse 或 Cucumber 等基于自然语言的验收测试驱动开发(ATDD)工具在现实中极难促使业务客户参与,反而沦为开发人员沉重的维护泥潭。当单元测试重新聚焦于端口行为时,繁琐的自然语言解析层便不再必要。若在复杂算法设计中需要临时探测,开发者可参考 Beck 的“换挡”理论降挡编写低级别测试,但一旦设计明朗,必须坚决删除此类临时测试以防后患。 Cooper 总结认为,TDD 本身并未失效,失效的是被行业误解的类级别隔离、过度 Mock 以及繁复的验收测试层。解决现代开发困局的方法在于重返 Kent Beck 的原始教导:将业务行为作为唯一测试触发点,依托六边形架构的端口确立测试边界,在实现阶段追求速度,在重构阶段坚决不增加新测试并保持实现细节隐藏,以此构建精简敏捷且真正支持长期演进的软件资产。

Detailed notes — zh

## Background 该视频由技术大会 DevTernity Conference 于 2017 年 12 月 20 日发布在 YouTube 上。页面简介指出,自 Kent Beck 于 2002 年提出 TDD 以来,行业内产生了大量关于“测什么”、“什么是单元测试”以及“公开接口”的误解,导致当今许多测试套件脆弱、晦涩且阻碍重构;本演讲旨在重温 Kent Beck 的原始构想,纠正关键误区并探索真正赋能软件开发的 TDD 实践。 ## TL;DR Ian Cooper 回顾了自己十余年的 TDD 实战经历,深刻反思了软件行业如何将原本旨在保障重构的优秀实践扭曲为阻碍开发的沉重负担。他指出,现代开发者误将“被测单元”窄化为单一类,并在方法级别泛滥使用 Mock,导致测试与具体实现细节深度耦合,代码稍有重构便大面积飘红。Cooper 倡导重归 Kent Beck 最初倡导的 TDD 原则与 Dan North 的行为视角:编写测试的唯一触发点是业务需求而非类的方法;在红-绿-重构循环中,允许以粗糙代码快速变绿,且重构期间严禁为提取的内部私有类补充测试;最终结合六边形架构,将测试锚定在稳定的对外端口(Port/Facade)层,从而写出更少却更高价值的测试套件。 ## Atmosphere & style 整场演讲呈现出一种直击痛点、充满自嘲与犀利解构的坦诚氛围。Cooper 既像一位遍历沧桑的老兵,又像一位不留情面的技术清醒者,借用乔治·贝斯特的艳遇笑话、“胶带程序员 Bob”以及晨间“抓鬼定罪”等鲜活案例,生动还原了工程师在日常开发中的疲惫与荒唐感。现场既有引人会心一笑的幽默嘲讽,又有刀刀见血的方法论批判,给听众带来一种卸下教条枷锁、豁然开朗的震撼与共鸣。 ## Bulletin - [20:24](https://www.youtube.com/watch?v=EZ05e7EMOLM&t=1224s) 现代 TDD 偏离了 Kent Beck 的初衷:行业误将面向模块行为的黑盒测试窄化为针对单一类的白盒测试,并泛滥使用 Mock,导致测试与内部实现细节高度死锁,反而阻碍了重构。 - [24:01](https://www.youtube.com/watch?v=EZ05e7EMOLM&t=1441s) 编写新测试的唯一合法触发条件是系统出现了新的业务需求或用例行为,而不是在类上新增或修改某个方法。 - [35:01](https://www.youtube.com/watch?v=EZ05e7EMOLM&t=2101s) 测试隔离的核心是“测试用例之间的执行隔离”而非“被测类的代码隔离”,单元测试直接连接无状态污染的数据库或文件系统完全符合 Kent Beck 的原始定义。 - [43:23](https://www.youtube.com/watch?v=EZ05e7EMOLM&t=2603s) 重构阶段的铁律是严禁编写新测试:重构提取出的内部类应当保持私有或 internal,其正确性已被面向对外契约的既有测试覆盖。 - [55:18](https://www.youtube.com/watch?v=EZ05e7EMOLM&t=3318s) 六边形架构的“端口层”(Ports/Facade)是执行开发者测试的最佳切入点,过度依赖端到端 UI 测试或复杂 ATDD 翻译层会严重拖慢研发效率与破坏编程心流。 ## Key takeaways - [06:04](https://www.youtube.com/watch?v=EZ05e7EMOLM&t=364s) 测试代码量达到生产代码两到三倍但开发速度减缓 20% 到 50%,反映出团队测试套件过于臃肿且缺乏高性价比反馈。 - [08:17](https://www.youtube.com/watch?v=EZ05e7EMOLM&t=497s) Mock 泛滥将底层方法调用序列与参数完全锁死,导致本不该破坏对外行为的代码重构引发大面积测试红灯。 - [14:38](https://www.youtube.com/watch?v=EZ05e7EMOLM&t=878s) 以 FitNesse 为代表的端到端验收测试长期处于全红状态,不仅掩盖真实缺陷,且业务方从不查阅,沦为维护沉重的假文档。 - [19:01](https://www.youtube.com/watch?v=EZ05e7EMOLM&t=1141s) DHH 发文宣告“TDD 已死”并抨击教条主义,促成了与 Kent Beck、Martin Fowler 的公开论战,推动业界反思测试偏离本质的现状。 - [24:01](https://www.youtube.com/watch?v=EZ05e7EMOLM&t=1441s) 测试驱动编写的唯一触发器是“实现业务需求行为”,以“为类添加新方法”作为测试触发点是当代实践中最致命的误区。 - [26:02](https://www.youtube.com/watch?v=EZ05e7EMOLM&t=1562s) 测试目标必须是模块向外暴露的稳定契约(API/Facade),绝不应将测试渗透到易变的内部实现细节与辅助函数中。 - [28:00](https://www.youtube.com/watch?v=EZ05e7EMOLM&t=1680s) SUT(被测系统)并非单一类,过度将“单元”窄化为“类”迫使开发者编写大量无意义的隔离 Mock,制造出过度指定的脆弱测试。 - [31:58](https://www.youtube.com/watch?v=EZ05e7EMOLM&t=1918s) Dan North 倡导 BDD 旨在用“行为”(Behavior)替换“测试”(Test)概念,让开发者聚焦验证业务需求而非微观方法。 - [35:01](https://www.youtube.com/watch?v=EZ05e7EMOLM&t=2101s) 隔离的单元是测试用例本身而非被测试代码,限制外部持久化访问纯粹为了防止测试间共享夹具污染并保障毫秒级反馈速度。 - [39:57](https://www.youtube.com/watch?v=EZ05e7EMOLM&t=2397s) 绿灯阶段追求“速度压倒设计”,允许像“胶带程序员”一样用最粗暴、简陋的事务脚本快速跑通逻辑,严禁过早套用设计模式。 - [43:23](https://www.youtube.com/watch?v=EZ05e7EMOLM&t=2603s) 重构阶段严禁编写任何新测试,提取的类应保持 internal 或包级私有,使用 [InternalsVisibleTo] 暴露细节测试是典型反模式。 - [49:42](https://www.youtube.com/watch?v=EZ05e7EMOLM&t=2982s) 设计模式的最佳应用时机是在重构阶段根据已跑通的丑陋代码消除坏味道,而非在绿灯前进行投机性预先设计。 - [53:07](https://www.youtube.com/watch?v=EZ05e7EMOLM&t=3187s) 依赖繁重人工点击和脆弱 UI 自动化(如 Selenium)的倒金字塔测试体系,一旦发生界面重构就会全线崩溃,极为昂贵且不可持续。 - [55:18](https://www.youtube.com/watch?v=EZ05e7EMOLM&t=3318s) 依据六边形架构,开发者测试应统一锚定在端口层(Ports/Facade),领域核心为纯对象,外部适配器只需轻量级集成测试验证配置。 - [57:59](https://www.youtube.com/watch?v=EZ05e7EMOLM&t=3479s) Kent Beck 的五档理论:遇到复杂算法可降入低档编写细粒度内部测试探索前进,但探索完成后必须立刻删除这些探路测试。 - [01:01:36](https://www.youtube.com/watch?v=EZ05e7EMOLM&t=3696s) 在测试代码中配置 IoC 容器注册或滥用 Mock 进行白盒实现审查是严重的设计脱轨,会彻底剥夺系统未来的可重构性。 ## Detailed walkthrough ### [00:00](https://www.youtube.com/watch?v=EZ05e7EMOLM&t=0s) 开场互动与演讲初衷 Ian Cooper 首先向在座的观众提问互动,询问有多少人看过他之前关于该主题的演讲视频,现场有少数几位举了手。他提醒道,现场演讲会有些许细微差别和节奏调整,但核心论点始终如一。接着,他调查了现场使用测试驱动开发(TDD)的情况:有多少人目前正在做测试(先测还是后测)、有多少人严格践行“先写测试”(test-first),以及有多少人在使用 Mock 技术。看到大量观众举手表示重度使用 Mock 时,他认为这非常耐人寻味。 随后,Ian Cooper 介绍了自己的背景。他打趣道,幻灯片上的个人履历大多枯燥无趣,但最重要的一点是:“像我这样来大会演讲并站在你们面前的人,并不一定比你们更聪明或更有才华;我非常鼓励大家尽可能站出来在技术大会上分享、回馈社区。”他解释说,自己只是一名普通的开发者,早年在.NET 刚起步时,由于市场上几乎找不到现成的专家,要想组织用户组或 Meetup 就必须硬着头皮自己上。他认为参与分享不仅是回馈,更是程序员个人成长的极佳途径,能逼自己把想法表达得更加清晰。他目前就职于 SaaS 软件公司 Huddle(同时顺便提了一句公司在招人),但他强调自己不是来推销任何产品或咨询服务的,所有的思考都纯粹源自多年的实战血泪经验。 - 现场大部分开发者都在实践 TDD,且绝大多数都极度依赖 Mocking 技巧。 - 演讲的核心目的在于分享同行经验,推动开发者自我表达与社区回馈,而非商业推广。 --- ### [02:02](https://www.youtube.com/watch?v=EZ05e7EMOLM&t=122s) 本次演讲的主线脉络 Ian Cooper 梳理了本场演讲的整体大纲。他希望重点探讨当今行业在实践 TDD 时陷入的误区,以及为什么开发团队对 TDD 的抵触情绪与日俱增。 ```mermaid flowchart TD accTitle: TDD核心问题与梳理路线 accDescr: 展示从当前TDD的痛点到回归原始原则的分析流程 subgraph Problem["当前TDD的困境"] A["社区抵触情绪上升"] B["测试代码庞大、昂贵且脆弱"] end subgraph Reboot["重温 Kent Beck 的初衷"] C["红-绿-重构循环 (Red-Green-Refactor)"] D["区分被测行为与实现细节"] end subgraph Solution["改进架构与实践"] E["六边形架构 (Ports and Adapters)"] F["重新审视测试金字塔与 Mock 的边界"] end Problem --> Reboot Reboot --> Solution ``` 他计划带领大家重温 Kent Beck 最初倡导的 TDD 原则,重新审视“红-绿-重构”(Red-Green-Refactor)、什么是“绿灯代码”以及重构的本质定义,从而挖掘出我们究竟在哪个环节误入歧途,导致了如今那些痛苦的反模式做法。最后,他还会结合六边形架构(Ports and Adapters)、Given-When-Then、BDD 风格的行为驱动理念以及如何恰当使用 Mock 进行深入剖析。 --- ### [02:53](https://www.youtube.com/watch?v=EZ05e7EMOLM&t=173s) 十年 TDD 之路与漫长周期的反思 Ian Cooper 回忆道,自己从 2004 年左右在伦敦就开始实践 TDD。当时伦敦的.NET 社区与极限编程(XP)圈子走得很近,彼此思想交流频繁,因此他们很早就成了 TDD 的先锋实践者。到了 2013 年他第一次做这篇演讲时,他已经积累了近十年的 TDD 经验。正是长期维护那些由 TDD 产出的测试套件,让他痛定思痛,意识到现有做法存在巨大漏洞。 他谈到了一个重要的职业心得:作为软件架构师,他倾向于在一家公司待足够长的时间,以至于能够亲眼目睹自己当年犯下的错误反噬自身。Ian Cooper 感慨道:“这是一件非常令人谦卑的事情,但这也正是你从经验中汲取巨大价值的方式。”如果一个人在每家公司只待两年,往往刚做完一个系统,自豪地觉得自己交付了一项了不起的成果便拍拍屁股走人,永远不知道后来的维护者对自己留下的设计有多么咬牙切齿。唯有留下来长期维护,才会发现自己写出的大规模测试套件在后续维护中是多么笨重、昂贵且难以维系。 --- ### [04:24](https://www.youtube.com/watch?v=EZ05e7EMOLM&t=264s) 资深工程师的质疑与初期的布道盲目 在 2005 到 2006 年前后,刚刚“喝了 TDD 迷魂汤”(drunk the Kool-Aid)的 Ian Cooper 处于极度热衷的状态。面对外界对 TDD 的怀疑与抗拒,他当时的反应充满优越感,总觉得那些反对者“根本没开窍”,甚至认为 TDD 是过去十年里最伟大的软件开发方法演进。 然而,令他感到困惑的是,反对队伍中不乏极其聪明、令他由衷敬佩的高水平工程师。他们写出的代码质量极高,却依旧坦言 TDD 不是正确的软件开发方式。每当遇到这种阻力,Ian Cooper 便会像教小孩一样,一字一句缓慢而固执地背诵 TDD 教条,以为只要重复得够多,对方脑子里的灯泡总会亮起来。直到有一天,一位他非常欣赏的优秀开发者对他说:“初级开发者确实需要 TDD,它能帮助他们写出好代码;但我相信你肯定不会真指望团队里的资深工程师也去搞 TDD 吧?”这番话彻底击中了他,让他开始反思那些顶尖工程师抗拒 TDD 的背后根源究竟是什么。 --- ### [06:04](https://www.youtube.com/watch?v=EZ05e7EMOLM&t=364s) 代码量失衡与开发速度的现实折损 Ian Cooper 讲述了一个令团队无比自豪的成功案例:他们曾开发过一个纯后端服务(不包含易变的 UI),上线运行整整一年没有报告过任何生产环境缺陷(zero defects)。团队当时一致认为这完全归功于 TDD,因为他们覆盖了所有业务场景,用无可辩驳的证据证明了代码的正确性。 然而,在审视该项目的代码库时,他发现了一个诡异的现象:测试代码量是生产代码量的整整三倍。为此他去推特(他戏称为“宇宙万物真理与智慧的源泉”)求证,结果同行们纷纷附和表示:“两到三倍的测试代码完全属于常态。”这种代码比例让他深感不安。 随之而来的就是项目经理(PM)的灵魂拷问:“各位,我们能不能跳过写测试?这样我们进入市场的速度会快得多。我知道你们追求优秀的工程素养,但写这些测试花的时间实在太长了。我们直接发布代码不行吗?就算生产环境有一点 Bug 也没什么大不了的吧?”面对这种质疑,开发人员搬出了标准教条回怼,声称测试就是写代码不可分割的一部分,“不让写测试就等于让我们停止写代码”。虽然成功挡回了产品经理的要求,但 Ian Cooper 也不得不承认一个残酷的现实: > “从实际情况来看,因为编写这些测试,我们的开发速度确实比以往慢了 20% 到 50%。” --- ### [08:17](https://www.youtube.com/watch?v=EZ05e7EMOLM&t=497s) 脆弱的 Mock 与重构承诺的破灭 更致命的痛点出现在系统维护期。面对庞大的测试套件,团队只要想变动一点实现,就会引发大面积的测试红灯崩溃。而且这种变动往往根本不是在修改业务逻辑,而仅仅是在做“重构”——例如随着新功能加入,既有实现的组织形态不够合理,开发者想通过重构调整代码架构,让其更易于维护、更具可读性。在系统的外部行为完全没变的情况下,测试却崩了一大片。 深入排查后发现,崩溃最严重的恰恰是那些充斥着大量 Mock 的测试。因为 Mock 强行把底层的实现细节锁死了:它巨细靡遗地规定了“这个方法必须先被调用两次,然后再调用那个方法,并且传入特定的参数”。一旦重构调整了内部协作逻辑,这些依赖 Mock 的测试立刻报废,开发者不得不花费海量精力去修改每一个 Mock 的断言逻辑。 这与 TDD 当初许下的神圣诺言形成了荒谬的对立。行业先驱原本承诺,TDD 能够赋予我们重构的信心,避免系统沦为“大泥球”(big ball of mud)或过早腐化;测试套件本应是重构的安全网,如今却成了阻碍代码变更的最大绊脚石。每次稍微改动实现细节,就必须修复成百上千个测试。Ian Cooper 尖锐地指出了这种脱节:“Martin Fowler 和 Kent Beck 告诉我们,重构是指在不破坏测试的前提下改变内部实现细节;然而我们的测试却无时无刻不在崩溃。”到了 2009 至 2010 年左右,业界甚至开始涌现出一批新的开发方法论,公开打出旗号劝诫开发者不要使用 TDD,理由正是“TDD 严重拖慢了研发交付的速度”。 ### [10:36](https://www.youtube.com/watch?v=EZ05e7EMOLM&t=636s) 极客无政府主义、精益创业与“胶带程序员”的崛起 随着软件行业的发展,一种被称为“程序员无政府主义”(Programmer Anarchy)的思想开始抬头。Ian Cooper 提到 Fred George 提出了类似于电影《摇滚万万岁》(Spinal Tap)式的极端观点:“如果极限编程(XP)是好的,那我们就把音量旋钮直接拧到 11——来搞‘极限极限编程’”。这种模式的主张非常激进:裁掉所有非软件开发人员,干掉 QA,干掉产品经理,只让开发人员直接与客户沟通。他们编写极小的代码片段,也就是最初意义上的“微服务”(Microservices),规模仅有区区 100 行左右代码。由于代码体量极其微小,他们认为开发人员肯定能写对,因此根本不需要任何测试;代码写完直接丢到生产环境,一旦需要变更,直接扔掉重写。与此呼应的还有“刺探与稳定”(Spike and Stabilize),以及成为加州硅谷初创企业圣经的“精益软件开发”(Lean Software Development)。精益思想告诫开发者:“先别做测试,尽可能快地把软件推到客户面前以获取真实反馈,因为你构建的可能从头到尾就是错误的东西,迟早要被扔掉;TDD 是你在规模化扩张阶段才需要做的事,现阶段写测试会严重拖垮你的速度。” 与这种思潮伴生的是每个团队中都存在的“胶带程序员”(Duct-tape Programmer)。业务方和产品团队极度偏爱这种人,因为每当有新需求,只要交给“Bob”,Bob 就能以惊人的速度把功能推上线。然而,团队里的其他工程师往往痛苦不堪,甚至咬牙切齿:Bob 的工程实践一塌糊涂,写出来的全是垃圾代码,其他人不得不耗费大量的时间替他重构、修补和维护。更令人沮丧的是,产品经理根本看不到背后的技术债务,他们盛赞 Bob 思维敏捷、执行力强;而当规范的开发者还在恪守严格的工程实践、慢吞吞地完善测试套件时,Bob 的代码早就已经在生产环境中跑起来了。这让坚守最佳实践的团队在交付信誉上彻底输给了“胶带工程师”。 Ian Cooper 这样描述业务方对这种开发者的偏爱: > “The guy's going Bob fantastic he's so receptive to our ideas he's pushes stuff out the rest are you going but it's so hard to maintain the Bob's code is terrible right but they love Bob because Bob delivers for them you're still writing it for your test suite and bob has shipped the code and it's running okay we had a few of those and needs to be really annoying you should be really annoying that we were still doing they're doing our best engineering practices and these guys were shipping code and there's waffle code but they were shipping right and so they won the credibility not us.” --- ### [13:21](https://www.youtube.com/watch?v=EZ05e7EMOLM&t=801s) 脆弱且充斥着 Mock 的测试:失去意义的套件 开发者在维护遗留测试套件时陷入了另一种深重挫败:由于测试过度绑定了实现细节,一旦重构内部实现,大量测试便纷纷变红报错。当开发者停下来排查失败时,却困惑地发现自己根本搞不懂测试到底在测什么,也无法判断这个红脸到底是揭示了系统缺陷,还是只是代码变动带来的假警报。例如某些测试只是把两个数字加在一起,得出一个匪夷所思的结果,开发者既无法理解最初为什么会通过,也无法理解为什么一旦重构就变红。 更严重的问题是测试套件中泛滥成灾的 Mock 对象。很多测试里密密麻麻塞满了 Mock,以至于当开发者仔细审视测试代码时,会震惊地发现整段测试里根本没有任何真正的业务代码在运行,被测的完全就是一堆 Mock 本身。面对这些既不提供有效信息、又无法保障业务逻辑的测试,开发者最后被逼得抓狂,唯一理性的选择竟然是直接将这行不知所云的测试代码删掉。 Ian Cooper 回忆起排查测试时的典型困境: > “They don't have this experience you go back and find the test is red and you're looking at it in your going I have no idea what this test is for and I have no idea whether the fact that this red is good or bad at this point in time.” --- ### [14:38](https://www.youtube.com/watch?v=EZ05e7EMOLM&t=878s) FitNesse 与验收测试的幻灭:无人问津的“活文档” 为了消除业务与技术之间的鸿沟,Ian Cooper 的团队曾引入了 FitNesse(后续的 SpecFlow 和 Cucumber 也是同类工具)。他们的构想十分美好:用自然英语编写可执行的规范,在 HTML 表格中清晰呈现高层测试,客户只要打开网页、点一下按钮,看到满屏绿色,就能完全确信系统正在严格按照他们的需求运转。然而在实际落地中,这套流程沦为了一场灾难。 在迭代开发初期,这些端到端验收测试几乎必然是全红的,并且会在整个迭代的大部分时间里持续保持红色,直到最终完成全部功能才会变绿。这就造成了一个致命的认知盲区:开发者无法分清测试变红是因为“功能尚未实现”,还是因为“已有的功能被改坏了”。长此以往,大家在测试变红时普遍默认是前者,导致真正的系统破坏长期被掩盖,测试套件大部分时间处于事实上的损坏状态。 而最具有讽刺意味的是,开发团队费尽心机打造的“面向客户的文档”,业务方根本连看都不看一眼。即便开发者主动把客户按在电脑屏幕前展示,客户也只会推脱“改天再聊”,转身就走。 ```mermaid flowchart TD accTitle: FitNesse 测试落地失效循环 accDescr: 展示 FitNesse 验收测试在迭代中由于长期变红而失去反馈价值并被团队边缘化的恶性循环 subgraph Iteration["迭代开发生命周期"] A["编写 FitNesse 自然语言规范"] --> B["迭代前半段:测试全红<br/>(功能尚未实现)"] B --> C{"测试持续变红"} C -->|"假设未实现,实际已破坏"| D["真实 Bug 被掩盖<br/>测试套件长期处于损坏状态"] C -->|"迭代末期开发完成"| E["期望:测试全部转绿"] end subgraph Feedback["反馈与价值检验"] D --> F["代码签入受阻<br/>测试缓慢打断心流"] E --> G["向业务客户展示 HTML 报告"] G --> H["客户毫无兴趣、拒绝查阅<br/>('改天有空再聊')"] end ``` Ian Cooper 总结了客户对这些测试页面的冷淡态度: > “And they were completely uninterested in seeing these test Suites that we written to prove to them that the code works correctly for and the check in dollars became more complicated because as well as passing a developer tests you had to run all these fitness test Suites before we allowed you to check your tests into the code base.” --- ### [16:56](https://www.youtube.com/watch?v=EZ05e7EMOLM&t=1016s) 心流被断与“捉鬼定罪”:团队对测试的全面排斥 FitNesse 带来的不仅是虚妄的客户满意度,更是开发效能的雪崩。由于团队要求在代码签入前必须完整运行这套缓慢的测试套件,开发人员不得不常常暂停编码去冲咖啡。这种强制性的流程阻断破坏了经典的“20 分钟编程心流”,开发者很难再重新找回专注于代码的状态,导致整个工程效率极度低下。 为了应对缓慢而脆弱的流程,开发者开始学会“钻空子”:无视全红的测试,抱着侥幸心理继续签入代码,甚至依靠“合理推诿”(Plausible Deniability)来互相甩锅,坚称“这次变红肯定不是我的提交造成的,是 Dave 的改动破坏的”。团队甚至畸形地设立了一个专职角色,专门负责“捉鬼”——排查是谁把测试搞砸了,并强迫当事人去修复。当一个团队必须引入专职角色来对测试变红追责时,恰恰证明了整个团队已经彻底对测试失去了信任与关切。Ian Cooper 指出,一个好的工程实践如果有效,团队会自发支持它;若团队长期抗拒并抱怨感受不到价值,那就说明现有的做法一定是哪里出了严重问题。 Ian Cooper 犀利地反思了团队设立“追责机制”背后的荒谬: > “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 at being a red the whole time really the developers couldn't see any value they constantly resisted write-ins his tests and one thing I've learned over time is that you know if an idea actually really works after a while your team will be quite happy behind it and if you get some supporters of it if your team is constantly pushing back saying we e doesn't they don't see where I'm getting from this I don't see the value in the test that you're forcing me to write and maybe you need to think again about what you're doing.” --- ### [19:01](https://www.youtube.com/watch?v=EZ05e7EMOLM&t=1141s) DHH 发难与“TDD 已死”论战的爆发 大约在该演讲初版分享的一年后,Ruby on Rails 的创始人 DHH(David Heinemeier Hansson)发表了轰动业界的博客《TDD 已死,测试长存》(TDD is dead. Long live testing.)。DHH 的核心痛点在于,主流 TDD 拥趸总是不依不饶地指责 Rails“做错了”,理由是 Rails 的架构对所谓的“测试友好框架”不够顺从。Rails 广泛采用 Active Record 模式,数据持久化方法直接附着在业务对象上(例如 User 对象自带持久化方法)。TDD 狂热者批评这种设计无法将领域模型与持久化及测试框架彻底解耦。 此外,DHH 过去编写单元测试时很自然地会直接读写数据库,如今却被 TDD 原教旨主义者痛斥为违反规则。DHH 抱怨自己最初其实很喜欢 TDD,但随着社区愈发教条,他无论怎么写都会被指责“姿势不对”。失望之下,DHH 宣布彻底放弃 TDD:虽然他依然会编写测试,但他拒绝再忍受 TDD 的教条束缚。这一发难在软件工程界掀起了轩然大波,随后甚至引来了 Kent Beck 本人写下了一篇略带讽刺意味的回应文章《TDD 安息吧》(RIP TDD),正式拉开了对 TDD 真正本质的全面反思与论战。 DHH 对当时苛刻的 TDD 规则表达了强烈的反弹: > “I used to like TDD when it first came out but now I'm constantly being told I'm doing TDD wrong then I'm just giving up on GED you know TDD as far as I'm concerned doesn't work I'm still gonna do some testing but I'm quitting on TDD.” ### [20:24](https://www.youtube.com/watch?v=EZ05e7EMOLM&t=1224s) 乔治·贝斯特轶事与重访 Kent Beck 的初衷 Ian Cooper 提到,在 DHH 对外发表对现代 TDD 的批评后,Martin Fowler 曾组织并主持了一系列 Kent Beck 与 DHH 之间的公开对谈。DHH 曾在挪威 NDC 大会上听过 Ian Cooper 本次演讲的早期版本,并表示两人的观点完全不谋而合;而那场三人辩论的最终结论,也与本次演讲的主旨高度一致。面对社区中对于 TDD 的诸多怨言与挫败感,核心问题究竟是什么?TDD 到底是在哪里走偏了?为了生动描摹这种现状,Ian 讲了一个关于 20 世纪 70 年代北爱尔兰著名足球明星乔治·贝斯特(George Best)的经典笑话: > 酒店的行李员推门走进乔治·贝斯特的套房,只见贝斯特赤身裸体躺在床上,身旁环绕着几位同样不着寸缕的超级名模。行李员推着准备送给他们的鱼子酱和昂贵香槟,低头看着床上的贝斯特,忍不住感叹道:“乔治,这一切究竟是在哪里出了差错?” Ian 借这个典故自嘲并表明立场:他自己始终是 TDD 的忠实拥护者,之所以问出“哪里走偏了”,并不是要全盘否定它,而是要重新审视其发展历程。他提出的核心论点是:**我们当下在 TDD 中遇到的所有困境,其实早在 Kent Beck 最初阐释 TDD 时就已有现成的解法。** 遗憾的是,整个行业在此后不断给 TDD 堆砌各种各样衍生出的模式与实践,反而远离了 Kent Beck 最初的设计意图。当 Ian 自己在实践中陷入挣扎时,他重新翻读了 Kent Beck 于 2002 年出版的经典著作《Test-Driven Development: By Example》,震惊地发现书中早已囊括并解答了他所经历的绝大多数难题(甚至包括被很多人误以为是后期才发明的 Mock 技术)。 ```mermaid flowchart TD accTitle: TDD的历史偏差与回归原始理念 accDescr: 对比Kent Beck原始TDD理念与现代过度装饰后走偏的TDD实践 subgraph Original ["Kent Beck 原始 TDD 理念"] A["需求与行为驱动"] --> B["模块公开接口(API / Facade)"] B --> C["支持无痛重构与信息隐藏"] end subgraph Modern ["走偏的现代 TDD 实践"] D["方法触发测试(Class/Method)"] --> E["与实现细节深度绑定"] E --> F["大量使用 Mock 导致脆弱脆弱测试"] end Original -.->|"被后期模式与误解带偏"| Modern ``` --- ### [24:01](https://www.youtube.com/watch?v=EZ05e7EMOLM&t=1441s) 触发测试的根源:测试行为,而非实现细节 对于实践 TDD 而言,最关键的一条准则被无数人严重忽视:**“测试行为,不要测试实现细节。”** Ian Cooper 强调,这是当代开发者在实践 TDD 时犯下的最普遍错误。在许多团队日常的现代 TDD 流程中,典型的操作逻辑是:“我准备在类上编写一个新方法,于是在写这个方法之前先写一个单元测试,用这个测试来判定该方法执行成功还是失败。”换句话说,开发者将“为类添加新方法”视作了编写测试的触发器。 Ian 明确指出,这种以方法为粒度的驱动方式根本不是 TDD 的灵魂,甚至完全南辕北辙: > “在 TDD 中,创建新测试的触发条件只有一个:你有一项需要实现的业务需求。仅此而已,别无其他。” 编写测试的动力应当源于软件必须完成的业务目标。例如,“当向客户的银行账户存入一笔金额时,软件需要更新其余额”,或者“软件需要允许用户向其个人文件库中上传一个新文件”。这才是驱动 TDD 测试编写的真实需求。相反,写一个测试去验证诸如“`foo()` 方法应当接收两个数字并返回它们的乘积”,纯粹是在测试具体的内部实现细节。除非系统本身就是一个计算器程序,否则没有任何一条实际的业务需求会直接定义这种底层函数的入参和返回值;过早陷入这种微观函数的测试,只会让测试代码沦为易碎的负担。 - 错误触发器:计划在类中添加或修改某一个内部方法或函数 - 正确触发器:系统接收到一项全新的业务需求或用例(User Story / Use Case) - 目标分层:需求处于顶层业务契约,而底层辅助方法属于极易波动的实现细节 --- ### [26:02](https://www.youtube.com/watch?v=EZ05e7EMOLM&t=1562s) 重新定义 API:软件与外部世界的稳定契约 为了摆脱对实现细节的纠缠,开发者必须将测试焦点转移到软件的 API 上。Ian Cooper 澄清道,在当今的技术语境下,只要一提到“API”,许多人便立刻联想到 RESTful、HTTP、JSON 或者 GraphQL 等网络传输接口;但他在这里所说的 API 具有更本质的软件工程含义——它是**模块公开暴露的表面积,是软件向外部世界许下的契约**。 从经典软件工程的模块化理论来看,每个模块都应该具备明确的信息隐藏(Information Hiding)与封装(Encapsulation)机制: 1. **公开导出部分(Exports / Facade):** 供外部调用者或其它模块依赖调用的公开契约。这部分代表了相对稳定的业务功能承诺,通常不会频繁剧烈变动。 2. **内部实现细节(Internal Implementation):** 隐藏在模块外壳内部的具体算法、辅助类与数据结构。这是最不稳定的部分,开发者随时可能想到更好的算法或设计来对其进行优化和重构。 Ian 告诫开发者,TDD 的测试必须且只能针对这个稳定的公开契约(API)进行编写,而绝对不要将测试渗透到模块内部的实现细节之中。 --- ### [28:00](https://www.youtube.com/watch?v=EZ05e7EMOLM&t=1680s) SUT 不是单个类:反思过度 Mock 与“单元”的真义 敏捷开发中通常使用用户故事(User Story)或用例来描述需求,并常借用 Given-When-Then 模式(例如:“假设账户内已有 100 英镑,当我存入 100 英镑时,账户总额应为 200 英镑”)。在这个过程中,测试关注的是向系统输入操作并观察其行为反馈。Ian 尖锐地批评了现代单元测试中泛滥的教条主义: > “被测系统(System Under Test)并不是一个类。太多的 TDD 实践把精力过度集中在为单个类编写测试上了。” 在经典测试理论诞生的时代,“单元测试”指代的是对一个完整模块的黑盒测试,开发者只需要与模块对外暴露的门面(Facade)进行交互。然而,随着面向对象语言(如 C#、Java)的普及,行业逐渐将“单元”窄化为单一的“类”,并演变成“必须把这个类与其所有依赖彻底隔离”的僵化规则。这种偏执引发了一连串恶果: - **过度 Mock 滥用:** 为了孤立单一类,开发者为所有周边协作对象编写大量的 Mock 和调用断言。 - **过度指定的测试(Overspecified Tests):** 测试代码通过 Mock 详尽地知道了类内部的方法调用顺序、参数传递与内部协同,导致测试与实现细节形成了 100% 的死锁绑定。 - **重构能力丧失:** 内部一旦有任何微调,哪怕对外行为毫无改变,所有 Mock 验证也会瞬间全部挂掉,使原本为了保障重构的测试变成了阻止重构的最大障碍。 ```mermaid flowchart LR accTitle: 模块黑盒测试与过度Mock类测试对比 accDescr: 对比针对模块Facade的黑盒测试与针对单一类的过度Mock白盒测试 subgraph GoodPractice ["提倡的做法:模块级黑盒测试"] T1["业务测试用例"] -->|"调用稳定API"| F["模块门面 (Facade)"] F --> I1["内部实现类 A"] F --> I2["内部实现类 B"] style F fill:#e1f5fe,stroke:#0288d1 end subgraph AntiPattern ["走偏的做法:类级白盒测试与过度 Mock"] T2["类测试用例"] -->|"测试具体类"| C1["目标类"] T2 -.->|"大量 Mock 验证细节"| M1["Mock 依赖 1"] T2 -.->|"深度耦合调用链路"| M2["Mock 依赖 2"] style C1 fill:#ffebee,stroke:#e53935 end ``` Ian 总结道,即使在强面向对象的静态语言中,测试驱动的入口虽然表现为一个类,但那个类应当扮演的是门面(Facade)或命令处理器(Command Handler)的角色,代表整个模块的边界。在这个架构下,**重构(Refactoring)** 是帮助开发者理清“哪些属于必须测试的对外契约”与“哪些属于无需测试的内部细节”的关键步骤。即便有时候为了排查极度复杂的算法逻辑,开发者偶尔需要探入内部细节编写探针测试,也应当在逻辑验证完成后坚决删除这些测试(即“豁免条款”)。最后,Ian 顺带提及了 Dan North 提出“行为驱动开发”(BDD)的初衷——Dan 当时之所以构想 BDD,正是因为观察到了业界在理解 TDD 的“测试”概念时已经产生了严重的偏差。 ### [31:58](https://www.youtube.com/watch?v=EZ05e7EMOLM&t=1918s) 行为驱动与 TDD 的核心初衷 Ian Cooper 指出,人们在推行测试驱动开发(TDD)时常常陷入极大的困惑,其根源恰恰在于“测试”(test)这个词。这个词误导了大家,而 Dan North 很早就洞察到了这一点:如果把“测试”换成“行为”(behavior)——即通过定义系统的行为来推进系统开发,大家立刻就能把事情做对。早在 2005 年左右,Dan North 就指出了同样的问题:人们之所以误解 Kent Beck 的原意,很大程度上是因为 Kent Beck 在书中使用了一些传统测试术语,比如“test”和“unit test”;但事实上,Kent Beck 在书中只提过寥寥数次“unit test”,他更频繁使用的词汇是“开发者测试”(developer test)。 这两位先驱所对抗的其实是同一个顽疾:开发者总是习惯于“我需要测试这个方法”,而实际上他们本应说的是“系统中存在某种行为,我希望用代码来验证该行为在系统中确实有效”。Ian Cooper 强调这绝非 Dan North 后来凭空发明的概念,Kent Beck 最初在书中讨论的就是“行为”——能够证明程序计算正确、生成报表正确的行为。例如,“将两种不同货币的金额相加,并根据给定的汇率换算出结果”,或者“用每股价格乘以股票数量以获得总金额”。测试的目标是验证这些业务需求行为,绝非仅仅测试某个类上的某个方法。测试代码应该扮演该代码的第一个外部调用者,用来设计和检验对消费者而言直观好用的公开 API。 > “People misunderstood what Kent was talking about probably because Kent used classical testing terminology like test, unit test, which he mentioned just a couple of times in the book; generally the phrase Kent Beck uses is developer test, right, not unit testing, developer tests.” —— Ian Cooper - Dan North 提出转向以“行为”为核心的思考方式,使系统开发流程更加明确且不易走偏。 - Kent Beck 关注的 TDD 需求案例包括多币种加法与股票总价计算,皆为具体的业务功能需求而非类级别的内部实现。 - 测试作为系统的首个外部消费者,其职责是探索与定义易用的公共 API(Public Facing API)。 --- ### [35:01](https://www.youtube.com/watch?v=EZ05e7EMOLM&t=2101s) 隔离的单元究竟是什么 在经典单元测试术语的演进中,出现了一个影响深远的误解。在传统测试理论中,模块(例如.NET 中的一个程序集 Assembly)通常是独立测试的单元;多个模块协作构成集成测试(Integration Test),所有模块联合则构成系统测试(System Test)。Kent Beck 在提出单元测试概念时,其实际指的是对一个模块的验证。然而,许多开发者却将“单元”(unit)生搬硬套成面向对象中的“类”(class),从而衍生出一套对 TDD 实践造成毁灭性打击的做法:把待测类与一切隔绝,强迫对该类的所有依赖进行全面 Mock。 Ian Cooper 严肃指正,这种做法完全颠倒了概念。在 Kent Beck 的定义中,**隔离的单元是测试用例本身,而不是被测试的代码(The unit of isolation is the test, not the thing under test)**。这意味着测试套件能够作为一个整体并发或按任意顺序运行,且任何一个测试用例的执行都不会对其他测试造成副作用。 ```mermaid flowchart TD accTitle: Unit of Isolation Comparison accDescr: Diagram contrasting the correct TDD isolation model (test-level) with the flawed anti-pattern (class-level with excessive mocks). subgraph AntiPattern["常见错误误区:隔离被测类"] direction TB TestA["单元测试用例"] -->|"测试与断言"| TargetClass["单个待测类 (Class Under Test)"] TargetClass -.->|"必须强制隔离"| Mock1["Mock 依赖 1"] TargetClass -.->|"必须强制隔离"| Mock2["Mock 依赖 2"] end subgraph CorrectPattern["Kent Beck 原意:隔离测试本身"] direction TB Suite["测试套件 (Test Suite)"] Suite --> Test1["测试用例 1 (独立运行/无状态污染)"] Suite --> Test2["测试用例 2 (独立运行/无状态污染)"] Test1 --> ModuleA["完整模块协作网络 / 真实代码与状态"] Test2 --> ModuleA end ``` 避免频繁访问数据库或文件系统并非因为要“隔离类”,而是为了避免“共享夹具”(Shared Fixture)带来的状态污染——如果前一个测试写入的数据污染了后一个测试读取的结果,测试间的独立性就会被打破。此外,测试必须保持秒级的极速反馈,开发者才可能使用它们;如果真实持久化太慢,可以换成能够快速重置的内存数据库(In-memory database)。但只要没有共享夹具带来的交叉影响,单元测试直接调用数据库或文件系统在 Kent Beck 的体系中是完全允许且非常普遍的。 > “The unit of isolation is the test, not the thing under test.” —— Ian Cooper - 许多人坚信必须 Mock 掉类的一切依赖才能称为“纯粹的单元测试”,但这属于对概念的彻底曲解。 - 隔离驱动力来自两个工程诉求:消除测试之间的互相干扰(测试用例互相独立)以及保证测试套件的执行速度(获取快速反馈)。 - 只要不存在跨测试的状态污染,单元测试中操作真实的文件系统或数据库完全符合 TDD 原理。 --- ### [38:28](https://www.youtube.com/watch?v=EZ05e7EMOLM&t=2308s) 方法级测试与过度 Mock 的危害 当测试聚焦于孤立的方法而非业务行为时,测试代码就会充斥着对方法调用协议与内部实现细节的刻画。日后开发者回看这些测试时,往往一头雾水,完全看不懂这段测试到底想表达什么业务意图。反之,如果测试绑定的是系统行为,意图就一目了然:它明确断言了系统在某场景下的行为特征。当代码发生演进导致该测试变红时,开发者能够清晰判断:这项既有行为应当继续成立并修复,还是业务需求已经变更因而需要同步调整测试。 过度使用 Mock 造成的灾难在此尤为致命。如果开发者把测试限定在孤立的类上,并对周围的一切协作对象配置繁琐的 Mock 调用期望,测试代码就不可避免地深入了解了代码的内部实现细节。一旦系统进行重构或内部交互略微调整,所有 Mock 的断言都会瞬间崩塌并飘红。这使得测试与实现细节形成了致命的高耦合(Coupled to implementation details)——任何一方的改动都会强迫另一方做出修改,重构的安全性与自由度荡然无存。 > “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.” —— Ian Cooper - 基于孤立方法的测试缺乏业务语境,可读性与可维护性极差。 - 基于行为的测试能够在系统行为发生改变时给出明确的因果提示。 - 对内部实现和 Mock 调用链细节的过度依赖,直接扼杀了安全重构的可能性。 --- ### [39:57](https://www.youtube.com/watch?v=EZ05e7EMOLM&t=2397s) 重归 Red-Green-Refactor:拥抱“胶带程序员” 针对如何走出上述误区,Ian Cooper 带领大家重新梳理了经典的“红-绿-重构”(Red-Green-Refactor)三部曲。当他向现场听众提问时,几乎所有人都认为自己理解这个循环,也有不少人会写出通不过的红测试,但真正坚持贯彻重构步骤的人却寥寥无几。 首先是“红”(Red)阶段。之所以必须先看到失败,是因为“测试本身是没有测试来检验的”(There are no tests for tests)。开发者必须证明:在缺乏正确实现代码的情况下,该测试确实会挂掉。否则,一旦写出一个永远为真的假测试,后续编写的任何业务逻辑都毫无验证价值。 其次是“绿”(Green)阶段。该阶段的核心法则是:**以最快的速度让测试通过,在这个过程中可以犯下任何架构设计上的“罪恶”**。这个阶段的目标仅仅是搞清楚如何解决当前问题并实现该行为,开发者应该直接写出最平铺直叙的事务脚本(Transaction Script),一行接一行地写代码,绝对不要在此时过早引入精美的类划分或设计模式。甚至可以直接去 Stack Overflow 上复制粘贴代码。在这个阶段,开发者必须抛弃学院派的工程洁癖,完全化身为团队里那种为了求快、用胶带修修补补的“胶带程序员”(Duct tape programmer),用最粗暴、最快的方式抢先跑通逻辑并追赶进度。 > “Make the test work quickly, committing any sins basically necessary in the process.” —— Ian Cooper - **红(Red)**:编写测试,确保代码可以编译,但必须运行失败;证明测试有效且能捕捉问题。 - **绿(Green)**:不择手段让测试以最快速度变绿,允许写出丑陋、未良好工程化的代码,优先搞清实现路径。 - **重构(Refactor)**:在绿灯的保护网下,才开始消除坏味道、提取良好设计和架构。 ### [43:23](https://www.youtube.com/watch?v=EZ05e7EMOLM&t=2603s) 重构的核心原则:在重构阶段绝不编写新测试 Ian Cooper 强调,在通过测试的瞬间,“速度压倒设计(speed trumps design)”,目标是用最快的速度让测试变绿。之所以不能在一开始就兼顾优雅设计,是因为人类很难同时做好两件事:你不可能一边理解业务问题的解决方案,一边把代码工程架构设计得尽善尽美。如果你试图同时做这两件事,要么会过度设计出远超当前测试所需复杂度的代码,要么就会陷入“分析瘫痪(analysis paralysis)”。 真正的代码清理发生在接下来的重构(Refactoring)阶段。重构的本质是一系列不改变系统可观察行为的安全步骤,它只改变内部实现。在重构阶段最关键的铁律是:**绝对不要编写新的测试**。测试验证的是行为,比如“将两种不同货币的金额相加得到正确的汇总结果”。只要最初的测试已经锁定了这一行为,无论你在内部如何提取类、调整内部结构,原有的测试都已经完整覆盖了代码。 ```mermaid flowchart TD accTitle: TDD两步走与重构阶段边界 accDescr: 展示从快速编码到安全重构的流程,强调在重构时不能编写新测试,只能依靠既有测试防护。 subgraph Solve["阶段一:快速实现"] A["编写测试并使其通过"] -->|"速度压倒设计"| B["产出可行解(允许简陋实现)"] end subgraph Refactor["阶段二:安全重构"] B --> C["消除重复 / 识别代码坏味道"] C --> D["引入设计模式 / 提取内部类"] D -->|"行为保持不变"| E["运行既有测试进行验证"] end classDef warning fill:#f96,stroke:#333,stroke-width:2px; D -.->|"严禁此时编写新测试!"| F["为内部类写单测(错误做法)"]:::warning ``` - 演讲者引用 Kent Beck 的观点:在追求测试变绿的那一刻,“Speed trumps design. I want to get there as fast as I can.” - Ian Cooper 总结核心矛盾:“You can't both understand the solution to the problem and engineer the code, right? One of two things will go wrong: you will either over-engineer your solution beyond what the test actually requires, or you'll get some kind of analysis paralysis.” - 如果在重构阶段引入了新的条件分支(conditionals)或投机性代码,代码覆盖率工具会发出警报;条件分支通常意味着引入了新的业务行为,而新的行为必须回到 TDD 起点,通过编写新的需求测试来驱动,绝不能在重构阶段顺手添加。 --- ### [46:07](https://www.youtube.com/watch?v=EZ05e7EMOLM&t=2767s) 解耦实现细节:保护内部可见性与消除脆弱测试 不为重构中产生的类编写新测试,带来两个巨大的好处:第一,测试数量更少,开发速度更快,工程团队不会因为背负海量无用测试而在交付节奏上落后于野路子的“胶带程序员(duct-tape programmer)”;第二,测试不再与实现细节发生耦合。例如在 C# 或 Java 中,提取出来的新类通常应该标记为 `internal` 或包级私有,永远不对外暴露。 只要测试只与稳定的公共契约(Public API)绑定,内部细节无论如何重构,测试都不会失败。很多人在重构时发现某个内部类没有测试,就顺手把它改成 `public`,或者在.NET 中使用 `[InternalsVisibleTo]` 特性把内部类暴露给测试程序集,Ian Cooper 直言这是绝对错误的倒退。把内部细节暴露给测试,不仅破坏了信息隐藏原则,还会让测试陷入脆弱的泥潭。 - Ian Cooper 直击痛点:“Dependencies [are] the key problem in software, right? Coupling is [a] worse enemy for you than DRY. Coupling is what kills all software.” - 谈到代码可见性与测试的隔离,Ian 坚决反对为了测试破坏封装:“Do not make these public to test. If you find yourself making things public in order to get it under test by implementation details, ask yourself if that genuinely is a part of the contract of your module? If it's not, don't.” - 针对.NET 开发者的常见坏习惯,Ian 警告:“If you are in.NET land and you use the assembly attribute `InternalsVisibleTo` and list your test assembly so that you can get your internals tested, do not do that.” - 这种解耦带来的实际成效极其显著:“We changed the whole way we did, for example, a modeling between of an aggregate without any test breaking, because the behavior hadn't changed; our choice about how to implement it had changed. So test behavior, don't test implementations.” - 如果在重构过程中发现确实需要一个新的公开类,唯一的“代码坏味道”提示是:该功能其实属于模块对外契约的另一个独立公开 API;此时应该通过 Mock 隔离该外部 API 依赖,后续再针对该公开接口单独编写功能测试。 --- ### [49:42](https://www.youtube.com/watch?v=EZ05e7EMOLM&t=2982s) 重构与设计模式的正确时机:Martin Fowler 与 Joshua Kerievsky 的洞见 Martin Fowler 的经典著作《重构》(*Refactoring*)出版于 Kent Beck 的 TDD 专著之前,书中详细列举了诸如发散式变化、依恋情结(Feature Envy)等代码坏味道,并给出了安全的重构手法。重构的定义本身就是只改变内部实现而不改变外部契约。Ian Cooper 以 DevExpress 和 ReSharper 的重构工具为例,早期 DevExpress 的工具严格禁止重构公开类上的公共方法,因为更改公开契约属于破环性变更(Breaking Change),根本不属于“重构”的范畴;后来虽然妥协加上了“不安全重构(Unsafe Refactoring)”按钮,也是迫于市场竞争压力。 与此同时,Joshua Kerievsky 在《重构与模式》(*Refactoring to Patterns*)中指出了软件开发中普遍存在的“模式狂热(Pattern Happy)”:很多开发者一上来就到处套用设计模式,结果在错误的语境下导致系统严重过度复杂化。Kerievsky 与 Kent Beck 的共同观点是:**应用设计模式的最佳时机恰恰是在重构阶段**。因为只有当你用最粗暴的方式先实现了一遍,彻底搞懂了具体问题和解法后,你才能清晰地看出来这里到底需不需要一个命令模式(Command Pattern)或模板方法模式(Template Method Pattern)。 | 维度 | 绿灯阶段(Green Phase) | 重构阶段(Refactor Phase) | |:--- |:--- |:--- | | **首要目标** | 极速通过测试,跑通业务行为 | 清理代码坏味道,消除重复,优化结构 | | **设计模式** | 严禁预先过度设计,不套用模式 | 识别具体场景后,将代码“重构到模式” | | **测试变动** | 编写并运行针对需求行为的高层测试 | **严禁编写任何新测试**,依赖既有测试验证安全 | | **可见性控制** | 满足行为即可,提取的中间代码收敛为 internal/私有 | 严格遵守信息隐藏,保持公开接口稳定 | - Ian Cooper 援引 Joshua Kerievsky 的核心理念:“Joshua Kerievsky said the ideal step for applying patterns is the refactoring step, because at this point I know what the solution is — I've written one. It's an awful solution, because I was told to make an awful solution, and at this point now I can see I can clean up by making a pattern.” - 无论是在 Fowler 的《重构》还是 Beck 最初的 TDD 著作中,重构向设计模式演进都是内生逻辑,而非后人的额外发明。 --- ### [53:07](https://www.youtube.com/watch?v=EZ05e7EMOLM&t=3187s) 传统测试倒金字塔的灾难与 UI 脆弱测试 讨论完单元测试的真正形态后,Ian Cooper 转向了系统架构维度的“端口与适配器(Ports and Adapters)”架构。在展开架构前,他剖析了多数组织中常见的一种极其不健康的“倒金字塔”测试现状:在发布软件之前,团队依赖一波规模庞大的人工手动测试,大家拿着手册拼命点点点;下面是一堆极其庞大的 Selenium UI 自动化测试套件;再往下是少量的集成测试;而最底层的开发者单元测试却寥寥无几。 Ian 斥责这种模式是一场“彻底的灾难(a bit of a disaster)”。手动测试昂贵且不可重复,而大量的端到端 UI 测试(如 Selenium)则极其脆弱。只要把系统的 UI 界面从老旧的 2008 年风格重构成现代的 2017 年风格,底层的核心业务逻辑和软件行为完全没有任何变化,整套自动化 UI 测试用例就会全军覆没,因为底层的控件、DOM 结构和组件交互方式全变了。依靠驱动脆弱的 UI 来测试系统的核心业务行为,是软件交付流程中最昂贵也是最痛苦的泥潭。 - 很多公司发布前依赖两道屏障:手动点击的人工验收大军,以及某个被称为“科学家”的专人维护的庞大 Selenium 测试用例库。 - 谈到 UI 自动化测试的脆弱性,Ian 吐槽道:“If I change my UI to give it a nice classy new look and feel that's much more, you know, 2017 rather than 2008, right? I may not be changing any other behavior of my software whatsoever... I've just cleaned up the UI, but these tools all break... because I changed the way that my widgets work.” ### [54:28](https://www.youtube.com/watch?v=EZ05e7EMOLM&t=3268s) 晨间指责游戏与测试金字塔 Ian Cooper 直言不讳地指出,糟糕的测试体系不仅跑得极慢——通常必须挂在通宵运行——而且每天早晨还会引发一场荒唐的“指责游戏”(The Blame Game)。在许多组织里,甚至会专门设立一个岗位,此人每天上班的唯一工作就是去查明“昨晚到底是谁把测试跑挂了”。Cooper 对这种现象给予了无情的嘲弄:“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.” 他自嘲道,自己之所以能在这里头头是道地指出这些问题,绝非自命聪明,而是因为他自己曾经犯过这上面所有的每一个错误。 解决这种困局的出路在于回归经典“测试金字塔”(Testing Pyramid): - 金字塔底部:占据绝大多数比例的应该是“开发者测试”(Developer Tests),由开发人员快速执行; - 金字塔顶部:只有少量的高层代码用来验证系统顶部的 UI 或各类小部件(Widgets)是否运行正常; - 金字塔中部:介于二者之间的是适量的集成测试,负责将内外两侧连接起来,但 Cooper 提醒中间层往往藏着不少“骗局”(Scam),随后会详细拆解。 --- ### [55:18](https://www.youtube.com/watch?v=EZ05e7EMOLM&t=3318s) 六边形架构(端口与适配器)与测试切入点 Cooper 引入了由 Alistair Cockburn 提出的六边形架构(Hexagonal Architecture,也被 Uncle Bob 称为整洁架构 Clean Architecture,或其他人称之的洋葱架构 Onion Architecture)。这套架构将系统划分为三大清晰层次: 1. **核心领域层(Core Domain)**:位于最中央,完全由纯粹的对象组成(POCOs、POJOs 或纯 Python 对象;Cooper 还幽默地提醒台下听众在英国别乱叫“POPO”,因为在英国俚语里那是“警察”的意思)。它们不关心任何外部技术实现,根本不知道怎么连数据库,也不知道怎么处理 Web 请求。 2. **外部适配器层(Adapters)**:位于最外层,承担所有与具体技术相关的脏活累活。驱动侧有 HTTP 适配器(Web API、Django、Sinatra、Flask 等)和 GUI 适配器;被驱动侧有数据库持久化、消息队列(RabbitMQ)以及邮件网关等。 3. **端口层(Ports)**:介于核心领域与适配器之间,就像操作系统中的端口或一扇门。HTTP 适配器通过这扇门进入领域层(通常体现为门面 Facade 层);领域层想要访问数据库或外部基础设施时,也通过由它定义的端口(如仓储 Repository 接口)跨出门外。 Cooper 强调,六边形架构中的“端口层”正是运行测试的绝对理想位置。测试代码应当直接调用端口层,绝不要去碰内部的实现细节。端口层对外接收所有进入系统的业务调用,对内接收领域发出的所有事件或持久化调用。对于外部适配器本身,它们主要由第三方库构成,根本不需要开发者编写复杂的单元测试去测适配器,集成测试只需验证适配器是否配置正确、能否顺利连接外部系统即可;而端到端系统测试则仅需极少量地确认自外向内的链路能够打通。 ```mermaid flowchart LR accTitle: Hexagonal Architecture Testing Strategy accDescr: Hexagonal architecture showing external adapters connecting via ports to the core domain, with tests targeting the port layer. subgraph Adapters["外部适配器层 (Adapters)"] HTTP["HTTP / Web API / GUI"] DB["数据库 / 消息队列 / 邮件"] end subgraph Ports["端口层 (Ports / Facade / Repositories)"] InPort["入站端口 / 门面"] OutPort["出站端口 / 仓储接口"] end subgraph Domain["核心领域层 (Domain)"] Core["POCO / POJO / 纯对象<br/>(零技术依赖,仅含纯业务逻辑)"] end Test["【开发者测试切入点】"] -.-> InPort HTTP --> InPort InPort --> Core Core --> OutPort OutPort --> DB ``` --- ### [57:59](https://www.youtube.com/watch?v=EZ05e7EMOLM&t=3479s) 切换档位:五档理论与何时删除测试 针对听众可能提出的疑问——“如果我就是想用 TDD 帮自己钻进内部细节、理清算法实现怎么办?”——Cooper 搬出了 Kent Beck 提出的隐喻:“换挡”(Shifting Gears)。TDD 就像开一辆拥有五个档位的汽车: - **四档(日常巡航)**:测试公共 API,写出能用的代码,然后进行重构。 - **五档(高速直达)**:实现方式非常直观显而易见,以至于省去了“先写糙代码再重构”的步骤,直接把最终答案写出来。但 Cooper 警告,一旦发现测试覆盖率开始异常膨胀,必须立即降回四档,因为这说明你开始凭空编写没有测试保护的投机性代码了。 - **低档(探索实现)**:如果你卡住了,“I don't know how to go green”,不知道该怎么写出能变绿的实现,想通过测试摸索前进,那就果断降入低档。你可以写一些针对细粒度内部实现的测试,或者借助 REPL 工具(如 Python 交互终端)来辅助试探。 但 Cooper 给出了极其严苛的后续操作规则:“At the end of that process, delete the tests.” 这些低档位探路测试的价值仅在于引导你攻克难关;一旦目标达成,保留它们就会沦为后来维护者的沉重包袱。下一位开发者重构内部细节时,根本看不懂你当年写下的临时探路脚手架,测试只会莫名其妙地阻碍正常的架构演进。把它们统统删掉,只保留面向公共端口的测试。 --- ### [59:39](https://www.youtube.com/watch?v=EZ05e7EMOLM&t=3579s) 验收测试驱动开发(ATDD)的反思与 BDD 的初衷 谈到验收测试驱动开发(ATDD),Cooper 回顾了其跌宕起伏的历史: - Kent Beck 最初极力反对 ATDD,理由是成本极高,而且客户对这些东西根本不感兴趣。后来他虽然略有动摇,担心开发者测试无法聚焦系统的核心主旨,但依然持保留态度。 - 工具 Fit 的作者之一 James Shore 多年后也公开发表反思,宣布自己不再使用这种工具了,原因同样是客户根本不在乎这些表格或特定文本,而且 Fit、Cucumber(Gherkin 语法)这类工具带来了极其沉重的长期维护负担。Shore 提倡开发人员直接与客户交谈,由交谈成果指导开发者编写地道的开发者测试。 Cooper 承认自己过去也迷信过 Fit 和 Gherkin 这一套,曾认为这种端到端的可执行规范能让程序员保持诚实,明确“完成的标准(Done Done)”。但后来他意识到,团队之所以需要 ATDD,纯粹是因为以前的单元测试走入了歧途——大家以前都在测“类和方法”,根本没有东西在记录和验证“系统行为”。一旦把 TDD 的核心纠正为“以行为为导向”,ATDD 就失去了存在的必要,大家就可以彻底扔掉自然语言和代码库之间那层恶心的翻译胶水层(Horrible translation layer)。 谈到行为驱动开发(BDD),Cooper 澄清,Dan North 最早写下 BDD 的核心初衷非常纯粹:测试应当关注行为(Behaviour),而不是关注类和方法。虽然 BDD 如今已被包装成一整套庞大的敏捷方法论,但今日本次演讲吸纳的只是 Dan North 最初的那颗思想内核。 --- ### [01:01:36](https://www.youtube.com/watch?v=EZ05e7EMOLM&t=3696s) 滥用 Mock、IoC 容器灾难与结语 在演讲的尾声,Cooper 快速而尖锐地炮轰了几个行业毒瘤实践: 1. **Mock 对象的本质与滥用**:Mock 的唯一合法场景是“该资源创建极其昂贵,且代表了共享的状态性资源”(Shared fixed problem)。然而行业里的大多数人却拿 Mock 来强行隔离每一个类。更恶劣的做法是资深开发者通过强制要求初级程序员使用 Mock,以此来肉眼审查初级开发者的方法实现步骤。Cooper 严厉告诫:“It just couples your test to your software means you can't change it, it's never worth it.” 这会使测试与软件结构深度耦合,彻底剥夺未来的重构能力。 2. **IoC 容器的滥用**:IoC(控制反转)容器在当代开发中被极度滥用。除非框架强行绑定,否则尽量不要引入。一个优秀的框架应当要求传入工厂(Factories),而不是扔给你一个全局 IoC 容器。Cooper 警告:“If you find yourself implementing an IoC registration in your test, something has gone horribly wrong.” 在测试代码里配置 IoC 注册,说明设计已经彻底脱轨,就像当年滥用 ORM 一样荒唐。 最后,Cooper 总结了整场演讲的核心教训: - 永远测试**系统行为**,只有在增加新行为时才新增测试; - 哪怕写出脏代码也要先变绿,然后在不破坏测试的前提下重构; - 重构出来的内部私有细节根本不需要单独写测试,它们已经被端口层的测试所覆盖; - 测试应锚定在六边形架构的“端口层”; - 集成测试仅用于验证外部适配器与端口的对接配置; - 彻底停止针对类内部实现的 Mock。 Cooper 抱歉没有留下提问时间,建议大家在会场中找他交流,或去观看网上 NDC 等会议的录播切片,并推荐大家观看 Kent Beck、DHH 和 Martin Fowler 关于 TDD 的那场经典论战视频。 ### [01:03:48](https://www.youtube.com/watch?v=EZ05e7EMOLM&t=3828s) 演讲结语与致谢 在整场演讲的尾声,Ian Cooper 对 TDD 在行业实践中偏离初衷的现状进行了最后的总结性收束。他提到关于测试驱动开发究竟“在何处走入歧途”的探讨还可以持续深入地交流,并对在场所有参会开发者的聆听表示由衷感谢。Ian Cooper 说道:“conversation about how it's all gone wrong but thanks very much thank you”,随后全场响起了热烈的掌声,为本次关于重拾 Kent Beck 经典 TDD 核心理念的演讲画上了圆满句号。 ## Notable quotes > [35:01](https://www.youtube.com/watch?v=EZ05e7EMOLM&t=2101s) The unit of isolation is the test, not the thing under test. — Ian Cooper > [24:01](https://www.youtube.com/watch?v=EZ05e7EMOLM&t=1441s) 在 TDD 中,创建新测试的触发条件只有一个:你有一项需要实现的业务需求。仅此而已,别无其他。 — Ian Cooper > [28:00](https://www.youtube.com/watch?v=EZ05e7EMOLM&t=1680s) 被测系统(System Under Test)并不是一个类。太多的 TDD 实践把精力过度集中在为单个类编写测试上了。 — Ian Cooper > [46:07](https://www.youtube.com/watch?v=EZ05e7EMOLM&t=2767s) Dependencies [are] the key problem in software, right? Coupling is [a] worse enemy for you than DRY. Coupling is what kills all software. — Ian Cooper > [46:07](https://www.youtube.com/watch?v=EZ05e7EMOLM&t=2767s) Do not make these public to test. If you find yourself making things public in order to get it under test by implementation details, ask yourself if that genuinely is a part of the contract of your module? If it's not, don't. — Ian Cooper > [01:01:36](https://www.youtube.com/watch?v=EZ05e7EMOLM&t=3696s) If you find yourself implementing an IoC registration in your test, something has gone horribly wrong. — Ian Cooper > [54:28](https://www.youtube.com/watch?v=EZ05e7EMOLM&t=3268s) 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. — Ian Cooper > [43:23](https://www.youtube.com/watch?v=EZ05e7EMOLM&t=2603s) You can't both understand the solution to the problem and engineer the code, right? One of two things will go wrong: you will either over-engineer your solution beyond what the test actually requires, or you'll get some kind of analysis paralysis. — Ian Cooper ## Mentioned Ian Cooper, Kent Beck, Martin Fowler, Dan North, David Heinemeier Hansson (DHH), Fred George, Joshua Kerievsky, Alistair Cockburn, Robert C. Martin (Uncle Bob), James Shore, Huddle, .NET, Ruby on Rails, Active Record, FitNesse, Cucumber, SpecFlow, Selenium, DevExpress, ReSharper, RabbitMQ, Test-Driven Development: By Example, Refactoring: Improving the Design of Existing Code, Refactoring to Patterns ## 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)

Source quotes

[35:01] Ian Cooper: The unit of isolation is the test, not the thing under test. [24:01] Ian Cooper: 在 TDD 中,创建新测试的触发条件只有一个:你有一项需要实现的业务需求。仅此而已,别无其他。 [28:00] Ian Cooper: 被测系统(System Under Test)并不是一个类。太多的 TDD 实践把精力过度集中在为单个类编写测试上了。 [46:07] Ian Cooper: Dependencies [are] the key problem in software, right? Coupling is [a] worse enemy for you than DRY. Coupling is what kills all software. [46:07] Ian Cooper: Do not make these public to test. If you find yourself making things public in order to get it under test by implementation details, ask yourself if that genuinely is a part of the contract of your module? If it's not, don't. [61:36] Ian Cooper: If you find yourself implementing an IoC registration in your test, something has gone horribly wrong. [54:28] Ian Cooper: 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. [43:23] Ian Cooper: You can't both understand the solution to the problem and engineer the code, right? One of two things will go wrong: you will either over-engineer your solution beyond what the test actually requires, or you'll get some kind of analysis paralysis.

Source quotes

[24:16] Ian Cooper: 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. [61:08] Ian Cooper: If you find yourself implementing an IoC registration in your test, something has gone horribly wrong. [35:01] Ian Cooper: 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 [49:04] Ian Cooper: Test behaviour don't test implementations.

Original transcript

[0:00] um just a quick sort of show of hands or [0:02] a couple of questions from the audience [0:04] how many of you have actually seen a [0:07] video of me doing this talk at any point [0:09] few of you okay [0:11] there are some nuances and giving it [0:15] live would be a bit time bit different [0:16] but you're seeing the gist of the [0:18] argument how many of you are using [0:20] tester from government today test first [0:22] the test after okay how many of you are [0:27] doing it you know test first all right [0:30] and how many of you are using so mock [0:34] mocking techniques and mocks okay two [0:37] loads you okay very interesting okay [0:42] this is me most of that is dull and [0:45] uninteresting but that I usually draw [0:48] attention to is the last point on that [0:50] slide which is people like me come and [0:54] talk at conferences to stand in front of [0:55] you we're not necessarily smarter or [0:58] cleverer than any of you guys and you [1:00] know I'd really encourage all of you to [1:01] gallop and speaker conferences if you [1:03] can contribute back to the community [1:06] that's all what I'm doing is I'm just a [1:08] developer like you who decided to try [1:10] and share some of my ideas with [1:11] everybody else [1:12] I kind of got forced into it because [1:14] back when dotnet first started there [1:17] were very few people to reverse the [1:19] experts and so as such you know if you [1:22] wanted to run a meet-up or a user group [1:23] you had to kind of start up and do it [1:25] yourself but I'd encourage all of you to [1:28] get involved if you can it's a great way [1:29] of personal growth for you as a [1:33] developer in terms of learning to [1:34] express your ideas more clearly that's [1:38] how I work for I work for huddle it's [1:41] not really that exciting we are hiring [1:42] by the way but it's not that really [1:44] exciting for you other than just to say [1:45] we are a SAS software business I have [1:49] nothing to sell you right you can't buy [1:51] my services so one of the ideas here are [1:54] based simply on my experience I'm trying [1:56] to share with you not on me trying to [1:59] basically market anything to you we can [2:02] talk about today so I want to talk about [2:05] what I perceive as being the problem [2:07] with how people are practicing TD today [2:09] and why there has been growing [2:11] resistance to TDD [2:13] I want to then look at rebooting RTD [2:16] practice taking us back already to where [2:18] we started from I'm looking again ideas [2:21] like red green refactor green code [2:24] refactoring etc until I understand how [2:27] those ideas should be applied where we [2:28] may have made mistakes in our practice [2:30] that have led us to adverse ways of [2:32] doing TDD and then I'll talk about some [2:35] ideas that can help ports and adapters [2:37] gears look at what a team behaved right [2:41] and give us and Mach squad to get [2:43] through some of the later sections we [2:45] may go a little bit faster on depending [2:47] on time so we'll try and focus on the [2:49] early sections first thing I do is talk [2:53] about the problem now I have been [2:55] practicing TDD since about 2004 in [2:59] London the dotnet community were quite [3:01] early adopters of test-driven [3:03] development just simply because we had [3:06] close relationships with an XP group [3:11] that used to meet in London and a lot [3:12] cross-fertilization of ideas and so we [3:15] were into practicing a PDF or quite a [3:16] while and I first gave this talk in [3:18] about 2013 so I've been roughly 10 years [3:22] I've been doing TDD and I had come to [3:25] the conclusion after owning TDD produced [3:28] test Suites for a while that there were [3:29] some problems of what we were doing and [3:32] you know one of my things is that I tend [3:35] to try because let's become a software [3:37] architecture on stair companies long [3:38] enough to see my own mistakes come back [3:40] to meet me it's a very humbling thing to [3:43] do but it's also how you gain kind of a [3:45] lot of value from your experience if you [3:47] stay somewhere two years quite often the [3:49] problem is that you don't ever see you [3:51] finished implementing something you're [3:53] very proud of this great work you've [3:55] given the need done and you move off and [3:58] you never find out what we're all the [3:59] things that you actually did wrong that [4:02] later developers went right about so if [4:05] you'd stay a bit longer quite often you [4:07] find what goes wrong and I have written [4:10] quite extensive test Suites and I found [4:13] some of them were very difficult and [4:14] very expensive to own so that's going to [4:18] talk about my understanding of TDD and [4:21] attach shifted over the years okay so [4:24] the first thing was back in 2005 2006 [4:27] when Alice first haven't drunk the [4:29] kool-aid on TDD if I was practicing TDD [4:31] I found quite a lot of resistance to [4:33] test driven development lot of people [4:35] were saying we don't want to do this [4:36] this is a crazy idea and I was very much [4:39] you just don't get it you just don't get [4:42] what the rest of us get you know this is [4:44] the best best change in development [4:47] practice I've seen in the last 10 years [4:49] you guys need to be doing TDD you just [4:51] don't get it but still the people saying [4:54] well I just I'm not really sure about [4:57] this TDD this crazy thing is a lot of [5:02] those guys were really quite smart [5:04] developers who I respect it [5:06] you know these are guys who wrote really [5:07] good code yet they were saying to me [5:09] this TDD thing it's not the right way to [5:13] develop software Anna you know the more [5:16] they said this the more just kept [5:17] repeating the TDD mantras as if like a [5:20] child if I just kept speaking slowly and [5:22] carefully and repeating how TDD worked [5:24] eventually the light bulb would go off [5:27] and they would appreciate the glory that [5:28] was TDD but a few of them just seem to [5:32] resist I can be one of them a really [5:34] smart developer at law I you know really [5:35] really admired his work said to me one [5:38] time it's okay and I finally got it and [5:40] that's like and he said the junior [5:44] developers they need TDD it really helps [5:47] them I can see how it helps them write [5:49] good code but surely you don't really [5:51] expect the experienced developers on the [5:53] team to be doing TDD right and I was [5:56] really puzzled I couldn't really [5:57] understand what is this guy getting it a [6:04] couple of times we're very proud of the [6:06] fact that we produced some code went [6:08] into production with almost zero defects [6:10] so there's no UI evolving like that bit [6:13] easier but one time I worked on our [6:14] back-end service and that went to [6:16] production and for a year there were no [6:18] reported defects what exactly's expected [6:21] we were very proud and we put down the [6:23] you know but down that way this had [6:26] worked at EDD we had a fantastic test [6:29] suite we knew all the scenarios we were [6:31] going to operate under and provably our [6:33] code worked all I looked at this test [6:37] suite one of the things I noticed was [6:38] that we had about three times as much [6:40] Tesco [6:40] as production code and I can remember [6:44] being a little bit mmm is this right so [6:47] I went onto Twitter source of all [6:50] answers to all questions in life in [6:51] universe their source of truth and [6:52] wisdom and I said to twitter twitter is [6:55] this right you know yeah no no we caught [6:58] often have you know two or three times [7:00] the amount test code that we have [7:02] basically is actual production code I [7:04] was thinking it's okay but it feels a [7:08] bit puzzling one of the problems with [7:10] this is you quite often got the [7:14] situation where you know some project [7:17] manager would turn around and say guys [7:18] could we just skip the tests we've done [7:21] so much faster we get out to market a [7:24] lot faster I know you guys like doing [7:26] good engineering except you're on I [7:27] really get that but it's taking us an [7:29] awfully long time to write all these [7:31] tests couldn't we just ship the code you [7:35] wouldn't really matter if there were a [7:36] few defects you know in production right [7:39] can we just not do the test and we'd say [7:41] no no no you don't understand [7:43] the way we write code is by writing [7:45] tests we can no more stop writing the [7:48] tests let me collect this is all right [7:49] into the standard mantra admittedly you [7:52] know tests are part of our practice for [7:53] writing code you're just telling us to [7:55] stop writing code if you do that and red [7:57] go away you go okay fair enough we get [8:00] it you know you guys want to write tests [8:03] but we were slower [8:06] I mean ssyn reality we were you know [8:09] twenty to fifty percent slower than we [8:10] had been before [8:11] by writing tests the other thing I [8:17] noticed over time was there's so many [8:18] big test Suites is that when we wanted [8:21] to change something we've quite often [8:24] break a lot of the tests and quite often [8:28] what we were doing was simply [8:30] refactoring we were going oh well we've [8:32] added some new features to the code and [8:34] now the shape of the implementation is a [8:37] bit wrong giving these new features and [8:38] I can think of a better way of [8:39] organizing our code make you more [8:41] maintainable so I'm going to refactor [8:42] the way the code is organized I want to [8:45] change the behavior of the system I just [8:46] want to reflect of the shape of the code [8:48] so that essentially it's more efficient [8:50] but how other tests would break [8:53] and all the time the tests that broke [8:55] were the ones that basically that were [8:57] heavily marked because these mocks we're [9:01] telling this exactly what ought to be [9:02] happening in the calendar test they were [9:05] saying old first you should be cooling [9:06] this twice then call this and then put [9:09] call this and put these parameters on it [9:10] when we refactor something we quite [9:12] often changed how that worked all these [9:14] tests with mocks they broke and we had [9:15] to rewrite all the mocks [9:17] it was kind of puzzling right because [9:20] the promise was that we should be able [9:23] to refactor our code keep it basically [9:26] fit and clean we wouldn't get into a big [9:29] ball of mud craziness we wouldn't really [9:31] see our code rot before us because our [9:34] tests would enable us to refactor and [9:36] refactoring kept our code healthy but we [9:40] wanted to change our code actually the [9:42] tests were an obstacle to that change [9:45] because they increased the amount of [9:47] effort we had to put into making any [9:48] changes it had to fix all these tests [9:52] that were breaking when we changed her [9:55] implementation details and that didn't [9:57] sound right because surely what Martin [10:01] Fowler an account that were telling us [10:02] about refactoring was that refactoring [10:05] was changing the implementation details [10:07] without breaking tests yet our tests [10:11] were breaking all the time the other [10:18] thing we began to notice was I guess [10:22] it's about 2009-2010 a holiday [10:24] methodologies began to emerge that [10:27] explicitly said don't do test-driven [10:29] development because test-driven [10:32] development slows you down [10:33] so there's program around okey know what [10:36] program around key is you guys that [10:37] reached over here ern [10:39] Uncle Fred George Fred George [10:41] essentially said for those who [10:42] understand spinal tap references he said [10:44] if XP is good let's turn the dials on XP [10:47] up to 11 let's do extreme extreme [10:51] programming that's basically fire [10:53] everybody who isn't a software developer [10:55] father QA father product guy just have [10:58] developers talking to customers and what [11:00] we'll do is we'll write really small [11:02] pieces of code we'll call them micro [11:04] services the only [11:06] about a hundred lines so different [11:07] schools microservices this is the [11:08] original one about hundred lines of code [11:10] I won't have any tests because it's such [11:12] a similar amount of code you ban to get [11:14] it right [11:14] we'll put it into production if you want [11:16] to change we'll just throw it away and [11:18] rewrite it okay [11:20] press record programmer Anakin but there [11:22] other things like spike and stabilizer [11:24] even lean software development which [11:25] became the real you know go to approach [11:29] of California and Silicon Valley startup [11:33] we're saying don't do tests right [11:37] working character customers get it out [11:39] there fast as you can get feedback [11:41] because you may be building the wrong [11:42] thing altogether I'm gonna throw it away [11:45] TDD is a kind of practice you do you [11:47] like to run when you're scaling I [11:49] pretend to test it slows you down too [11:50] much so there's this whole pressure [11:54] saying TDD is a bad idea because TDD [11:58] really slows you down and he's better [12:00] off just getting feedback you guys [12:06] especially over here duct-tape [12:07] programmer so doc tech program was this [12:11] really annoying guy right and every [12:13] organization has one and he's the guy [12:15] that business or the product team [12:16] actually love because he just gives them [12:19] solutions as fast as he can and as a [12:22] requirement comes out and they say let's [12:24] keep it to bob bob will just get it out [12:26] the door really fast and rest the team [12:28] are going but this is crazy then and she [12:30] ain't you going we hate Bob Bob's [12:32] engineering is rubbish we spent all this [12:34] time maintaining the stuff that Bob's [12:36] written and having to rewrite it and [12:38] it's really annoying because Bob gets [12:39] all the credit right from the product [12:41] guide the guy's going Bob fantastic he's [12:43] so receptive to our ideas [12:45] he's pushes stuff out the rest are you [12:47] going but it's so hard to maintain the [12:50] Bob's code is terrible right but they [12:53] love Bob because Bob delivers for them [12:55] you're still writing it for your test [12:57] suite and bob has shipped the code and [12:59] it's running okay we had a few of those [13:03] and needs to be really annoying you [13:05] should be really annoying that we were [13:06] still doing they're doing our best [13:07] engineering practices and these guys [13:09] were shipping code and there's waffle [13:11] code but they were shipping right and so [13:14] they won the credibility not us [13:21] sometimes we to come back to our tests [13:24] when they were breaking quite often [13:26] because we were trying to change [13:26] implementation details we'd realized [13:29] that we had no idea what the test was [13:32] doing they don't have this experience [13:33] you go back and find the test is red and [13:36] you're looking at it in your going I [13:37] have no idea what this test is for and I [13:41] have no idea whether the fact that this [13:43] red is good or bad at this point in time [13:45] okay this test seems to add two numbers [13:48] and come to a result which I can't [13:50] possibly understand how they could have [13:52] originally ever got that result and why [13:53] this test was originally passing and now [13:55] I've refracted it and it's red surely [13:57] just because the test is wrong but lots [14:00] of tests just don't really give you any [14:02] information especially the ones that are [14:03] like swarm with mocks and you kind of [14:05] like what is actually an under test here [14:07] I don't have the experience I'd look to [14:09] the test and they finally realized [14:10] nothing is actually being tested apart [14:13] from the mocks right someone's written [14:15] this thing about it so how do you go [14:16] there's no real code actually under test [14:19] here okay and that experience place if [14:22] you began to drive us mad we're like [14:24] well I no longer know what this test is [14:25] for the best thing for me to do is just [14:27] to delete this test because it's broken [14:30] I don't know why I made to my shame a [14:38] team I worked for embrace fitness and we [14:45] sat down and produced basically these [14:47] loving tables in HTML and they drove use [14:54] them to drive our software and [14:56] eventually you know these little tests [14:58] would show up the page would shop green [14:59] so that we could say yes these [15:01] high-level tests that are the cut-in [15:04] written in natural English so the [15:06] customer can understand them these tests [15:08] basically demonstrate that our software [15:10] does exactly what we say it does on the [15:12] can and spec flow cucumber these tools [15:17] are all the same kind of thing the idea [15:19] essentially that I will write tests that [15:21] the customer can just flip through the [15:22] HTML pages see the requirement we've [15:25] implemented feel confident that the [15:27] system does exactly what he specified [15:30] because it [15:31] and press the button and see or go green [15:33] are the software runs exactly as I [15:35] expected the gun is basically hated [15:39] writing these tests because they'd start [15:42] out practically nil be red in the [15:43] beginning the iteration so be red for a [15:45] lot of the iteration until eventually [15:47] they would go green when you'd done the [15:48] implementation and that meant [15:50] essentially your fitness test suite was [15:51] quite often well correctly read much the [15:55] iteration so you could never really be [15:57] sure if the tests were read is that [16:00] because it's yet to be implemented or is [16:01] that because essentially we we we broken [16:03] it and so the thing that happened was a [16:07] lot of time the tests were red because [16:09] we just assumed all that red because not [16:10] implemented yet when in fact they were [16:11] broken so it spent a lot of their time [16:14] as broken test Suites the customers [16:18] never really looked at this stuff right [16:22] we'd built these customer-facing tests [16:24] but it was very rare to get the [16:26] customers to actually come along and [16:27] flip through the HTML pages and say I'm [16:28] so impressed guys I can see exactly the [16:30] requirements that I want I can see them [16:32] written in a natural language and I I [16:34] can see that it works right a cage we [16:36] try and sit them down make them look at [16:37] the screen and they go maybe something [16:40] my calendar guys and we'll talk about [16:41] this at some point in the future right [16:42] and they were completely uninterested in [16:45] seeing these test Suites that we written [16:47] to prove to them that the code works [16:49] correctly for and the check in dollars [16:56] became more complicated because as well [16:58] as passing a developer tests you had to [17:00] run all these fitness test Suites before [17:02] we allowed you to check your tests into [17:04] the code base they took a while to run [17:06] they were slow and that meant [17:08] essentially you go for making a cup of [17:09] coffee and come back essentially because [17:11] really you know that's you then you'd [17:12] lose track of where you were what you [17:14] were doing and the whole flow of your [17:16] coding session was broken because now [17:18] you're in that classy developer [17:19] situation won't take you 20 minutes to [17:21] really get my head around the code that [17:23] I'm working on and then I'm in the [17:24] groove and I'm working on it but now I [17:25] forced the interruption to our process [17:27] which is going to mean that you stop [17:28] working effectively during that time [17:30] period okay [17:31] so become a really efficient way of [17:33] doing it and the developers just really [17:35] hated doing it and they started to [17:36] ignore them right if we just basically [17:38] keep going and checking code in [17:40] eventually and will notice the tests are [17:42] all red and go by again sort of thing [17:45] and then we have to [17:45] basically down towards or fix them but [17:46] until then we can keep moving right and [17:50] then what happened was if someone would [17:52] always be plausible deniability right my [17:54] test didn't make that go right my change [17:57] as with my change that was Davis change [17:58] then used to have somebody appointed on [18:00] the team whose job was to essentially [18:02] determine why the who had broken the [18:04] tests and made them go red and then that [18:06] person would essentially blame you know [18:08] apportion blame for the person that had [18:10] to go and fix it right the fact that we [18:12] actually had a step in our process which [18:14] essentially was somebody decides who's [18:16] to blame for the tests being red today [18:18] and then they go to have to go and fix [18:20] them implies that no one really cared [18:22] about those tests because they'd reached [18:23] that point at being a red the whole time [18:28] really the developers couldn't see any [18:30] value they constantly resisted write-ins [18:32] his tests and one thing I've learned [18:34] over time is that you know if an idea [18:36] actually really works after a while your [18:40] team will be quite happy behind it and [18:42] if you get some supporters of it if your [18:44] team is constantly pushing back saying [18:47] we e doesn't they don't see where I'm [18:49] getting from this I don't see the value [18:51] in the test that you're forcing me to [18:53] write and maybe you need to think again [18:55] about what you're doing okay I'm about a [19:01] year after I gave this talk for the [19:03] first time DHH the kind of crisis is [19:05] about petrol rails posted this blog post [19:08] TDD is their donative testing and a lot [19:11] of what his complaint was was I am being [19:14] forced by people to become that rouse [19:20] wrong because rouse doesn't work the way [19:22] they think test friendly framework [19:24] should work so he said you know I have [19:27] the active record pattern rails no next [19:29] record those who don't know effectively [19:30] the data persistence methods own are [19:32] actually on the object that you're [19:33] persisting so your user object has a [19:36] persistence but it's on it for object I [19:38] see people tell me this is no good [19:40] because they can't cleanly separate [19:42] their domain object models from [19:44] basically their testing framework and he [19:47] said I just used to test like right my [19:49] unit tests and I talked the database and [19:51] I'm now told that's wrong as well and I [19:53] said I used to like TDD when it first [19:55] came out but now I'm constantly being [19:57] told I'm doing TDD wrong [19:59] then I'm just giving up on GED you know [20:01] TDD as far as I'm concerned doesn't work [20:03] I'm still gonna do some testing but I'm [20:05] quitting on TDD and of course such a [20:09] stir that Kent Beck kind of responded [20:11] saying you know our IP TDD it was kind [20:15] of sarcastic post which men have been [20:18] the best choice when Americans are [20:19] amongst your audience but um he kind of [20:22] said you know oh well here's all the [20:24] things I'm not gonna do now I don't have [20:26] TDD anymore so it's kind of offensive [20:27] TDD and Martin Fowler actually produced [20:30] this series of dialogues between [20:31] mediated them between Ken and DHH [20:35] talking about to you the DHS went and [20:37] saw an original version this talk I gave [20:39] at MDC and was kind of saying yeah I [20:41] think you and I on the same lines and [20:44] they came to a pretty similar conclusion [20:46] as thrusters talk so you might go away [20:48] if you like the ideas here and go and [20:49] see Ken and DHH having this dialogue [20:52] made it by Martin about whatever II [20:55] think happened but the question is kind [20:58] of where did it will go wrong right what [21:00] and wrong for TDD and can we fix it [21:02] commit can we can we change our our ways [21:05] um anyone know anyone ever hear of a [21:08] call to an English footballer called [21:09] George best no mostly present now it's [21:13] very very famous in giving a major in [21:16] their 1970s and the story goes that a [21:23] bellboy at the hotel enters George's [21:25] room in the hotel George is lying on the [21:28] bed naked with a couple of supermodels [21:31] the bellboy is wheeling in the caviar [21:35] and champagne that is going to give to [21:37] George and these naked supermodels on [21:39] the bed and the bellboy looks down at [21:42] George says George where did it all go [21:46] wrong [21:49] and so treat that sentence in that kind [21:54] of light right I am a fan of TDD what I [21:57] want to talk about is you know where did [21:58] it all go wrong okay so what I want to [22:01] do really is go back to the beginning [22:02] and let's go back to the beginning of [22:03] TDD and I want to explain to you the [22:06] firmest in my argument is TDD has [22:08] answers to all the problems that we have [22:10] been meeting with TDD we have just [22:12] ignored the fact that basically the [22:15] original approach to TDT is described by [22:18] Kent back works later patterns and [22:21] practices we have loaded onto TDD made [22:24] many cases a mistake and have led us [22:26] away from what was originally intended [22:28] in our mistake so I would recommend [22:30] who's read this book okay many more if [22:35] you practice TDD than I read this book [22:37] who's right so he'd read some other book [22:38] to learn about TDD who was told how to [22:42] do TDD by somebody else that worked near [22:44] them yeah probably more of it okay I [22:46] would recommend reading this book if you [22:51] practiced her sister and development you [22:52] want to understand how it works I [22:54] recommend reading this book when I began [22:56] to struggle with TDD I went back to read [22:58] I read this book again I read it read it [23:00] originally I went back to read this book [23:02] again and one of the things I learned [23:03] was that Kent had an enormous amount of [23:06] wisdom about TDD having practiced TDD [23:09] for a large number of years before he [23:11] read the book and many of the problems I [23:14] was encountering with TDD Kent had [23:17] already encountered understood and were [23:20] discussed in this book and this book of [23:25] though it seems old covers all sorts of [23:26] things that mocking a TDD which many [23:29] people think are they much later in most [23:30] later developments they happened a lot [23:32] later in life cycle they're all in him [23:36] right this book is probably the only [23:38] wisdom you need but I don't think I [23:40] understood much which wisdom and so I've [23:42] been doing TDD for a certain amount of [23:44] time and I was then able to understand [23:47] what Kenan was trying to tell me okay I [23:52] think the most important thing to take [23:54] away from and understanding of that book [23:56] is this okay when you are practicing [23:59] test-driven development [24:01] do not test template implementation [24:03] details test behaviors are exploring [24:06] that sentence more as we go through this [24:07] talk that is the key thing that people [24:11] get wrong today so what happens is is a [24:16] classic modern TDD cycle someone says [24:19] I'm about to write method on a class I [24:21] will write a test before write that [24:25] method and that test will govern that [24:27] whether that method succeeds or fails [24:29] and so the trigger to writing a test in [24:33] TDD practice is essentially adding a [24:36] method to a class that is the wrong [24:38] thing that is not the ethos of TDD the [24:51] trigger in TDD for creating a new test [24:56] is that you have a requirement you want [24:58] to implement it is nothing more it is [25:01] nothing less okay there is something I [25:05] want the software to do I will write a [25:06] test to do that okay [25:10] I want basically the software to add an [25:14] amount to this customers bank account [25:16] essentially right [25:18] I want the software to upload a new file [25:22] to the users collection of files right [25:29] that's the that's what drives a test in [25:32] TDD that's what you are tested writing [25:33] test for you're not writing test to say [25:36] the foo method should take the two [25:39] numbers multiply them and return the [25:42] result okay and that is an [25:44] implementation detail there is no [25:47] requirement you received one problem [25:49] that's not necessary in a calculator [25:50] that no requirement you received it gave [25:52] you that the requirement you receive was [25:54] something else much higher up right and [25:57] that is where you write the tests [26:02] what you want to be doing is focusing on [26:04] testing the API of your software okay [26:08] when we talk about the API we're talking [26:11] care for that in a second but we mean [26:13] what is the contract your software has [26:16] with the world what what is it offered [26:19] to do for people who are basically [26:21] consuming your software that is the [26:24] stable idea of your software it will not [26:28] change that rapidly and you may add that [26:31] new stuff to it etc but it's a stable [26:35] requirement you have how you implement [26:37] that requirement is unstable you may get [26:40] better ideas over time and you may wish [26:42] to change it but what your software [26:45] offers to consumers is the stable [26:47] contract and that is what you should [26:50] test we don't necessarily by the way [26:55] mean by this and actually keep you Jason [26:57] API right so I'm conscious now that's [27:00] when I use the word API everyone [27:01] imagines you know rest HTTP Jason o [27:04] graph QL whatever I said right what we [27:07] mean simply by an HTTP API really or by [27:10] an API really is just the publicly [27:13] exposed surface area of your module [27:16] generally that somebody else is [27:18] concealing so in you know the classical [27:21] description of a module you know module [27:24] has basically a set of exports public [27:26] facing things that other other consumers [27:29] can call other module but other software [27:31] can call an internal implementation [27:33] details that you've hidden away these [27:35] are these classic notions of information [27:37] hiding encapsulation right your your [27:40] modular has a facade something on the [27:43] outside that essentially represents [27:45] hides all those details away and it is [27:48] that external sort of exports that you [27:50] are interested in testing not the [27:52] implementation details which tell you [27:54] how you are implementing that okay [27:58] what happens is we should get some use [28:00] cases or a story in and the kind of [28:03] agile environment and that says here is [28:06] the requirement I want you to implement [28:07] here is the story here is the use case [28:09] here is the thing that your software is [28:11] supposed to be doing and you write tests [28:14] to say right I'm going to write [28:16] test to prove I can do that and [28:18] generally speaking there are good models [28:20] first like given when then given the you [28:22] know given that my account has a hundred [28:24] pounds in it when I add another hundred [28:26] pounds to it then I have a total of two [28:27] hundred pounds right that is a kind of [28:30] classical requirement trying to [28:31] implement now I may end up using object [28:35] to represent accounts and that kind of [28:37] thing but what I'm doing is saying my [28:39] requirement is the behavior of my [28:41] account and a half perhaps when I add or [28:43] remove things from the system under test [28:46] is not a class too much TDD practice [28:51] focuses on essentially writing tests for [28:54] classes okay the system under test is [29:00] really the exports for a module its [29:03] facade when people talk about this [29:05] phrase unit testing at the time that [29:07] people were basically coming up with [29:09] this kind of idea to them classical [29:12] testing paradigms a unit test was a test [29:16] of a module right it's kind of a black [29:19] box where effectively you talk to [29:21] basically what exposed now there's a [29:25] certain line between classes and modules [29:27] where effectively publicans say what a [29:29] class is a module it's information [29:30] hiding encapsulation except like I think [29:31] that and I appreciate that but the [29:34] definition of unit has become too much [29:37] fixated on being a class people talk [29:40] about by selecting that class and I take [29:41] them across is dependencies right now it [29:44] leads into all sorts of problems because [29:47] then you start mocking heavily which [29:49] leads you in totally callout specified [29:50] tests where essentially you know all [29:53] about the internal implementation [29:54] details of that class through your mocks [29:56] and how they're called and essentially [29:59] you've hidden no implementation details [30:01] so you want to focus in a much higher [30:03] level you want to say I have some public [30:07] things that this module does that is [30:10] what I'm going to test I have some [30:12] implementation details and I'm not going [30:15] to test those ok and we will explain how [30:18] to get there TDD explains exactly how to [30:20] get there this may sound complicated but [30:22] there is an easy route to get there ok [30:25] so obviously you may actually be driving [30:28] a class because [30:29] if you're writing say C sharp or Java [30:31] you can only drive classes but that [30:33] classmate way may tend to be a facade or [30:36] command or etcetera some kind of [30:38] interface to the actual module [30:40] underneath you don't want to write tests [30:45] to cover implementation details because [30:48] of those change you want to write tests [30:53] for your stable contract which is [30:56] essentially your exports from your [30:57] module your API refactoring is the key [31:04] step which enables you to achieve this [31:06] goal of separating between things that [31:09] are implementation details that you [31:10] don't need to test and things that you [31:13] do need to test there is a get out [31:18] clause and they get out Clause says [31:20] sometimes it's helpful to probe the [31:24] implementation details are difficult to [31:25] understand you can do that but the test [31:28] afterwards again will come back to that [31:31] so a lot of these ideas I would support [31:32] it as we go through I want to give a bit [31:35] of a proxy to Dan - Dan wrote a post [31:37] about what he called behavior driven [31:40] development [31:40] now that's turned into something else [31:43] really that has it has the same origin [31:46] point same route [31:48] this is the root of that when people [31:49] talk about behavior driven adopted a [31:51] they mean something else in time what [31:52] about that later but in his original [31:54] post Dan said I've often find when I go [31:58] to basically implement TDD in places [32:00] people are very confused about happy TDD [32:02] because of the word test the word test [32:04] throws them and if I changed that word [32:07] to behavior drawn up the developing your [32:10] system using behaviors people do it [32:11] correctly this insight was very old [32:15] economist when this war is basically [32:18] 2500 etc and he's he's saying exactly [32:21] what really I'm gonna tell you in this [32:23] talk people misunderstood what Kenan was [32:25] talking about probably because can you [32:27] use classical text in terminology like [32:29] test unit test me I mentioned it's just [32:31] a couple of times in the book [32:33] generally the phrase Kent Beck uses [32:34] developer test right not unit testing [32:38] developer tests [32:40] but Dan basically effectively recognized [32:42] the same problem I'm recognizing it here [32:44] which is people were driving tests by [32:48] saying I need to test this method they [32:51] should have been saying I need I was a [32:52] behavior in the system and I want to use [32:55] code to basically prove that that [32:57] behavior works in the system okay so in [33:01] case you think that's just a damn thing [33:02] Kent wasn't talking about this just came [33:04] back originally so it can talk about [33:06] behaviors behaviors would demonstrate [33:08] that confident work will compute report [33:11] correctly so he's saying the object of [33:13] TDD is to test behaviors in the system [33:17] okay here is a classic thing that you [33:22] are testing add amounts in two different [33:24] currencies convert the results of given [33:26] set of exchange rates we are not testing [33:28] a method on a class we are testing a [33:33] behavior of the system that we can add [33:35] two different amounts in two different [33:37] currencies and then see basically the [33:41] result in one currency we are [33:46] multiplying a price per share by number [33:48] of shares to receive an amount okay the [33:52] behavior is the requirement these are [33:55] TDD requirements as described by Kent [33:57] Beck originally that is what your test [34:02] should be looking at not I need the [34:05] method on myclass for exchange rates [34:07] that's nothing that's not nothing to do [34:09] at EDD so the idea behind test [34:16] environment has always been that we are [34:18] essentially saying what is what should [34:20] the api of our code look like if i am an [34:24] external consumer of this code and i [34:26] come to call it [34:27] what kind of API would i find useful [34:31] your testing as much the usability of [34:34] the api how you interact with it as you [34:38] are essentially in designing that public [34:40] facing API is anything else okay so you [34:42] tell yourself a story about how this API [34:44] should look if you were a consumer the [34:46] test becomes the first consumer of your [34:48] code that essentially says what should [34:51] this look like and how should it work [34:52] right that is the objective a test [34:55] driven development so Kent uses unit [35:01] tests in a very specific way right and [35:03] simply for hit when he uses the phrase [35:04] all he means is that you should better [35:07] on all your tests together there's one [35:10] suite without the impact with effect we [35:12] do that without one test being run [35:14] impacting the other test the unit of [35:16] isolation is the test and a lot of [35:22] mistakes have been made because people [35:23] believe the unit of isolation is the [35:25] class under test it is not okay so all [35:32] these people mocking because they say [35:35] well you got to have the class isolated [35:38] when you test it these all have to be [35:40] mocks you can't interact with anything [35:42] else you got a mock all the classes [35:44] dependencies right because otherwise [35:45] it's not a proper unit test you are [35:48] wrong the unit VIN permit the unit of [35:52] isolation is the test not the thing [35:55] under test so one of the problems is we [36:01] would you know people were using [36:03] definitions from classical testing and [36:06] the thing is essentially unit tests [36:08] referred to decided over a module right [36:09] listen wives are confusing phrase [36:11] because people talked about unit test [36:12] they were saying take a module so say [36:14] and.net term is an assembly or something [36:16] right take that and then essentially [36:18] test it on its own then an integration [36:21] test is I've got a couple of modules [36:22] talking to each other and then [36:24] essentially a system test is talking all [36:26] and while just talking to each other so [36:28] when can they used this phrase unit test [36:30] what he was really talking about was a [36:31] module being tested okay [36:33] it's an unfortunate phrase because [36:35] people then said well the unit is a [36:36] class and came up with a hole so our [36:39] practice which basically has been [36:40] negative towards RTP practice you may [36:46] choose to avoid using certain [36:50] interactions and testing right databases [36:52] file systems those kind of things right [36:55] but the reasons Ken gives for that are [36:58] slightly different for the ones you [36:59] think it's not about isolation it's [37:02] about my selection only in the sense [37:03] that if I touch a database there [37:06] one test may impact another test because [37:09] if I add some rows in the first test [37:11] when I read them in the second test that [37:13] may be impacted by basically the order [37:15] they running so I have to make sure that [37:17] between these two tests I'm isolated [37:20] from that kind of problem which may mean [37:22] I choose to use something like an [37:24] in-memory database unless we tear that [37:25] down rapidly and provide a fresh one for [37:28] each test right [37:29] but it's the isolation of the test that [37:31] is driving that not the isolation of the [37:32] code under test [37:33] same with fascism everything else and [37:35] the other cut the issue is speed right [37:38] tests have to run fast otherwise [37:41] developers don't get fast feedback so [37:44] really I want my test suite to run in a [37:45] couple of minutes and it's quite [37:47] possible if I'm talking to real hard [37:48] dependencies that becomes hard so I may [37:51] wish to essentially swap those out for [37:52] the purpose of a CD speed but those are [37:54] the reasons for it but there are others [37:57] if there is no shared fixture problem [38:01] it's perfectly fine in a unit test to [38:03] talk to a database or a file system and [38:07] that happens all the time people are [38:09] Chemex testing so people telling the you [38:14] contort it by some unit test they are [38:16] wrong okay yeah okay focusing on the [38:28] methods crates tested that are hard to [38:30] maintain okay because essentially when I [38:34] look at that test what I see is somebody [38:37] describing how you interact with a [38:38] method that's what we get this problem [38:41] when we come back to our test they're [38:42] very hard for us to understand because [38:43] of kind of going well what does his test [38:44] doing well we've got a method in [38:47] isolation so it's very hard to [38:48] understand what it's doing if the test [38:50] tests of behavior it's very easy to [38:52] understand what that test is doing [38:54] because it says this behavior is [38:56] asserted to be true in the system [38:58] now that test is red because I've [39:01] changed another part of my code I can [39:02] either say well that should still be [39:04] true or perhaps no that isn't true [39:06] anymore that behaves been modified now [39:08] rather than you require which means this [39:10] behavior has also changed and I've just [39:12] seen an impact of that behavioral change [39:18] and when we have implementation details [39:20] exposed now the words essentially all [39:23] those things that make up how our API [39:25] actually works that's when you get this [39:28] problem effectively of being hard to [39:29] change particular few use mocks [39:31] so when I use mocks effectively when I [39:34] said that's just their class on the test [39:36] and I know about all the cool set of [39:38] mocks the problem is when I change [39:39] something that changes those all the [39:41] basically assertions break and if I [39:43] essentially therefore know too much [39:45] about the implementation details then [39:48] I've coupled my tests to my [39:50] implementation and I can't change one [39:52] without changing the other all right so [39:57] let's talk about how we get there [39:59] because the way we do this is really [40:01] simple and it's red green refactor who [40:04] and who knows who thinks they understand [40:05] red green refactor okay great [40:08] who does red how many of you make sure [40:11] that essentially okay very few of you [40:14] who does Green Party most of you who [40:17] then it always does the refactoring step [40:20] some okay reflective it's the key thing [40:24] right the test that doesn't work okay [40:27] this is important there are no tests for [40:29] tests I have to demonstrate that my test [40:31] will fail in the absence of correct [40:34] implementation right I've always a [40:35] stumble across this a few times had the [40:37] problem or essentially had a test that [40:40] would always pass and so you know [40:43] essentially until I got that red phase I [40:45] wouldn't prove anything by writing the [40:47] right in my code and having it passed [40:48] again because it's always gonna pass [40:50] again Green make the test work quickly [40:54] committing any sins basically necessary [40:56] in the process this is really important [40:59] what I'm doing is making the test passed [41:02] by the quickest process possible okay a [41:05] good solution here cut and paste code [41:08] from Stack Overflow right this is where [41:12] the duct tape type area mcduck wins [41:14] because you're afraid to do that and [41:16] he's not do that [41:20] okay now basically make good code but [41:27] it's three steps I'll test make it [41:34] compile unseal it avails make it run [41:37] reification centuries refactoring okay [41:42] when you write your foot implement your [41:46] test and you go to your green phase what [41:49] you are seeking to do is understand how [41:51] to solve the problem you want to [41:56] basically as fast as possible get [41:59] something that satisfies the behavior I [42:01] want to add two currencies in different [42:04] amounts from different from different [42:06] and I want to basically look at the end [42:08] result right in one currency what I want [42:12] to do as quickly as possible is figure [42:13] out how to do that in my code I just [42:16] write a good transaction script one line [42:18] with one line after another [42:19] don't try and basically put it into [42:22] lovely classes don't try and essentially [42:24] put patterns in there just write line [42:26] after line of code until it works okay [42:29] and that code can be dodgey if you want [42:32] to be probably should be your goal is to [42:36] as fast as possible get that test to go [42:38] green because you have managed to [42:41] understand how you will implement the [42:43] requirements at this point you are [42:47] trying to determine how you will [42:48] implement the requirements and you can [42:51] figure out the answer to that by going [42:54] green go to stack overflow copy some [42:57] code right you are explicitly enjoined [43:03] in this step to write non [43:07] well-engineered code you want to be the [43:10] duct tape programmer you want to be that [43:13] really annoying guy on your team who [43:15] writes bad code emulate him do what he [43:19] is doing to get out the door fast you [43:21] will now be keeping up with him you've [43:23] added the test but he was automated he's [43:25] probably having done manually a few [43:27] times you are moving the same right [43:29] here's now [43:34] mr. Blake says for this brief moment [43:37] speed Trump's design I want to get there [43:42] as fast as I can okay Kent's point is [43:47] essentially you can't do two things at [43:49] once easily [43:50] you can't both understand the solution [43:52] to the problem and engineer the code [43:55] right one of two things will go wrong [43:57] you will either over engineer your [43:59] solution beyond watch the test actually [44:02] requires boy you'll get some kind of [44:06] analysis paralysis right but the first [44:09] thing you do to solve the problem with [44:10] the code [44:12] okay so when we're getting our clean [44:16] code you know right okay so we believe [44:18] in clean code we don't want to have duct [44:22] tape programmer code oil long I'm we're [44:23] going to do that the next phase the [44:25] refactoring step and the produce clean [44:28] code so if Kent basically near the key [44:31] drivers duplication right you see [44:33] duplication you remove that that starts [44:34] to reengineer your code well and that [44:37] works there you can add into that I [44:38] think Fowler Fowler's book refactoring [44:41] alt about that bit later look for code [44:43] smells ok look for smells in your code [44:45] that indicate they're the better ways of [44:46] doing this and Yoshioka is keys point is [44:51] if you want to think about using these [44:53] are design pans this refactoring step is [44:56] when you identify them because now you [44:57] have a clear idea what your solution is [44:59] you can tell whether a pattern is [45:01] appropriate or essentially whether [45:03] you're using patterns inappropriately as [45:05] a solution ok when you refactor and this [45:10] is the really important part you do not [45:12] write any new tests refactoring is a [45:17] process of safe moves that let you [45:21] essentially change the design of the [45:23] code they do not change its behavior [45:27] your behavior is covered by the original [45:30] test the original test tells me I'm [45:33] adding two amounts in two different [45:35] currencies and getting the final result [45:36] the behave that code them implementing [45:39] produces that result if I use safe [45:42] refactoring steps to get [45:44] doesn't matter how much I changed [45:46] implementation details I am still [45:48] covered by my original test code [45:51] coverage tools help you here because you [45:52] don't want to introduce for example new [45:54] conditionals speculative code at this [45:56] point need to be honest and when you do [45:58] that you need to have new tests to cover [46:00] those new behaviors because generally [46:02] you've got a conditional because you [46:03] have a new behavior that you're trying [46:04] to produce but do not write new tests at [46:07] this point this results in two things [46:09] one you will write less tests so you [46:12] will move faster the refactoring step [46:16] shouldn't take you so long that you four [46:17] miles behind this duct tape with the [46:19] programmer right you will keep up in the [46:22] way that the lean software guides and [46:24] your managers want you to because you [46:26] are not saying oh I'm creating a new [46:28] class here I'm doing extract class [46:30] because I believe that essentially these [46:32] things should be Anouk should be a class [46:33] and my implementation don't write a test [46:36] for that class right if you have a [46:38] language to support some visibility for [46:40] example Java or C in a c-sharp and [46:42] you've got things like protected [46:43] internal private cetera public right in [46:46] c-sharp for example most needs to be [46:48] internal and never exposed and as such [46:52] the test should not be coupled to them [46:54] you should be at liberty to change their [46:57] implementation whatever you like and no [46:59] test will fail because no test is [47:03] coupled to hate on their implementation [47:04] and this is a key to understanding this [47:06] right in the refactoring step do not [47:09] write new tests you're not introducing a [47:13] couple of classes if you basically keep [47:16] thinking mmm I need to increase in [47:18] public class that only smell you may be [47:20] looking at is that essentially in your [47:22] solution there is some new thing that is [47:25] also power I PI ok that will itself be [47:30] exposed as part of the API have your [47:32] module in that case that's probably an [47:36] appropriate time to think about a mock [47:38] for use in your test and later go and [47:40] implement that other part your API that [47:42] you're currently missing right but focus [47:46] initially on trying to avoid writing [47:47] these extra tests [47:50] it says it right dependencies the key [47:53] problem and software right coupling is [47:57] worse enemy for you than dry coupling is [48:01] what kills all software so stronger to [48:04] decouple your tests from your [48:07] implementation details focus on your [48:09] public contract your stable API and [48:11] leave your implementation details free [48:13] of tests and avoid heavy mocking this [48:21] allows you to meet the promise to [48:23] refactoring you will refactor your code [48:24] and your tests won't break right I've [48:26] seen this work since I changed this [48:28] practice it's been very easy for us to [48:30] refactor implementation details to [48:33] figure out want new ways of doing things [48:34] we changed the whole way we did for [48:37] example a modeling between of an [48:40] aggregate without any test breaking [48:43] because the behavior hadn't changed our [48:46] choice about how to implement it or [48:48] changed so test behaviour don't test [48:53] implementations these mics are are [48:57] annoying if you wear glasses right okay [49:04] but des tournelles don't make these [49:06] puppets a test if you find yourself [49:08] making things public in order to get it [49:11] under test by implementation details ask [49:13] yourself if that genuine is a part of [49:14] the of the contract of your module if [49:17] it's not don't [49:18] it's just implementation detail why did [49:19] you surface it for testing if you are in [49:22] dotnet land and you use the assembly [49:25] attribute internals visible to and list [49:28] your test assembly so that you can get [49:30] your internals tested do not do that [49:32] write your internal should not be [49:35] visible to the test because they're not [49:36] under test okay so preserve your [49:42] presentation hiding have a public API in [49:44] the internals right this is Fallon's [49:49] book of refactoring actually came out [49:50] before Kent's book on TDD and it talks [49:52] really about code smells and says here [49:55] are some safe steps to be able to [49:56] effectively refactor basically your code [49:59] when you see these smells and one of the [50:02] key things to understand is you know [50:03] very explicit okay we are changing [50:06] implementations it has not public [50:08] interface anyone ever use the DevExpress [50:11] tools as opposed to resharper's tools [50:12] for refactoring so till express what I [50:16] want at least one e DevExpress have set [50:18] of tools which basically had a code [50:20] plug-in this was your studio and these [50:21] to offer refactoring and don't express [50:24] his tools and she didn't know some of [50:26] the guys the workmen were very clear [50:27] right you cannot refactor a public [50:31] method on a public class they wouldn't [50:34] let you do it because that's change [50:38] that's not refactoring because you don't [50:41] know the impact of that now later they [50:44] said ok you can press the button marked [50:46] unsafe refactoring because everybody [50:50] else lets you do it and we're being [50:52] killed in the middle in the marketplace [50:54] but also because they said well you may [50:56] say to me the only consumers of this [50:58] public API within the span of the [51:00] solution and therefore essentially I [51:03] understand who all the coolers are and I [51:05] can then refactor them so if I'm doing a [51:07] rename I know who I'm going to break but [51:10] the goal was originally don't do this [51:12] right don't change a refactoring is not [51:17] changing your public interface it's [51:19] changing how you chose to implement that [51:20] it is a safe small change there are [51:23] recipes to do them that's why tools [51:25] automate them and they're safe in the [51:27] sense that your code is not basically [51:30] going to CH it's not going to negatively [51:32] be impacted if you extract a method it's [51:35] a safe change ok his list of user smells [51:41] it's not wearing what this talks about [51:42] but those are the kind of things that [51:45] you're looking for and those are the [51:48] things that drive you in your [51:50] refactoring step to do things like [51:52] extract a class out of this basically [51:54] what transaction stripped body has a [51:55] method out this body right look at [51:58] basically where you've got feature env [52:00] whether you're doing some kind of dot [52:02] syntax etc right and I just you joshiya [52:08] karaeski in this book said there's a [52:11] problem with the patterns and the [52:12] problem with patterns is people are [52:14] getting pain happy lady sipan's [52:17] everywhere and start applying them and [52:19] in many cases what they've done is [52:20] applied them in the wrong context or [52:21] made their software over complicated [52:23] when didn't need them well Joshy [52:24] Aquarius you said is the ideal step for [52:26] applying patents is the refactoring step [52:28] because at this point I know what the [52:30] solution is a written one [52:33] it's an awful solution because I was [52:35] told to make an awful solution and at [52:38] this point now I can see I can clear up [52:39] by making a pan I can see that actually [52:42] this is which is the command pattern I [52:44] need that here this is the template pan [52:46] I need that here but I know that now [52:48] because I've already implemented it once [52:50] badly and even in CHEM box original book [52:56] he identifies patterns you may want to [53:00] refactor too but so this is part of the [53:02] original TDD is not new stuff okay so [53:07] what about ports and adapters so this is [53:11] unfortunately the common way that [53:14] testing occurs in a lot of organizations [53:18] before we release the software is a big [53:20] big wave of manual testing going on [53:22] everyone's getting into the system [53:23] running manual scripts et cetera doing [53:25] some kind of conference releasing right [53:27] then below that there's some sort of [53:30] scientist who has who has big Suites of [53:31] selenium tests that they run against [53:32] their software here not so bad that's [53:35] funny of you okay and then we have [53:37] integration tests and the bot we have a [53:40] few developer tests right this is a bit [53:42] of a disaster [53:43] why well first of all this is really [53:46] expensive unrepeatable so you really [53:47] want to automate your way out of that [53:48] situation this one is very problematic [53:51] and I'll tell you why [53:52] um if I change my UI to give it a nice [53:57] classy new look and feel that's much [53:58] more you know 2017 rather than 2008 [54:02] right I may not be changing any other [54:05] behavior my software whatsoever they do [54:08] exactly the same thing that did [54:09] yesterday I've just cleaned up the UI [54:11] but these tools all break okay but I'm [54:19] driving them to test the behavior of my [54:20] system but the tools all break because I [54:22] changed the way that my widgets work [54:25] okay so they're very fragile freshman [54:28] stone they also run really slow quite [54:30] when you run overnight and then you get [54:31] the blame game in the morning someone's [54:32] job in the organization is to determine [54:34] who is to blame for last night's test [54:35] failing right if you have find yourself [54:37] in the situation where you have somebody [54:38] whose job every morning is to determine [54:40] who's to blame the test failing you are [54:42] doing it wrong I know that cuz I've done [54:45] that and that's wrong okay all the [54:47] mistakes I'm telling you about right [54:49] I've made those don't worry I'm not [54:51] gonna go at you because I think I'm [54:53] smarter than you I only know this [54:54] because I've made every single one of [54:56] these mistakes right so what you want is [54:59] what's called the testing permit the [55:01] majority of your code should be tested [55:03] by your developer tests a small amount [55:06] of code up here should test your widgets [55:08] work correctly and in between there's a [55:10] certain amount of integration testing [55:11] where the two gets hooked together every [55:13] bit of a scam in the middle I'll come [55:14] back to that later okay so this is a [55:18] hexagonal architecture it's gained [55:20] popularity recently people at Bob Martin [55:22] the coordinates the clean architecture [55:23] simply quit an onion architecture there [55:26] are various names this architecture [55:27] various attempts a CO discovery of the [55:30] same thing all right what happens okay [55:32] in the middle we essentially have plain [55:35] old C sharp objects plain old Java [55:37] objects pent-up Python objects are not [55:39] here that heard that phrase Popo that's [55:40] also slang well for the police in the UK [55:42] so be careful about using that word but [55:45] yeah but these things are devoid of any [55:48] technology concerns so don't know how to [55:50] talk to the database they don't know how [55:51] to talk to the web they're just [55:53] basically completely plain on objects [55:55] without any technology concerns in on [55:58] the outside what's called the adapter [56:00] like their data learners have to talk to [56:02] the outside world it has all the [56:04] technology concerns so our primary ones [56:06] over here [56:07] things like basically an HTTP adapter so [56:09] something like basically your web [56:10] framework Web API Django Sinatra [56:13] effective your Flash matte effects etc [56:15] or a GUI adapter so like web forms or [56:18] you know Jagger's would like that right [56:21] and they talk basically to your domain [56:23] over here we've basically got a database [56:25] that your domain used to persist itself [56:27] from here we've got a sense the idea of [56:28] things like sending messages [56:29] everything's that rabbitmq or I'm [56:31] essentially that kind of thing right or [56:32] emails in between is what called the [56:35] port's layer to the port there literally [56:37] is like a port in an operating system it [56:39] was a door it's the door which [56:40] essentially allows [56:41] once one [56:43] to be crossed here it's the door that [56:46] essentially says well my HTTP code he's [56:49] talked to my domain it would go through [56:50] this door some kind of facade layer [56:52] which says we talk to it over here it's [56:55] saying I'm like my domain code needs to [56:57] basically persist a database so I have a [56:59] door that lets me talk databases some [57:01] kind of repository model happen perhaps [57:04] now okay you got if you five minutes [57:09] more and I'll just run through these [57:10] quickly right [57:13] apologies for over random static okay so [57:16] the port here is the ideal point to do [57:20] York for your tests the test talk to [57:23] that port layer not to the internals [57:25] here because the port is how everything [57:29] communicates with your code and also as [57:31] everything your code raised like events [57:32] or cause the database okay so the poor [57:36] is the point view to make your calls [57:42] integration tests are really just [57:44] checking then essentially you can talk [57:46] to the data credit you're configured [57:47] correctly on this adapter don't test [57:49] adapters themselves they use your [57:51] third-party code and a system test just [57:54] checks that you can call from the [57:55] outside in right and that is essentially [57:57] how you do it okay make two quick points [57:59] I try very big gears so you may say [58:02] that's all great in testing the API is [58:04] fine but I sometimes like TDD to help me [58:07] understand how something's working and I [58:10] want to basically drill down using my [58:12] TDD into the details okay [58:14] so connect is notionally gears it has [58:17] how it work its motion I have five gears [58:19] in fourth gear it's the price you just [58:21] described to your testing public API and [58:23] you're getting the basically the full [58:25] script out and your refactoring that [58:26] right in fifth gear it's kind of like [58:29] the implementations very obvious so [58:31] actually that refactoring step when I [58:33] write my code rather than write the [58:35] sinful code I just write the correct [58:37] answer so it's regular refactoring step [58:39] be careful here if basically you find [58:42] that your coverage starts to blow out [58:44] when you put your tests shift down to [58:46] fourth because you're probably writing [58:48] too much speculative code you haven't [58:49] got tests for if what I find though is [58:52] that I'm saying I don't know how to go [58:55] green [58:56] I'm not really sure how to implement [58:58] this and I want to fill my way with the [59:00] aid of tests to probe me a bit more and [59:02] shift gears down a write some tests to [59:04] help you do that you could also use [59:06] things like a rapid beeping rapidly [59:07] these records and language like Python [59:09] except for this technique of just [59:10] feeling your way bit but feel your way [59:12] but at the end of that process [59:14] delete the tests the tests help guide [59:17] you but now they're a burden to somebody [59:19] else coming along person coming along [59:21] who wants to basically change the [59:22] implantation details probably means that [59:24] your way of doing it they're changing it [59:26] altogether your tests and they're [59:28] something they don't understand we're [59:29] just protecting your implementation [59:31] details throw them away at the next [59:33] person coming along write tests if they [59:35] needed to take the implementation okay [59:39] ATD the well very quickly [59:42] Kent Becker Brittany said don't do it [59:44] because it has T problems [59:45] customers are not interested and it's [59:50] very expensive to do right [59:51] he later 7120 had a book about ATP in [59:54] this series maybe I was wrong maybe [59:55] actually you need this because [59:57] programmer tests are problem they don't [59:59] focus on the gist of the system [60:00] James Shaw who wrote fit said I he was [60:03] only guys that wrote one of these tools [60:04] says I don't do it anymore they said the [60:07] reason in fact over the years that [60:08] customers essentially don't care and [60:10] there are significant maintenance burden [60:12] rights all these tools that fit gherkin [60:13] that that kind of thing right [60:16] he just says actually if I have [60:18] customers again that help developers [60:20] drive the developer tests so the unit [60:22] tests are informed by the developers [60:25] talking to the customer and write the [60:27] appropriate unit tests over time I used [60:31] to believe in these test days to think [60:33] that was worth writing at you know fit [60:34] and gherkin test because I think you can [60:36] respectfully test because I thought it [60:38] helped keep developers honest we know [60:39] what done done was but over time I've [60:42] come around to realize that probably [60:43] what we were solving was the problem of [60:45] how we're doing our unit testing because [60:47] our unit testing was focusing methods on [60:49] classes we had nothing that told us [60:51] about behaviors but we actually to do a [60:54] shift our TDD practice through about [60:55] behaviors and then we can drop a TDD [60:58] altogether just write developer tests in [61:00] the same language using to implement and [61:02] drop these horrible translation layer [61:04] between natural language and your code [61:06] base [61:08] Avram development [61:09] I mentioned briefly the Dan basically [61:11] the craziest Engel had this original [61:13] post which basically it describes [61:15] testing tree about behaviour it is now a [61:18] full-scale agile methodology it's worth [61:20] it being you may be interested in that [61:22] but this talk is not about behavior and [61:24] development as a methodology right it's [61:27] taking that original kernel idea that [61:29] Dan had a saying test behaviors don't [61:31] test methods and classes mocks briefly [61:36] mocks are useful when a resource is [61:39] expensive to create and represents a [61:41] shared fixed a problem but the problem [61:43] with mock objects essentially is people [61:47] have used them to isolate classes don't [61:49] do that and especially don't do this [61:51] thing where effectively you say I'm [61:53] going to make sure junior developers [61:54] have done the right thing by by mocking [61:56] I forced them to use mocks so I can see [61:59] how they implemented the method it just [62:01] couples your test to your software means [62:03] you can't change it it's never worth it [62:07] they also specified software generally [62:13] speaking IOC containers are overused [62:14] Chinese move live as possible [62:17] only use them because your framework [62:19] demands that you actually do it because [62:20] they don't understand how to create [62:21] objects that you have if your framework [62:23] was good it would actually ask you to [62:25] give it factories not I did not an IRC [62:26] container and you might implement your [62:28] framework have fought in terms and I see [62:30] container if you find yourself [62:32] implementing an IOC registration in your [62:36] test something has gone horribly wrong [62:39] okay bit like ORM as we overused IOC [62:43] containers please avoid that okay right [62:52] quick summary reason tested behavior and [62:56] basically because we as a new behavior [62:59] my dirty code to get green and refactor [63:01] no need to test three factors internals [63:04] and privates they're open sets against [63:07] tests right on a port and second [63:08] architecture add integration tests only [63:11] really to extent you're covering [63:12] basically our ports implemented and [63:14] don't mock internals processor adapters [63:15] are not one little bit missing asset [63:18] defended cup words okay I rather had [63:20] type of questions I'm sorry [63:21] III [63:22] gonna let you'll go please come up to [63:24] the front if you want to ask a question [63:25] at the end I'll come and find me during [63:26] the conference I'll have to talk about [63:27] the stuff you can see this talk online [63:32] versions we've given the place like MDC [63:34] so you want to share with your [63:35] colleagues you can you can go and dig [63:36] out the video and I think these guys are [63:38] pretty bidding it too so you can spread [63:40] the free edge with your colleagues it's [63:42] worth going looking that conversation [63:44] between Kent Beck and DHH need to be [63:46] Martin Fowler as well for a similar [63:48] conversation about how it's all gone [63:49] wrong but thanks very much thank you [63:51] [Applause]