Tests as Data Thoughtworks developers interviewed in early 2026 said they no longer trust their automated tests because AI coding agents refactor both production code and tests as features are added, according to a write-up dated 11 November 2026. The author, drawing on interviews with 26 developers at an organization heavily invested in AI coding agents, argues that when AI writes the code developers become its customers, so customer tests become the primary contract between humans and AI, and that moving that contract into test data rather than test code makes it easier to protect from unexpected modification by coding agents. The proposal builds on Extreme Programming acceptance tests, Fit test DSLs, specification by example, tabular tests from Go, and approval testing, arguing AI now makes custom DSL parsers and test runners cheap to write and maintain. Tests as data Ensuring AI-generated code does what we want Automated testing in the age of AI coding assistants is problematic: when AI agents write both production and test code, developers risk losing the assurance that automated tests protect against functional errors. I make three claims: 1 when AI writes the code, developers become its customers; 2 therefore customer tests, not developer tests, become the primary contract between humans and AI; 3 moving that contract into test data rather than test code makes it easier to protect it from unexpected modifications by coding agents. All of this optimizes for human-readable tests that establish a contract between humans and AI. While in the past DSL parsers and custom test runners were expensive to write and maintain, now AI makes them cheap. I build on established practices: customer-oriented acceptance tests from Extreme Programming, test DSLs from Fit and specification by example, tabular tests from Go, and readable textual representations of application state from approval testing. 11 November 2026 Introduction Problems with AI-generated code In early 2026, I conducted interviews with 26 developers working for an organization that was heavily invested in developing with AI coding agents. One of my questions was “do you trust our tests?” After all, the developers were from Thoughtworks, and Thoughtworks prides itself on the quality and thoroughness of our testing. Unfortunately, the developers invariably answered “ Well… I don’t trust our tests that much Because even if I review the tests when they are initially generated, as new features get added, the AI refactors both existing code and existing tests, so I’m no longer confident that the tests are still ensuring the right behaviour” . This got me thinking. As we learn to get more work done from coding agents, it’s becoming natural that AI writes most, if not all the tests. The practice of developers writing automated tests was a major breakthrough in software engineering in the late 20th century. The most practical way to do this is to write the tests before the code test-first and to write and make pass one test at a time test-driven development . This practice allowed teams developing business software to deliver nearly bug-free code, at a fraction of the cost of formal specifications and proofs. In 2026, a lot of code is written by AI coding agents, and those agents will generate tests even without asking, so one could hope that we can achieve the same level of correctness that human coders enjoyed with TDD. Unfortunately, it’s a common experience that this is not the case. In fact, there have been a number of reports published in early 2026 showing that the quality of code being deployed by organizations is getting worse. I don’t know for certain what the causes are of this widely reported decrease in external quality, but I think it’s plausible that one of the causes may be a decline in quality of the automated functional tests. Problems with AI-generated tests When we give high autonomy to AI to generate code, we need to trust the AI to write good tests, as the alternative of writing all the tests by hand is no longer realistic. Let’s get back to the problem reported by the developers who stopped trusting the tests when the AI refactored them. Why does the AI refactor the tests? Most likely, it needs to refactor them because the signature of a function changes as the code evolves, and this is expected to happen in the course of development, so the AI is not “wrong” when it does this. But reading AI-generated test code is even more boring than reading AI-generated production code, so it’s not realistic to expect developers to carefully re-read all changed test code at every step. Another problem with AI-generated tests is that they might be tightly coupled to the implementation. If we ask a coding agent to “cover module X with unit tests”, it will happily generate a lot of tests, and often these tests will use lots of mocks and stubs. The purpose of tests is to break when the code returns the wrong result, or produces the wrong side effects. Unfortunately, tests that use mocks and stubs heavily tend to break whenever the implementation of the stubbed and mocked code changes, even when the change is a harmless refactoring that preserves correctness. So these tests produce a lot of false positives. What’s worse is that these tests might not give us confidence that they will protect us when the code does the wrong thing. Instead of proving that “when input is X, output must be Y”, these tests might just prove that “when we invoke function A, it will invoke function B”, which is not useful, because it’s not proving that the code will produce the correct results. In fact, when we ask AI to cover something with tests, we’d like the tests to specify the correct behaviour, but most often, the AI will write tests that confirm the current implementation, including any bugs it may contain. So in summary, AI-generated tests are often problematic because: - The volume of test code is large - Test code is hard to read - The tests are coupled to the implementation, and - They might not even prove that the code is correct We could mandate that human developers write all the tests, and write good tests, but this will negate most of the expected economic benefit of coding agents, and is not realistic given where software engineering seems to be heading. But what if there was a way to write tests that is much easier for humans to both read and write? This is what we’ll cover in what follows. Problems with AI-generated specs A lot of AI-assisted development is done using some form of spec-driven development https://martinfowler.com/articles/exploring-gen-ai/sdd-3-tools.html : vendors recommend it https://claude.com/resources/articles/the-ai-native-sdlc-playbook and some SDD frameworks are very popular. While it seems clear that aligning with AI before development is very useful, e.g., by getting the AI to ask clarifying questions, there are problems with the generation of “specifications” written as prose in Markdown documents. One problem is that, again, the volume of generated prose makes it hard for developers to read it all. After a point, fatigue kicks in and we risk accepting the AI-generated plans after little more than a cursory glance, in what someone called cognitive surrender https://addyosmani.com/blog/cognitive-surrender/ . Another problem is maintenance: once a feature is complete, what do we do with all the specs? Some frameworks support maintaining a set of durable specs, but the risk, again, is that over time the volume of these specs grow too large for maintenance. A third problem is that large-language models do not comply with all of the detailed specifications; it can happen that the AI gives up on some parts of the spec in order to claim “done” sooner. Finally, and most fundamentally, English is ambiguous: while we can try to specify what we want as precisely as we can, natural language does not afford the level of precision that we get with programming languages. In short, prose specs can't be executed; tests can. What I am proposing Our goal is to have tests that exhibit these properties: 1. They predict success in production https://coding-is-like-cooking.info/2025/12/test-desiderata-2-0/ : when all the tests pass, it gives us confidence that the functionality is implemented correctly and it will behave as expected in production 2. They are resistant to refactoring 3. They are as easy to read as possible To achieve this, we propose these techniques: - Customer tests : write in the language of the customer. - Black box : test the system, or components of the system from the outside, without mentioning implementation details of the production code. - Tests-as-data : focus on the important details: the input, the expected output or side effects, and the test setup. In other words, the test should capture just the facts , without mentioning implementation details in the test code. - Test DSL : find the most straightforward ways to represent the input, the output and the system state in text that can be understood by a domain expert, and produces a clean diff when the test fails. When AI writes the code, the developers become in a way customers. The tests are a contract that establishes what the code should do for us, and moving tests to data files, interpreted by a relatively smaller test runner, makes them easier to protect from unexpected manipulation by AI. The same AI makes it easy to write test runners that run our test DSLs. Where does this leave TDD? Given how much humans benefit from TDD when coding, can we expect AI to do a better job when we ask it, or force it, to use TDD in its inner coding loop? Getting AI to follow canon TDD https://newsletter.kentbeck.com/p/canon-tdd is not easy, because AI usually generates lots of code before writing tests. On the other hand, it seems that coding agents perform better when we just tell them what we want and how to validate the result: adding instructions on how to achieve the result seems to make them perform worse https://danluu.com/agentic-testing/ . The techniques discussed here provide the “how to validate the result” that coding agents need. In a way, we could see this as a way to do test-first, with larger steps. Generally speaking, taking smaller steps makes development safer; however, the goal of this article is to discuss ways to enable taking larger steps safely. Core techniques Customer tests Developers writing tests was one of the original practices of Extreme Programming. The resulting tests are sometimes called “developer tests” because they support developers in ensuring that their code does what they think it should. Most developers call them unit tests . Another one of the original XP practices was Customer tests , also known as Acceptance tests . A customer test is a test written in the language of the customer; it describes what the software should do, from the point of view of the users. The idea is that a customer can understand what a customer test is trying to prove, so that these tests help bring alignment between customer and developers. The interesting part is that when a machine is writing most of the code, the human developers start playing more and more the part of the “customers” of the code. We are interested in specifying what the software should do, in terms that are as free of implementation details as we can, because we are delegating those implementation details to the AI. The circles of Extreme Programming practices. Image by Ron Jeffries https://ronjeffries.com/articles/017-02ff/new-framework-0/ , used with permission. That's why I think that customer tests are the primary kind of tests that we should rely on; especially when production code is AI-generated. Unit tests developer tests can still be useful, but we don’t treat them as the primary source of truth where specifying behaviour is concerned. Customer tests: - Define the functional content of a user story, so that when they pass, we know that the story is functionally complete - Protect us against regressions: once a customer test passes, it must always pass, until the business rule that it’s testing changes - Support refactoring, because they test behaviour, not implementation - Last but probably most important: they are a tool for ensuring alignment between developers and customers. The customers can read and understand the test, and can provide feedback to ensure that we are testing the right thing. On the other hand, customer tests might prove difficult to work with. Especially when they are implemented as end-to-end tests, they can be slow and flaky. There are ways to overcome these problems, and we will discuss them in the coming sections. Focus on what matters Let's see how we can fix the problem of hard-to-read tests. Consider the following simple example: @Test void defaultGreeting throws IOException, InterruptedException { HttpRequest request = HttpRequest.newBuilder .uri URI.create baseUrl + "/hello" .GET .build ; HttpResponse