Ah yes, AI, our favorite topic. Give an agent an empty folder and it can spin up an API, wire in a database, and add a test suite before I have finished reading the plan. I like the speed. I get nervous when all that code wraps around decisions the team did not make.
Testing is where I keep getting hung up. I can ask an agent to “add tests for OrderService” and get a green suite, but that prompt says nothing about our architecture or where we draw the test boundary. It does not tell the agent whether we use ports and adapters or whether mocks are part of our TDD style. If I leave those choices out, I have handed them to the tool.
The result can look fine in a PR. Each dependency has a mock, every method has a test, and coverage goes up. Then somebody renames an internal method and half the suite fails while the behavior of the application stays the same. We got tests, though.
Coverage gave us a number
Teams had this problem before AI. I have seen engineers write tests to reach a coverage threshold because the pipeline asked for 80%, and the number gave them a clear target. They made a line run, covered the other branch, and got CI to turn green without having to explain why the test existed.
An 80% line coverage gate tells me which code ran during the suite. I need the engineer to tell me which behavior they validated. A test that calls every getter can raise the number, but I do not get much confidence that an order cannot ship twice.
Testing comes with enough pain that I understand the temptation to chase the number. Setup takes too long, fixtures break when a type changes, and some integration suites give you enough time to reconsider your career. Flaky tests make it worse because the team learns to click rerun instead of reading the failure.
AI can take a decent bite out of that work. I can use it to build a fixture, clean up repeated setup, or draft cases around a boundary. I can use it to generate forty shallow tests and reach 80% before lunch, which puts us back where we started. Before I care about the number, I want the engineer to explain which failure they are guarding against and which behavior needs to survive a refactor.
The prompt needs the architecture
Alistair Cockburn’s original description of hexagonal architecture puts the application inside ports, with adapters connecting it to things like a UI or database. That setup lets a test drive the application without booting the production database, which changes the test I want.
An agent looking at a service with two dependencies may produce this:
it("places an order", async () => {
const inventory = {
reserve: vi.fn().mockResolvedValue(true),
};
const orders = {
save: vi.fn().mockResolvedValue(undefined),
};
const service = new OrderService(inventory, orders);
await service.place(order);
expect(inventory.reserve).toHaveBeenCalledWith(order.items);
expect(orders.save).toHaveBeenCalledWith(order);
});
This test checks the conversation between three objects. That may fit the way the team works, or it may tie the suite to the current implementation and make a refactor much louder than it needs to be.
If the team treats placeOrder as an application port, I may want to drive that port with an in-memory adapter and check the result:
it("rejects an order when stock is gone", async () => {
const inventory = new InMemoryInventory({
sku: "watch-01",
quantity: 0,
});
const app = createOrderApp({ inventory });
const result = await app.placeOrder({
sku: "watch-01",
quantity: 1,
});
expect(result).toEqual({
ok: false,
reason: "out_of_stock",
});
});
The second test knows less about which methods the application calls because it checks the behavior at the port we chose. I prefer that version when the port is the contract I care about, but the agent cannot pull “hexagonal architecture” out of thin air.
If the repository has no clear boundary or example tests that demonstrate the pattern, I need to put that context in the prompt. An engineer unfamiliar with ports and adapters will not know to include it, and better autocomplete does not teach them which boundary the team cares about.
I need to say which kind of TDD
“Use TDD” sounds specific until two engineers start writing the first test. Kent Beck describes TDD as a loop where the programmer chooses the next test, sees it fail, changes the code, and refactors. The important word for me is “chooses.”
There are different ways to make that choice. In Mocks Aren’t Stubs, Martin Fowler describes classical and mockist TDD. A classical approach uses real collaborators when they are easy to use. A mockist approach uses mocks for objects with interesting behavior and verifies the messages between them.
Both approaches can produce solid code, but they push the design in different directions. If I tell an agent to use TDD without explaining the style, it will copy whatever signal it finds in the repository. A codebase full of mocks gets more mocks, and a folder of slow integration tests gets one more. I want the engineer to understand the pattern they are extending instead of assuming the agent picked the right one.
AI is useful once we pick the test
Once I know the behavior and the boundary, AI can save me a bunch of time. I can give it an acceptance rule and ask for a table of cases, have it build the fixture, or replace repeated setup with a test builder. If I am working on a bug, I can give it the failing input and ask for a characterization test before touching the fix.
I use it to challenge a test too. I might ask which cases I missed around a date boundary, whether a mock couples the test to an implementation detail, or if an in-memory adapter can replace the database setup. Those prompts make me look at a decision with the tool instead of asking it to fill the folder with more test files.
I want the engineer to run the generated test before changing the implementation and watch it fail for the expected reason. If a broken fixture causes the red test, we have not validated the bug. If the test stays green after removing the fix, we have managed to automate nothing.
I ask about the test in the PR
In an AI-assisted PR, I care less about which lines came from a model than I do about whether the author can explain the test. I ask which behavior it protects, why the test sits at that level, and which regression will make it fail. “Coverage was low” tells me how the work started, but we have more to talk through before I approve it.
For my team, AI fits into the day-to-day work as a fast pair for setup and case generation, or as a way into a test suite an engineer has not seen before. The engineer owns the architecture and the validation. If they cannot explain why the test uses a mock or crosses a database boundary, we pause before generating another file.
AI can type most of the test. I want enough understanding in the PR that we do not end up with 80% coverage and a green check, then stare at each other when somebody asks what the suite proves.