A repeatable loop to drive a spec directory to 100% line coverage of its target
source directory on its own — e.g. bundle exec rspec spec/models covering
100% of app/models without leaning on controller or service specs.
Specs only. Do not change production code to make a line reachable. If a line can only be covered by proving the code is broken, stop and report the bug.
The examples below use app/models / spec/models; the same steps apply to any
pair (services, policies, workers, …) — point the tooling at the other directory.
Two coverage-only helpers (not production code):
coverage_eager_load.rb— rspec--requirehook. Force-loads every source file under the target dir inbefore(:suite)so SimpleCov tracks files no spec touched (otherwise unreferenced files silently drop out of the denominator).model_coverage.rb— parsescoverage/.resultset.jsonand prints per-file line coverage for the target dir, worst-first, plus overall and the missed line numbers. It honors# :nocov:and# simplecov:disable/# simplecov:enableso its numbers match SimpleCov's own report.
Both currently filter on app/models; change that path (and the eager-load glob)
to target another directory.
- Measure the full baseline:
COVERAGE=1 bundle exec rspec --require ./bin/coverage_eager_load spec/models ruby bin/model_coverage.rb # worst-covered first - Pick one candidate: the worst file, or a small one for a quick win.
- Read the source file, and its existing spec if present.
- Add or extend the spec. Structure: outer
describe '#method', innercontextper scenario. Target the missed lines (ruby bin/model_coverage.rb <file>). - Re-measure that file to confirm 100%:
COVERAGE=1 bundle exec rspec --require ./bin/coverage_eager_load spec/models/<name>_spec.rb ruby bin/model_coverage.rb <name> - Commit (one file per commit). Repeat from step 2.
coverage/.resultset.json holds only the files from the last COVERAGE run
(SimpleCov overwrites the same command name), so a per-file run makes other files
look uncovered, and a file's create-path lines covered by a different file's
spec look missed. Re-run the full baseline (step 1) before trusting a missed list
or an overall number.
- Line coverage only; branch coverage is out of scope.
- Test real objects; stub only external dependencies (network, DB clients, cloud services). Stub a collaborator at its boundary rather than reimplementing it.
- Genuinely test-unreachable lines (class-load code gated by env, methods that
only run a live external call) get
# simplecov:disable…# simplecov:enablewith a one-line reason, or a config-leveladd_filterfor a whole file. - A runtime guard like
return if Rails.env.test?is NOT unreachable: stubRails.env.test?after creating records, then call the method. - If a spec can only pass by proving the code is broken, stop and report the bug with a failing example — do not change the source to hide it.