Every software team writes tests — but not every team writes them well. Over the years, certain testing habits have become so common that they’ve earned the label of anti-patterns: practices that feel productive in the moment but quietly undermine your quality assurance efforts over time.
Originally catalogued by developer Kostis Kapelonis in his widely-shared essay on testing anti-patterns, these 13 pitfalls span everything from architectural blind spots to cultural problems within engineering teams. Here are four of the most damaging ones — and what to do about them.
The Unit Test vs Integration Test Imbalance
One of the most common anti-patterns is an unbalanced test suite. Some teams lean entirely on unit tests, believing that if every function works correctly in isolation, the whole system will magically hold together. It won’t. Unit tests cannot catch database transaction failures, broken API contracts between services, or deadlocks that only emerge when components interact.
The opposite extreme is equally harmful. Teams that rely exclusively on integration tests — often found in large enterprises where unit testing is dismissed as a waste of time — end up with test suites that are painfully slow, expensive to maintain, and combinatorially explosive. A service with just four modules of modest complexity (2, 5, 3, and 2 code paths each) needs only 12 unit tests for full business logic coverage, but requires up to 60 integration tests to cover every possible path through the system.
The fix is not to choose one over the other — it’s to use both where they shine. Unit tests verify business logic cheaply and quickly. Integration tests catch cross-cutting concerns that unit tests simply cannot see. Together they form a safety net that neither can provide alone.
Testing Internal Implementation Instead of Behavior
Nothing ages a test suite faster than coupling it to implementation details. When tests reach into private methods, mock out every internal dependency, or assert on the exact sequence of intermediate calls, they become fragile. A simple refactoring that preserves identical behavior suddenly breaks dozens of tests — and developers learn to dread touching the codebase.
The antidote is straightforward: test the what, not the how. Write tests against public APIs and expected outcomes. When you change the internal wiring of a component, the tests should still pass if the observable behavior hasn’t changed. This approach — sometimes called black-box testing at the unit level — makes your test suite an asset that supports refactoring rather than a liability that blocks it.
When Code Coverage Becomes an Obsession
Code coverage tools are useful indicators, but they make terrible managers. Teams that mandate arbitrary coverage thresholds — 80%, 90%, even 100% — often end up with test suites full of trivial assertions that inflate the numbers without improving quality. These tests verify that getters return what setters set, or that constructors don’t crash — and they create a false sense of security while adding maintenance burden.
Coverage should be a conversation starter, not a goal. A low-coverage module might signal genuine risk — or it might be a thin wrapper around a well-tested library where additional tests add no value. Focus instead on risk-based testing: identify the code paths where failures would hurt the most, and ensure those are covered thoroughly.
Treating Test Code as a Second-Class Citizen
In many organizations, test code is treated as an afterthought. It gets no code reviews, no refactoring, and no documentation. Developers copy-paste test snippets from Stack Overflow without understanding what their testing framework can actually do. The result is a tangled mess of duplicated setup logic, cryptic helper functions, and tests that nobody on the team fully understands.
Test code deserves the same engineering discipline as production code. Invest time in learning your framework’s capabilities — parameterized tests, fixtures, mocking libraries, and test categorization. Write tests that are readable, maintainable, and focused on one thing at a time. When a production bug is found, the first response should be to write a test that reproduces it before fixing the code.
Building a Healthier Testing Culture
The full list of 13 anti-patterns also addresses flaky tests, manual-only testing workflows, dogmatic TDD, and the problem of experienced developers giving testing a bad reputation based on past trauma from poorly managed test suites. The common thread is that testing is a skill — one that requires continuous learning, honest retrospectives, and a willingness to question habits that have outlived their usefulness.
The best teams don’t just write more tests. They write better ones. And they understand that a healthy test suite is not a static artifact — it’s a living part of the codebase that evolves alongside the software it protects.
Source: Software Testing Anti-patterns by Kostis Kapelonis (Codepipes Blog). Originally featured on Hacker News with 465 points and 166 comments.
Leave a Reply