Your Test Suite Is Probably Making You Slower

Your test suite is lying to you. It says your code works. It reports 90% coverage. It shows green checkmarks. Meanwhile, you're afraid to refactor, your CI takes 45 minutes, and "just rerun it" is part of your team's vocabulary.The problem isn't missing tests—it's that your existing tests have become a tax on shipping.

Every flaky test trains your team to ignore failures. Every slow test pushes developers away from running the suite. Every brittle test punishes refactoring. You built a safety net that's now a straightjacket.

TL;DR

  • • Test suites slow you down when they optimize for coverage instead of value
  • • Flaky tests train your team to ignore failures (and miss real bugs)
  • • Mocking your own code means you're not testing anything real
  • • Tests that break on refactoring are testing implementation, not behavior
  • • Slow tests don't get run, which means they provide zero value
  • • The best tests catch bugs, document behavior, and stay out of your way
  • • Bad tests are worse than no tests—they cost maintenance time without providing safety
  • • Coverage is a vanity metric that says nothing about test quality
  • • Most teams need fewer tests, not more

The Coverage Lie

Code coverage is a vanity metric. Teams with 95% coverage ship broken features weekly. Teams with 60% coverage deploy confidently multiple times a day. The difference? One team writes tests that check if code runs. The other writes tests that verify the code does what it promises.

Coverage tells you which lines executed. It doesn't tell you if your authentication prevents unauthorized access, if your payment flow handles declined cards, or if your API returns the right errors.

You can hit 100% coverage by writing assert(true) after every function call.

Coverage tells you which lines executed, not whether your code works.

How Test Suites Die

Test suites don't fail overnight. They rot slowly, test by test, until the whole thing becomes a liability. Here's how it happens:

First, they become unreliable

Someone writes a test with sleep(1000) because the API is slow. Someone else copies that pattern. Soon you have tests that pass most of the time.

Flaky tests are worse than no tests.

They train your team to ignore failures. When CI is red 30% of the time for no reason, nobody investigates when it's red for a real reason. You just rerun it until it passes.

Tests without isolation are even worse. They pass when run in order, fail when run individually, and randomly fail when run in parallel. Debugging becomes archaeological work: which test modified which global state that this test depends on? By the time you figure it out, the fastest fix is to delete --random from your test runner config and pretend the problem doesn't exist.

See examples of flaky tests →

Then, they become slow

A slow test is already dead—developers just haven't admitted it yet.

When your test suite takes 30 minutes, developers stop running it locally. At 45 minutes, they stop waiting for CI. At an hour, they merge PRs with failing tests because "it's probably fine."

Speed isn't about impatience—it's about feedback loops. A test that takes 10 milliseconds gets run constantly. 10 seconds? Occasionally. 10 minutes? Never.

Fast tests get run. Slow tests get skipped. Dead tests get deleted.

Then, they become cryptic

Nothing wastes more time than a test failure that doesn't tell you what broke. "Expected true, got false" is not a useful error message. Neither is "AssertionError on line 47" when line 47 is inside a 200-line test with eight unrelated assertions.

Good test failures read like bug reports: "Expected cart total $90 after applying 10% discount code, got $100." Bad test failures read like riddles: "Expected 1, got 2." Is that an array length? A count? An ID? Who knows! Time to read the entire test to figure out what it's even checking.

Then, they become brittle

The worst moment in any refactoring: you finish the change, run the tests, and watch 40 of them fail—even though the behavior didn't change. You renamed an internal function. You extracted a helper.

If refactoring breaks your tests, your tests are testing the wrong thing.

Tests should verify contracts, not implementations. When you mock internal function calls and assert they were called exactly N times with exactly these arguments, you're not testing if your code works. You're testing if it's structured a particular way. The first time someone refactors, all those tests explode.

See examples of brittle tests →

The Mocking Trap

If you're mocking your own code, you're not testing anything.

You're testing a simulation. A simulation that might have nothing to do with how your code actually works.

Mocking exists for external dependencies: databases, HTTP clients, payment gateways. Systems you don't control. But when you mockcalculatePrice() to test processOrder(), you've stopped testing your system. You're verifying that mocks return what you told them to return.

Test suites where 80% of the code is mock setup. Hundreds of lines configuring responses. If a function is that complicated to mock, test the real function. With real objects. Doing real work.

Mock I/O. Test logic. Don't mock your own code.

The E2E Fallacy

"We need more E2E tests" is the rallying cry of teams that don't trust their unit tests. They're right not to trust them.The solution is to write better unit tests.

E2E tests are expensive. Slow, flaky, brittle. Every pixel shift breaks screenshots. Every millisecond of latency causes timeouts. They catch integration bugs slowly, unreliably, and long after you've moved on.

You need E2E tests for critical paths: signup, payment, core flows. Not for every feature. Not for error handling or edge cases. Those belong in fast unit tests that run in milliseconds.

What Good Tests Look Like

Good tests have nine characteristics. Not as a checklist, but as a filter. The more of these your tests violate, the more they're hurting you:

1. Isolation — Tests are hermits

No shared state. No execution order. No hidden coupling. Every test creates its own world and cleans up after itself.

2. Speed — A slow test is already dead

Milliseconds, not seconds. If your test suite takes longer than a coffee break, developers won't run it.

3. Determinism — Flaky tests are worse than no tests

Same input, same output. Every single time. No "just rerun CI."

4. Signal Clarity — Fail loudly, fail obviously

When a test fails, you should know exactly what broke without reading the test code.

5. Focus — One failure reason per test

Tests with "and" in the name should be split. One concern, one assertion, one clear purpose.

6. Stability — Tests should survive refactoring

Test what the code promises, not how it delivers. Renaming internals shouldn't break tests.

7. Setup Honesty — Every line of setup is a smell

If you need 50 lines to set up a test, something's wrong. Minimize data, maximize signal.

8. Boundaries — Test your code, fake the world

Mock external systems. Test your business logic with real objects doing real work.

9. Maintenance — Delete tests that don't pull their weight

Bad tests are worse than no tests. If it's never failed, never will fail, or tests trivia, delete it.

Notice what's missing? Coverage targets. Mock quotas. E2E percentages. None of that matters if your tests aren't helping you ship.

Read the full 9 Laws →

The Uncomfortable Truth

Your team needs fewer tests, not more. Not zero—fewer.

Delete the tests that always pass. Delete the tests that mock everything. Delete the tests that take 10 seconds. Delete the tests that fail randomly. What's left? Tests that catch bugs.

You've been told deleting tests is irresponsible. That coverage should only go up. That every feature needs tests. But keeping useless tests isn't responsible—it's expensive. Every test has a maintenance cost. If it's not providing value greater than that cost, it's waste.

The best test suite is the smallest one that catches all your bugs.

Starting Over

Stop adding tests for a week. Fix what you have. Make flaky tests deterministic or delete them. Make slow tests fast or delete them. Make cryptic tests obvious or delete them.

You'll have fewer tests. But they'll run in seconds, pass consistently, and tell you when something breaks.

Not the biggest test suite. Not the highest coverage. The one that helps you ship.

Written for engineers who have shipped real systems and know that perfect test suites don't exist—but better ones do.