PHPackages                             ahmed-bhs/pyra - 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. ahmed-bhs/pyra

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

ahmed-bhs/pyra
==============

Standalone CLI that validates the shape of your test pyramid (unit/integration/e2e ratios) for any PHP project.

v0.1.0-beta.3(1mo ago)10MITPHPPHP &gt;=8.2

Since Jun 3Pushed 1mo agoCompare

[ Source](https://github.com/ahmed-bhs/pyra)[ Packagist](https://packagist.org/packages/ahmed-bhs/pyra)[ RSS](/packages/ahmed-bhs-pyra/feed)WikiDiscussions main Synced 1w ago

READMEChangelog (1)Dependencies (12)Versions (5)Used By (0)

Pyra
====

[](#pyra)

[![PHP](https://camo.githubusercontent.com/ccaa43fc634d348cffccb1d8db7b55d9f17c5d46944bc99a15c3c982724b387d/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f5048502d382e322532422d3737374242343f6c6f676f3d706870266c6f676f436f6c6f723d7768697465)](https://camo.githubusercontent.com/ccaa43fc634d348cffccb1d8db7b55d9f17c5d46944bc99a15c3c982724b387d/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f5048502d382e322532422d3737374242343f6c6f676f3d706870266c6f676f436f6c6f723d7768697465)[![PHPUnit](https://camo.githubusercontent.com/133a108c09a123f7859584bfc0ffe55b3a5870725b32fff8a10429dfecacdb0f/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f504850556e69742d737570706f727465642d334339434437)](https://camo.githubusercontent.com/133a108c09a123f7859584bfc0ffe55b3a5870725b32fff8a10429dfecacdb0f/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f504850556e69742d737570706f727465642d334339434437)[![Gherkin](https://camo.githubusercontent.com/0c21d77dd73e24393d2d6c0c0e96772040ee79fd0a819e25e908fbe71372fce9/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f476865726b696e2d737570706f727465642d323344393643)](https://camo.githubusercontent.com/0c21d77dd73e24393d2d6c0c0e96772040ee79fd0a819e25e908fbe71372fce9/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f476865726b696e2d737570706f727465642d323344393643)[![License](https://camo.githubusercontent.com/f8df3091bbe1149f398a5369b2c39e896766f9f6efba3477c63e9b4aa940ef14/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d4d49542d677265656e)](https://camo.githubusercontent.com/f8df3091bbe1149f398a5369b2c39e896766f9f6efba3477c63e9b4aa940ef14/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d4d49542d677265656e)

Pyra looks at your tests and answers two questions: is the overall pyramid the right shape, and for the code you just changed on a branch, was it tested at the level it should be.

It works on any PHP project (Symfony, Laravel or plain PHP) as long as the tests are written with **PHPUnit** (methods `test*` / `#[Test]`) or **Gherkin** scenarios. Pest is not supported yet, so a project whose tests are written only in Pest will report zero tests.

It does not boot your app. It reads files and a YAML config, so it runs the same everywhere.

Why
---

[](#why)

The test pyramid (Mike Cohn) says: lots of small, fast, isolated unit tests at the bottom; fewer slow, costly end-to-end tests at the top. The [Practical Test Pyramid](https://martinfowler.com/articles/practical-test-pyramid.html)boils it down to two rules worth keeping:

> - Write tests with different granularity
> - The more high-level you get the fewer tests you should have

Most teams agree with this and then drift away from it one change at a time: a feature ships with an end-to-end test and no unit test, a "unit" test quietly spins up the database. Pyra is there to notice.

Install
-------

[](#install)

```
composer require --dev ahmed-bhs/pyra
```

The two commands
----------------

[](#the-two-commands)

`pyra check` looks at the whole suite: how many tests live at each level, whether the ratios and ordering still make a pyramid, and whether any "unit" test is really an integration test in disguise (it depends on something like an `EntityManager`).

`pyra diff` looks at what changed on your branch against a base ref. For every class you changed, it checks the test levels that area is expected to have. It searches the *whole*test suite, not just the files in the diff, so a test you wrote three months ago still counts and you do not get nagged about a class that is already covered.

```
vendor/bin/pyra check --strict
vendor/bin/pyra diff --base origin/main --strict
vendor/bin/pyra diff --base origin/main --coverage build/clover.xml
vendor/bin/pyra diff --base origin/main --format=github   # inline annotations
```

`--base` is any git ref: a branch (`origin/main`), a tag, or a commit. You do not need an open pull request; a local branch is enough.

With `--strict`, a violation exits `1` (for CI). Without it, violations are printed but the command still exits `0`. `diff` can render as a `table` (default), `json`, or `github` annotations. See **[docs/github-actions.md](docs/github-actions.md)** for a ready-to-use workflow.

Config
------

[](#config)

Pyra reads a `pyra.yaml` at the project root. The shortest config that does something useful:

```
pyra:
    levels:
        unit:
            paths: [tests/Unit]
            forbidden_dependencies:
                - Doctrine\ORM\EntityManagerInterface
        integration:
            paths: [tests/Integration]
        e2e:
            paths: [features]
            counter: gherkin
```

Every key (levels, percentages, counters, the `diff` block, ready-to-copy Symfony and Laravel examples) is documented in **[docs/configuration.md](docs/configuration.md)**.

What it actually catches
------------------------

[](#what-it-actually-catches)

- A class you changed that has no test at the level its area expects.
- A unit test that pulls in an integration-only dependency.
- A pyramid that has tipped over (more integration than unit), compared within one counting style.
- If you pass `--coverage`, the changed lines that no test executes.

Where it stops
--------------

[](#where-it-stops)

A few things worth knowing before you trust the output:

- Without a coverage file it can only tell you a test *looks* missing, never that coverage is low. "Coverage" is only ever reported from a clover/cobertura XML you pass in with `--coverage`.
- There is no notion of a "feature": the unit of work is the changed class, summed up over the branch.
- The class-to-test matching is by name. A test that mentions a class without really exercising it is a false positive; a class hit only through a collaborator (or reflection, or the container) without being named is a false negative. Coverage is how you close that gap.
- A `.feature` file names no PHP class, so e2e cannot be name-matched. Only the path rules or coverage reach it.
- Telling coverage apart *per level* needs one coverage run per suite; a single merged file cannot say which level hit a line.
- It counts PHPUnit (`test*` / `#[Test]`) and Gherkin scenarios. Pest is not counted yet.
- It classifies tests by **directory**. You map folders to levels; a folder that mixes unit and integration tests in the same place cannot be split (a project like api-platform/core, which groups tests by component rather than by level, is only as precise as your paths). Per-test level markers are a possible future addition.

License
-------

[](#license)

MIT

###  Health Score

35

—

LowBetter than 77% of packages

Maintenance93

Actively maintained with recent releases

Popularity2

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity35

Early-stage or recently created project

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

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 ~7 days

Total

3

Last Release

37d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/3297c1384e75483020a47fb9a7614a02bf02ba06f286bc56bf1bb81a25941660?d=identicon)[ahmed-bhs](/maintainers/ahmed-bhs)

---

Top Contributors

[![ahmed-bhs](https://avatars.githubusercontent.com/u/19672303?v=4)](https://github.com/ahmed-bhs "ahmed-bhs (18 commits)")

---

Tags

testingphpunittestqualitycitest-pyramid

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan

Type Coverage Yes

### Embed Badge

![Health badge](/badges/ahmed-bhs-pyra/health.svg)

```
[![Health](https://phpackages.com/badges/ahmed-bhs-pyra/health.svg)](https://phpackages.com/packages/ahmed-bhs-pyra)
```

###  Alternatives

[behat/behat

Scenario-oriented BDD framework for PHP

4.0k101.8M2.3k](/packages/behat-behat)[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.6k](/packages/infection-infection)[friendsoftypo3/content-blocks

TYPO3 CMS Content Blocks - Content Types API | Define reusable components via YAML

103519.9k57](/packages/friendsoftypo3-content-blocks)[jolicode/castor

A lightweight and modern task runner. Automate everything. In PHP.

54643.3k4](/packages/jolicode-castor)[aeliot/todo-registrar

Register TODOs from source code in issue tracker

153.0k](/packages/aeliot-todo-registrar)[typo3/cms

TYPO3 CMS is a free open source Content Management Framework initially created by Kasper Skaarhoj and licensed under GNU/GPL.

1.2k1.9M122](/packages/typo3-cms)

PHPackages © 2026

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