Every QA team wants a reliable test suite. But somewhere between sprint planning and production deployment, bad habits creep in. You end up with tests that break randomly, coverage numbers that look impressive but catch nothing, and a team that has stopped trusting the test results altogether.
These aren’t just annoyances — they’re testing anti-patterns: systemic mistakes that silently undermine your entire quality assurance effort. Kostis Kapelonis catalogued 13 of them in his classic 2018 article, and nearly a decade later, they’re still everywhere.
Here are the four that do the most damage — and what to do about them.
1. The Unit-Test-Only Trap (Anti-Pattern #1)
Your application has hundreds of unit tests. Every method is covered. The CI pipeline glows green. But somehow, bugs still slip into production.
This is the unit-test-only trap. Teams fall into it for understandable reasons: integration tests are harder to set up, slower to run, and more likely to break. Unit tests, by contrast, are fast and predictable. So teams double down on what feels safe.
The problem? Unit tests only verify isolated pieces of code. They can’t catch integration failures — database transactions that silently roll back, API contracts that drift out of sync, or deadlocks that only happen under real concurrency. These are the bugs that wake you up at 3 AM.
The fix: Bookmark the test pyramid. Unit tests cover business logic. Integration tests verify that components actually work together. You need both. If setting up integration tests is painful, fix the setup process first — containers and infrastructure-as-code have made this dramatically easier than it was even five years ago.
2. Testing Implementation Instead of Behavior (Anti-Pattern #5)
This one is subtle but devastating. A developer writes tests that check how the code works — internal fields, private methods, specific object structures — rather than what the code should do from the user’s perspective.
Then the code gets refactored. Suddenly 40 tests break, even though the behavior hasn’t changed. The team now faces a choice: spend hours fixing tests that shouldn’t have broken, or disable them and move on. Most teams choose the latter — and the test suite starts rotting.
The fix: Write tests against expected behavior, not internal state. Instead of checking that a customer object has a type field set to 1, verify that a registered user can access premium features. Mock collaborators, not implementation details. When the internal structure changes, behavior-focused tests keep passing — because the feature still works.
3. The Code Coverage Obsession (Anti-Pattern #6)
Somewhere, a project manager is demanding 90% code coverage. Somewhere else, a developer is writing meaningless tests that verify getters and setters just to hit the target. Neither of them is improving quality.
Code coverage is the most dangerous metric in testing because it feels objective. It’s a number, it goes up, and stakeholders love numbers that go up. But 100% coverage doesn’t mean zero bugs. It means every line was executed — not that every line was tested meaningfully, or that edge cases are covered, or that the tests assert anything useful.
The fix: Replace coverage targets with better metrics. What percentage of your developers actually write tests? (It should be 100%.) What percentage of production bugs get a regression test written before the fix is merged? What percentage of your tests verify behavior rather than implementation? These are harder to automate, but they actually measure quality. If you must set a coverage number, follow the Pareto principle — the 20% of your code that causes 80% of your bugs is the part worth covering.
4. Flaky Tests: The Trust Killer (Anti-Pattern #7)
You commit code. The CI pipeline runs. Three tests fail. You investigate — and find that two of them pass when you rerun the pipeline. The third one is a legitimate regression, but you wasted 20 minutes figuring that out.
Flaky tests destroy trust faster than any other anti-pattern. When developers can’t distinguish between a real failure and a random glitch, they stop paying attention to test results altogether. From there, it’s a short slide to ignoring genuine regressions — the very thing your test suite was meant to prevent.
The fix: Flaky tests are almost always integration or UI tests with timing issues, shared state, or environment dependencies. Quarantine them into a separate suite that runs independently. Fix the root cause — retry logic, proper teardown, deterministic test data — rather than slapping retry(3) on top and hoping for the best. A small rock-solid test suite is infinitely more valuable than a large one nobody trusts.
Building a Test Suite That Actually Works
The full list of Kapelonis’s 13 anti-patterns covers everything from treating test code as a second-class citizen to treating TDD as a religion. But these four — the unit-test-only trap, testing implementation, coverage obsession, and flaky tests — are the ones that silently kill the most QA strategies.
The common thread? Intentionality. Don’t write tests because you’re supposed to. Write tests that catch real bugs, run reliably, and survive refactoring. That means fewer tests with higher signal — not the other way around.
Source: Software Testing Anti-patterns by Kostis Kapelonis (Codepipes Blog, 2018). Discussion on Hacker News.
Leave a Reply