PHPackages                             testparity/parity - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Testing &amp; Quality](/categories/testing)
4. /
5. testparity/parity

ActiveLibrary[Testing &amp; Quality](/categories/testing)

testparity/parity
=================

Structural parity and code coverage validation — pluggable rules, framework agnostic.

v1.1.1(3d ago)026↓100%MITPHP ^8.4

Since Jul 6Compare

[ Source](https://github.com/testparity/cli)[ Packagist](https://packagist.org/packages/testparity/parity)[ Docs](https://testparity.com)[ RSS](/packages/testparity-parity/feed)WikiDiscussions Synced today

READMEChangelog (2)Dependencies (6)Versions (3)Used By (0)

 [![TestParity](https://camo.githubusercontent.com/6f4533b9e15a291569946e9129680c77c316f82ac9cac3ff344478c83938b3f3/68747470733a2f2f746573747061726974792e636f6d2f6272616e642f746573747061726974792d726561646d652d62616e6e65722e6a7067)](https://camo.githubusercontent.com/6f4533b9e15a291569946e9129680c77c316f82ac9cac3ff344478c83938b3f3/68747470733a2f2f746573747061726974792e636f6d2f6272616e642f746573747061726974792d726561646d652d62616e6e65722e6a7067)

Parity
======

[](#parity)

[![CI](https://github.com/testparity/cli/actions/workflows/ci.yml/badge.svg)](https://github.com/testparity/cli/actions/workflows/ci.yml)[![Latest Stable Version](https://camo.githubusercontent.com/0e5ad68bc5d9c8856b47da0818ea10511d9b068acaa6f97e8fef19862bafc3f0/68747470733a2f2f706f7365722e707567782e6f72672f746573747061726974792f7061726974792f762f737461626c65)](https://packagist.org/packages/testparity/parity)[![Total Downloads](https://camo.githubusercontent.com/cadaae65856dbf138b2aa08b439b42c32254c9679f2fb8e9469685dc9ded702a/68747470733a2f2f706f7365722e707567782e6f72672f746573747061726974792f7061726974792f646f776e6c6f616473)](https://packagist.org/packages/testparity/parity)[![License](https://camo.githubusercontent.com/42aed81ddf3ac4b390993ab80d3ae200b871c505be7994074a982e7c5ca57387/68747470733a2f2f706f7365722e707567782e6f72672f746573747061726974792f7061726974792f6c6963656e7365)](https://packagist.org/packages/testparity/parity)

Structural parity and code coverage validation for any project. Parity ensures application files have belonging tests, verifies coverage ownership, and catches files whose direct tests are weaker than global coverage suggests.

Global coverage answers "did the test suite touch enough code?" Parity answers "did the test that owns this file cover enough of this file?"

Install
-------

[](#install)

```
composer global require testparity/parity
```

Make sure Composer's global bin directory is in your `PATH`:

```
# Add to your shell profile (~/.zshrc, ~/.bashrc, etc.)
export PATH="$HOME/.composer/vendor/bin:$PATH"
```

Quick Start
-----------

[](#quick-start)

```
# In your project root
parity init    # Creates parity.yaml
parity check   # Run validation

# Options
parity check --format=json          # JSON output for CI
parity check --config=path/to.yaml  # Custom config path
```

Parity reads existing coverage reports; it does not run your test suite. Prefer detailed coverage formats with per-line or per-test attribution when your ecosystem supports them.

```
coverage_xml: [parity-coverage.json, coverage-xml, clover.xml, cobertura.xml]
```

`coverage_xml` can be a single path or an ordered list. Parity uses the first existing path, so teams can prefer attribution-rich reports and fall back to portable reports in CI:

FormatBest forMatched coverageParity JSONLanguage-neutral per-test attribution from custom convertersYesPHPUnit XML directoryPHP, PHPUnit, Pest, and line-level test attributionYesClover XMLPHP, JavaScript, TypeScript, and common coverage toolsNoCobertura XMLRust, Python, Go, JavaScript, TypeScript, JVM, and CI coverage toolsNoDevelopment
-----------

[](#development)

```
composer install
./vendor/bin/pint --test
XDEBUG_MODE=coverage ./vendor/bin/pest --coverage-xml=coverage-xml --coverage-clover=clover.xml --colors=never
php parity check --format=json
composer validate --strict
```

This repository dogfoods Parity through the root `parity.yaml`. The self-check expects PHPUnit XML coverage in `coverage-xml/`, so generate coverage before running `php parity check`.

Build the PHAR manually with:

```
./vendor/bin/box compile --no-interaction
php parity.phar --version
```

Samples
-------

[](#samples)

Public sample repositories demonstrate Parity across PHP, Laravel, TypeScript, AdonisJS, Rust, PHPUnit, Pest, Jest, Mocha, Vitest, and Cargo. Each sample installs the public Packagist package in GitHub Actions, runs `parity check`, and documents the same proof shape: global coverage can be 80% while the owning test only covers 40% of one file.

SampleLanguage/frameworkTest runnerRepositoryPHPPlain PHPFixture coverageLaravelLaravel-style PHPFixture coverageTypeScriptTypeScript utilityFixture coverageAdonisJSAdonisJS-style TypeScriptFixture coverageRustPlain RustFixture coverageCargoCargo projectCargoPHPUnitPHPPHPUnitPestPHPPestJestJavaScriptJestMochaJavaScriptMocha + NYCVitestTypeScriptVitestSee `docs/SAMPLES.md`, `docs/WHY-GLOBAL-COVERAGE-LIES.md`, and `docs/PARITY-COVERAGE-JSON.md`.

The local `samples/` directory contains the original fixtures. Run them from this package root:

```
php parity check --config=samples/php/parity.yaml
php parity check --config=samples/laravel/parity.yaml
php parity check --config=samples/vite/parity.yaml
php parity check --config=samples/adonisjs/parity.yaml
php parity check --config=samples/rust/parity.yaml
php parity check --config=samples/phpunit/parity.yaml
php parity check --config=samples/pest/parity.yaml
php parity check --config=samples/jest/parity.yaml
php parity check --config=samples/mocha/parity.yaml
php parity check --config=samples/vitest/parity.yaml
php parity check --config=samples/cargo/parity.yaml
```

Specs and Docs
--------------

[](#specs-and-docs)

Public specs are indexed in `specs/README.md`. Feature docs live in `docs/`, with the complete code/config/plugin/output reference in `docs/REFERENCE.md`. The VitePress website lives in `../parity-website` during this workspace phase and mirrors the full specs tree under `/specs/`.

Configuration
-------------

[](#configuration)

All behavior is driven by `parity.yaml`. Parity is designed to be framework and language agnostic — settings control how files are discovered, named, and validated.

### Settings

[](#settings)

```
settings:
  # Namespace roots: directory prefix -> namespace prefix
  namespace_roots:
    app: App
    tests: Tests
  # File discovery
  source_extension: ".php"       # .ts, .py, .go, etc.
  test_suffix: "Test"            # Spec, _test, .test, etc.
  test_extension: ".php"         # Defaults to source_extension
  # Identifier format
  namespace_separator: "\\"      # . for Java/Python, / for Go
```

### Coverage

[](#coverage)

```
# Coverage file(s): first existing path is used
# Supports Parity JSON, PHPUnit XML, Clover XML, and Cobertura XML
coverage_xml: [parity-coverage.json, coverage-xml, clover.xml, cobertura.xml]

# Global thresholds
min_coverage: 80
min_coverage_global: 80
```

### Structure with Rules

[](#structure-with-rules)

Each structure maps a source directory to a test directory and applies rules:

```
structure:
  - name: "Unit Services"
    paths:
      source: "app/Services"
      test: "tests/Unit/Services"
    rules:
      - enforce-coverage-link
      - minimum-coverage:
          min: 80
      - matched-coverage:
          min: 60
    file_map:  # Override test path for specific files
      "Auth/LoginService.php": "Auth/LoginServiceTest.php"
```

Built-in Rules
--------------

[](#built-in-rules)

Rules are pluggable and registered in the container as `parity.rules.{name}`.

RuleColumnParametersDescription`test-exists``∃`noneTest file exists (auto-added)`enforce-coverage-link``Link``linkers`, `attribute`Test declares `->covers()` (Pest) or `#[CoversClass]` (PHPUnit)`minimum-coverage``Cov``min` (required)Per-file coverage meets threshold`matched-coverage``Match``min` (optional)Coverage from matching test file only`coverage-attribution``#` + `Other`noneShows test count and non-matching test count### Rule Parameters

[](#rule-parameters)

Each rule declares a `parameters()` method that returns validation rules:

```
// MinimumCoverageRule
public function parameters(): array
{
    return ['min' => 'required|numeric|min:0|max:100'];
}
```

Plugins
-------

[](#plugins)

Parity discovers custom rules from three locations:

SourcePathFormatProject-local`.parity/plugins/*.php`PHP file returning `RuleInterface`Global user`~/.parity/plugins/*.php`PHP file returning `RuleInterface`Composer package`extra.parity.rules` in composer.jsonArray of class FQCNs### File Plugin

[](#file-plugin)

Create a PHP file that returns a `RuleInterface` (or array of them):

```
// .parity/plugins/naming-convention.php
return new class implements \App\Rules\RuleInterface {
    public function name(): string { return 'naming-convention'; }
    public function parameters(): array { return ['pattern' => 'sometimes|string']; }
    public function evaluate(\App\Rules\RuleContext $context, array $params): \App\Rules\RuleResult { ... }
    public function columnHeader(): ?string { return 'Name'; }
    public function formatCell(\App\Rules\RuleResult $result): string { ... }
    public function isEnforced(): bool { return true; }
};
```

### Composer Plugin Package

[](#composer-plugin-package)

Publish a composer package with rules declared in `composer.json`:

```
{
    "name": "acme/parity-rules",
    "extra": {
        "parity": {
            "rules": [
                "Acme\\Parity\\NamingConventionRule"
            ]
        }
    }
}
```

Then use any plugin rule in `parity.yaml`:

```
rules:
  - naming-convention:
      pattern: "{ClassName}Test.php"
```

Coverage Linkers
----------------

[](#coverage-linkers)

Coverage link detection supports multiple strategies via `CoverageLinkerInterface`:

LinkerDetectsExample`pest-covers`Pest method chains`->covers(Foo::class)``php-attribute`PHP 8 attributes`#[CoversClass(Foo::class)]`Linkers auto-detect based on file content (Pest vs PHPUnit). Override with:

```
rules:
  - enforce-coverage-link:
      linkers: [pest-covers]  # Only check Pest-style
```

Legacy Config
-------------

[](#legacy-config)

The old format (`source_path`, `test_path`, `enforce_attribute`, `min_coverage`) is still fully supported:

```
structure:
  - name: "Unit Actions"
    source_path: "app/Actions"
    test_path: "tests/Unit/Actions"
    enforce_attribute: 'PHPUnit\Framework\Attributes\CoversClass'
    min_coverage: 90
```

Architecture
------------

[](#architecture)

```
parity.yaml (config)
    |
    v
Settings (resolved config DTO)
    |
    v
CheckCommand (orchestrator)
    |
    +-- RuleRegistry (resolves rules from config)
    |       |
    |       +-- TestExistsRule
    |       +-- EnforceCoverageLinkRule --> CoverageLinkerRegistry
    |       +-- MinimumCoverageRule
    |       +-- MatchedCoverageRule
    |       +-- CoverageAttributionRule
    |
    +-- CoverageReader / PhpUnitXmlCoverageReader
    +-- NamespaceHelper (configurable pathFQCN)
    +-- ParityChecker (file-level validation)

```

License
-------

[](#license)

MIT

###  Health Score

44

—

FairBetter than 90% of packages

Maintenance99

Actively maintained with recent releases

Popularity10

Limited adoption so far

Community2

Small or concentrated contributor base

Maturity52

Maturing project, gaining track record

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~0 days

Total

2

Last Release

3d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/9d02cdb7563da13d24667b14025c67a4e2fc52a104becc8bfe384da21d056768?d=identicon)[marktopper](/maintainers/marktopper)

---

Tags

testingclistatic analysiscoveragequalityparity

###  Code Quality

TestsPest

Code StyleLaravel Pint

### Embed Badge

![Health badge](/badges/testparity-parity/health.svg)

```
[![Health](https://phpackages.com/badges/testparity-parity/health.svg)](https://phpackages.com/packages/testparity-parity)
```

###  Alternatives

[behat/behat

Scenario-oriented BDD framework for PHP

4.0k101.8M2.2k](/packages/behat-behat)[phpunit/php-code-coverage

Library that provides collection, processing, and rendering functionality for PHP code coverage information.

8.9k935.9M1.7k](/packages/phpunit-php-code-coverage)[orchestra/testbench

Laravel Testing Helper for Packages Development

2.2k42.5M41.5k](/packages/orchestra-testbench)[infection/infection

Infection is a Mutation Testing framework for PHP. The mutation adequacy score can be used to measure the effectiveness of a test set in terms of its ability to detect faults.

2.2k28.9M2.4k](/packages/infection-infection)[spatie/phpunit-snapshot-assertions

Snapshot testing with PHPUnit

69619.8M643](/packages/spatie-phpunit-snapshot-assertions)[orchestra/workbench

Workbench Companion for Laravel Packages Development

8320.3M76](/packages/orchestra-workbench)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
