Skip to main content
The coverage.* family folds test coverage into mehen’s metric tree, next to the complexity and size metrics computed from source. mehen does not instrument or run your tests — your test runner already produces a coverage report; mehen ingests it, maps its file paths onto the files under analysis, and publishes coverage as a first-class metric category: selectable in mehen top-offenders, gateable through mehen.toml thresholds, and rendered in the JSON and Markdown reports of mehen metrics. Coverage answers a question no static metric can: which of this code do the tests actually exercise? Combined with complexity it locates the riskiest code in a repository — complex and untested — which is exactly the intersection the CRAP index formalizes (see roadmap below).

Quick start

Bare --coverage means auto: mehen discovers report files in their idiomatic locations — including gitignored directories like coverage/, target/, and TestResults/ where they conventionally live. Six report formats are recognized by content, not by filename (see supported formats), and report paths are reconciled with workspace paths by a dedicated path-matching layer.

What mehen emits

Every coverage.* metric is higher-is-better: a configured threshold is a minimum, and top-offenders ranks the least covered files as the worst offenders.

Semantics

  • Report-scope, engine-published. Like the history.* family, coverage is not computed by a language analyzer — the engine injects it after static analysis, from the ingested reports. It works for any of mehen’s source languages, because the report formats are language-agnostic.
  • Unmeasured is never 0%. A file absent from every report publishes no coverage keys: it ranks as least concerning and never fires a threshold. top-offenders renders the missing score as n/a, while mehen metrics omits the coverage family from its output entirely. A file present in a report with zero executed lines publishes an honest 0. The distinction is load-bearing — a fabricated 0% would fail every gate on every file the moment path matching hiccuped.
  • Dimensions are independent. A format that cannot measure a dimension leaves it absent: a Go coverprofile has no branch or function records, so only coverage.line appears.
  • Per-function attribution. Beyond the file totals, every function and closure space in the metric tree receives coverage.line and coverage.branch scoped to its line span — so you can see which function inside a 60%-covered file is the untested one.
  • Merging. Multiple reports (monorepo per-package runs, .nyc_output shards, re-run artifacts) merge as a union of files with saturating-max hit counts: covered anywhere ⇒ covered. Max is order-independent, so the result is deterministic regardless of which report is found first.

Coverage and complexity together

Coverage percentages are a risk denominator, not a quality score: high coverage does not prove the tests assert anything (Inozemtseva & Holmes measured only weak-to-moderate correlation between coverage and suite effectiveness once suite size is controlled), and Google’s large-scale experience report treats coverage as a heuristic adopted for insight, not as a target to chase. The productive use is directional and combinatorial:
  • rank by coverage.line ascending to find blind spots (top-offenders),
  • gate on a floor so blind spots stop growing ([thresholds]),
  • and cross it with complexity: a trivial getter at 0% is noise; a cognitive-complexity-30 function at 0% is where bugs hide. The per-function coverage.line mehen publishes is the direct input to the CRAP index — comp(m)² × (1 − cov(m)/100)³ + comp(m) (Savoia & Evans) — planned as a follow-up composite in this family.

See also

References