Skip to content

tepyd shape

How much test code is there, and in what shape?

shape counts test LOC against source LOC for every source unit and reports the per-tier breakdown, the ratios, and the pyramid shape. It runs nothing and takes no measurable time.

$ tepyd shape
$ tepyd shape --json
$ tepyd shape --min-src 50      # skip source units under 50 LOC (default: 20)
$ tepyd shape --exclude faker   # skip a unit, on top of config exclusions (repeatable)

Output

package         src  unit  integration  e2e  tests  ratio    u%
--------------  ---  ----  -----------  ---  -----  -----  ----  -
core/discovery   97   180            0    0    180  1.86x  100%  ▲
lenses/cover    491   332          109    0    441  0.90x   75%  ▲
cli             304     0            0  240    240  0.79x    0%  ▲
lenses/gaps     130     0           89    0     89  0.68x    0%  ▲
core/loc         87    24            0    0     24  0.28x  100%  ▲

=== Summary ===
Source units analysed : 11
Total source LOC      : 2,700
Total test LOC        : 1,706
  unit              : 1,020
  integration       : 446
  e2e               : 240
Weighted test/src     : 0.63x  (Σ tests / Σ src)
Plain mean ratio      : 0.70x  ± 0.38
Median ratio          : 0.68x

Under-tested (0 < ratio < 0.5x — 4 units):
  - lenses/report                 src=   586  tests=   169  ratio= 0.29x
  ...

Tier mix across the codebase : unit 60%  /  integration 26%  /  e2e 14%
  ✓ unit share is 100% (after setting aside 686 LOC of tests at their expected
    higher tiers) — at or above the 60% target; tests live at their tiers.
Caveat : tier is by directory, not by what each test actually exercises.

How to read it

Each tier gets its own column, named by its label. Then:

src
Source LOC for the unit.
tests
Total test LOC across all tiers.
ratio
tests / src. The summary calls out under-tested units (below 0.5x) and heavily-tested ones (above 2.0x). Those buckets are empirical: test/source ratios tend to be bimodal (rich suites cluster high, peripheral packages near zero), and IQR fences surface neither tail usefully.
u%
How much of this unit's test code sits in the cheapest tier; see unit share. — means there are no tests.

The last column is the shape glyph.

The summary also warns when the codebase-wide unit share falls below the first tier's target_share.

Layer-aware judging

If you have scoped your tiers with expects, shape respects it. A unit whose home is e2e, a web controller say, is not flagged ▼ for being e2e-heavy. The codebase target_share check also sets aside each unit's tests at its expected higher tiers before measuring. Only test mass sitting above where it belongs counts against you.

Without expects, nothing changes: every unit is judged against the classic unit-pyramid.

The caveat, stated up front

LOC is a proxy for effort. A test's directory decides its tier, whatever the test actually exercises. shape tells you where to look. cover tells you whether the tests are real.

--json

The payload is an array holding one object per unit:

[
  {
    "name": "lenses/cover",
    "src_loc": 491,
    "tier_loc": { "a_unit": 332, "b_integration": 109, "c_e2e": 0 },
    "total_test_loc": 441,
    "ratio": 0.898,
    "unit_share": 0.7528,
    "shape": "▲"
  }
]