Code coverage measures which lines ran during your tests — not whether those lines are correct. A test with no assertions can hit 100% coverage and prove nothing. Treat 75% as the deploy gate it is, and measure real test quality with assertions and mutation testing.
What the 75% actually means
The Salesforce platform blocks a production deployment if your org's Apex code coverage drops below 75%. It's a real, hard gate, and it exists for a defensible reason: it stops people from shipping wholly untested code. Fine.
The problem is what the industry did with that number. It became the definition of "well tested." Teams set 75% as the target, hit it, and call the class done. But coverage answers exactly one question — did this line execute while a test was running? — and that is not the same question as does this line do the right thing?
The test that proves nothing
Here is a test that gives you 100% coverage of the method below and verifies absolutely nothing:
public class DiscountService {
public static Decimal apply(Decimal price, Decimal pct) {
return price - (price * pct / 100);
}
}
@isTest
private class DiscountServiceTest {
@isTest
static void testApply() {
DiscountService.apply(100, 10); // runs the line. asserts nothing.
}
}Every line of apply executed, so coverage reports 100%. But if someone "fixes" the formula to price * pct / 100 — returning the discount amount instead of the discounted price — this test stays green. The coverage number is a lie told with a straight face: fully covered, completely unprotected.
This isn't a contrived edge case. Coverage-driven test writing produces exactly this shape at scale: call the method, maybe check it didn't throw, move on to the next uncovered line. The suite gets greener and the codebase gets no safer.
Coverage is necessary, not sufficient
To be clear: coverage isn't useless. A line that never runs during any test is definitely untested, and a coverage report is the fastest way to find those gaps. Coverage is a good way to find the code your tests ignore. It's a terrible way to judge the code your tests touch.
The useful mental model: coverage is a floor, not a ceiling. It tells you where the tests aren't. It says nothing about whether the tests that are there would notice if the behavior changed. For that, you need to measure something else.
What to measure instead — part 1: assertions
The cheapest upgrade is a rule your team can adopt this afternoon: a test without a meaningful assertion is not a test. Every test should make a specific claim about behavior — a returned value, a field on a record after DML, a thrown exception, a callout that did or didn't fire.
@isTest
static void testApply() {
Decimal result = DiscountService.apply(100, 10);
System.assertEquals(90, result, '10% off 100 should be 90');
}Same coverage as before. Completely different value. Now the "fix" that breaks the formula turns the test red, which is the entire point of having a test. If you do nothing else after reading this, audit your suite for assertion-free tests — they're the ones inflating your coverage number while protecting nothing.
What to measure instead — part 2: mutation testing
Assertions are necessary but you still can't tell, at a glance, whether they're the right assertions. That's what mutation testing answers, and it's the closest thing the industry has to a real test-quality metric.
The idea is delightfully direct: deliberately break your code — flip a > to a >=, swap a + for a -, replace a boolean with true — and re-run your tests. Each broken version is a "mutant." If your tests fail, they caught the mutant: good. If they stay green, the mutant survived — meaning you have a change to real behavior that no test objects to. Every surviving mutant is a concrete, specific hole in your suite, pointing at an exact line.
Mutation score — the percentage of mutants your tests kill — measures what coverage only pretends to: whether your tests would actually notice if the code broke. A class can sit at 95% coverage and a 40% mutation score, and that gap is precisely the set of bugs your tests will happily wave through.
Why this has been impractical in Apex — until now
Mutation testing has an obvious catch: it runs your whole suite once per mutant. A class with 200 mutants means 200 full test runs. In the traditional Apex loop — deploy, queue, run — that's hours per class, so nobody does it, and mutation testing stayed a thing other ecosystems got to have.
The blocker was never the technique. It was the feedback loop. When your tests run locally in milliseconds instead of minutes, 200 runs is seconds, and mutation testing becomes something you can actually run on every class. That's exactly why Nimbus ships mutation testing built in — the local runtime makes it cheap enough to be routine, and its branch coverage goes past Salesforce's line-only metric to track both sides of every if, switch, and ternary.
A saner way to think about the number
- 75% is a deploy gate. Clear it, don't worship it.
- Coverage finds untested code. Use it to locate gaps, not to grade quality.
- Assertions make a test a test. Ban the assertion-free ones.
- Mutation score grades the tests themselves. It's the metric that actually correlates with catching bugs.
The goal was never a coverage percentage. It was the confidence to change code without breaking production. Coverage buys you a deployment. The other three buy you the confidence.
See your real numbers
Run your suite locally with coverage, then run mutation testing on a class you think is well tested. The gap between the two numbers is usually the most honest feedback you'll get about your tests all quarter.