Skip to content

Find the files your tests only cover by accident.

Global coverage counts every line any test touched. Parity checks that each source file is exercised by its own test, and fails CI when a different test is doing the work.

Reads PHPUnit, Clover, Cobertura, and Parity JSON coverage from PHP, JavaScript, TypeScript, and Rust projects.

src/Calculator.php
80%of this file covered
1final class Calculator
2{
3 public function add(int $left, int $right): int
4 {
5 return $left + $right;
6 }
7
8 public function subtract(int $left, int $right): int
9 {
10 return $left - $right;
11 }
12
13 public function multiply(int $left, int $right): int
14 {
15 return $left * $right;
16 }
17
18 public function divide(int $left, int $right): int
19 {
20 return intdiv($left, $right);
21 }
22
23 public function maximum(int $left, int $right): int
24 {
25 return max($left, $right);
26 }
27}
Covered by CalculatorTest.phpCovered only by DiscountPolicyTest.phpNot coveredOpen this sample on GitHub

The same gap in eleven real projects

Each sample passes an 80% global coverage gate. In each one, a single file gets most of its coverage from a test that belongs to something else. These numbers come from each repository's CI run of parity test.

  1. PHPPHPUnitsrc/Calculator.php80% overall, but only 40%from its own test
  2. LaravelPHPUnitapp/Services/InvoiceTotal.php80% overall, but only 40%from its own test
  3. PHPUnitPHPUnitsrc/InvoiceCalculator.php80% overall, but only 40%from its own test
  4. PestPestsrc/Slugger.php80% overall, but only 40%from its own test
  5. JestJestsrc/sum.js85.71% overall, but only 42.86%from its own test
  6. MochaMocha + NYCsrc/greeting.js85.71% overall, but only 42.86%from its own test
  7. VitestVitestsrc/formatCurrency.ts100% overall, but only 50%from its own test
  8. TypeScriptnode:test + c8src/formatCurrency.ts80% overall, but only 44%from its own test
  9. AdonisJSJapa + c8app/services/user_presenter.ts84% overall, but only 44%from its own test
  10. Rustcargo llvm-covsrc/lib.rs80% overall, but only 40%from its own test
  11. Cargocargo llvm-covsrc/lib.rs80% overall, but only 40%from its own test

Add it to CI in three steps

Keep your test runner and your coverage tool. Parity reads what they already produce.

  1. Map source folders to test folders

    parity init writes a parity.yaml. Each structure says where a file's own test lives and how much of the file it must cover.

    structure:
      - name: "Currency"
        paths:
          source: "code/src"
          test: "code/tests"
        rules:
          - minimum-coverage:
              min: 70
          - matched-coverage:
              min: 70
  2. Run each test on its own

    If your coverage tool already records which test hit which line, skip this. Otherwise parity test runs every test file in isolation and keeps one report per test.

    $ parity test --format=json
  3. Fail the build with a reason

    The suite reaches 100%, but formatCurrency.test.ts covers only half of its own file, so the check fails and says which file to fix.

    formatCurrency.ts
      Cov    100%
      Match   50%   N
    
    Global                 100.00%  OK
    Per-file min
      (matching test only)  50.00%  FAIL

Install

composer global require testparity/parity

Requires PHP 8.4 or newer, even for JavaScript and Rust projects. A standalone PHAR is attached to every GitHub release. Parity is MIT licensed.