Prioritizing Test Coverage Without 100% Dogma
Prioritizing Test Coverage Without 100% Dogma
~6 Min. Lesezeit Zuletzt aktualisiert am August 9, 2026
PHPUnit's coverage report (with Xdebug or PCOV as the driver) answers one, very precise question: which line of code executed at least once during the test run. It does not answer a different, far more important question: whether that execution actually verified anything meaningful. A line can be 100% "covered" and still be completely untested if the test contains no assertion about it.
bin/cli vendor/bin/phpunit -c dev/tests/unit/phpunit.xml.dist \
--coverage-html var/coverage/loyalty \
app/code/Mironsoft/Loyalty/Test/UnitWhat deserves high priority in this module
- Money and points arithmetic:
PointsCalculator::calculatePoints()/determineTier()(chapters 5/91) - every bug directly affects real customer balances. - Reconciliation logic: the "what should have been booked, minus what's already been booked" arithmetic in
ReversePointsOnCreditmemoSave(chapter 31) andExpirePoints(chapter 33) - exactly the spots where a sign error silently double-deducts or double-credits points. - Idempotency and guard conditions: the two checks from chapter 92 - a single forgotten guard means points awarded twice on every repeated event dispatch.
- Security-relevant code:
RedemptionRateLimiter(chapter 86), ACL checks, the guest-vs-customer distinction in GraphQL resolvers (chapters 82/83).
What deliberately gets low priority
- Plain getter/setter chains: the generated accessor methods of
Api\Data\RewardInterface(chapter 79) - no logic, no branch point, nothing a test could catch that PHP's own type checking doesn't already guarantee. - Thin controller/resolver wrappers: controllers that delegate in a single line to an already-tested service contract (chapters 79-83) - the value lives in the service behind it, not in the one-line delegation.
- Alpine.js/phtml templates: the
customer-data/points-badge.phtmltemplate from chapter 84 - PHPUnit checks PHP, not client-side JavaScript; that would need an entirely different tool this module deliberately doesn't introduce.
Tipp: A useful rule-of-thumb question before every new test: "What concrete, real bug would make this test fail?" If the answer is hard to find, the test is probably pure line coverage with no real value - exactly the pattern a 100% mandate systematically produces, because it demands a test even for lines with no branch at all.
Achtung: Chapter 92 deliberately tested only two of several possible paths through AwardPointsOnOrderPlaced::awardPoints() - the two early exits. The full happy path with several order items, different multipliers, and an actually persisted ledger entry was assigned to chapter 93 as an integration test, not rebuilt as a third, fourth, and fifth mocked unit test scenario. A deliberate, documented gap is a different thing from a forgotten one.
Team agreement instead of a tooling mandate
A coverage threshold in the CI pipeline (chapter 95 shows how to enforce one there) makes most sense as a floor against regression - "coverage must not drop" - not as a goal in itself. A team chasing 100% ends up spending time on tests for Api\Data interfaces instead of on exactly the reconciliation and guard tests that actually prevent real bugs.
Chapter 95 takes these tests - and the coverage report itself - and automates them: continuous integration.