Skip to content

tepyd gaps

Does the test tree structurally parallel the source tree?

gaps is a static, no-execution comparison of the test tree against the source tree, per tier, at the same granularity as your units, the slices shape and cover use. Refine the units patterns to make gaps coarser or finer; it never floods a deeply-nested project with per-sub-package findings.

$ tepyd gaps
$ tepyd gaps --json
$ tepyd gaps --exclude faker

Output

Mirror — test tree vs source tree (11 source units)

unit: 8/11 mirrored (73%)
  gap     cli
  gap     lenses/gaps
  gap     lenses/shape

integration: 5/5 mirrored (100%), 6 out of scope

e2e: 1/1 mirrored (100%), 10 out of scope

How to read it

For each tier, every unit falls into one of four buckets:

present
The unit has a matching test counterpart at this tier that actually contains tests. Counted in the X/Y mirrored figure. For a package unit that means a mirrored directory (checked recursively, so a test for any sub-package counts); for a module unit, a test_<stem>.py file.
gap
A unit with no test counterpart at this tier that the tier was expected to test.
out of scope
A unit this tier is not responsible for, so its absence is reported as n/a. Shown as a count; the full list is in --json. See layer awareness.
orphan
A test directory that contains tests but has no source on disk: tests for code that moved or vanished.

Orphan detection asks whether the source actually exists, so it is independent of exclusions: excluded source still exists, so its tests are not orphans. Tests sitting directly at a tier root mirror nothing in particular and are never orphans.

Mapping is forward-only, from source unit to test location, honouring a tier's strip_prefix. That sidesteps the ambiguity of reversing a flattening map: orphans are found by asking which test directories are not expected, never by un-mapping them.

Scoping a tier to its layer

By default every tier is checked against every unit. That is fine for a flat app and noisy for a layered one:

unit: 4/10 mirrored (40%)
  gap     di
  gap     domain/ports
  gap     infrastructure
  gap     repositories
  gap     web
  gap     web/controllers

…and the integration and e2e tiers each report eight more in the same vein, all of it code that lives at a different tier. expects fixes this:

unit: 4/4 mirrored (100%), 4 out of scope
integration: 2/2 mirrored (100%), 6 out of scope
e2e: 2/2 mirrored (100%), 6 out of scope

A unit that should never be tested anywhere, such as a pure Protocol/ports layer with no runtime behaviour, belongs in exclude, which drops it from every tier.

Reading the figures

The figures are diagnostics, not targets. A browser tier showing 1/20 mirrored is often exactly right.

--json

{
  "source_packages": ["cli", "core/config", "..."],
  "tiers": [
    {
      "name": "b_integration",
      "label": "integration",
      "coverage": 1.0,
      "expects": ["lenses/*"],
      "present": ["lenses/cover", "lenses/gaps"],
      "gaps": [],
      "out_of_scope": ["cli", "core/config"],
      "orphans": []
    }
  ]
}