Technical Note

Branch coverage for changed Ruby code

Global coverage numbers can hide the risk in the change under review. A branch-focused SimpleCov group makes review more concrete.

Technical Note Paulo Fidalgo

Global coverage numbers can hide the risk in the change you are reviewing.

A project can report a healthy overall coverage percentage while the current branch adds untested behavior.

The inverse is also true. A project with a weak historic baseline can still hold a high bar for new work.

That is why I like having a coverage view focused on files changed in the current branch.

The problem with one global number

Coverage is useful as a signal, but a single percentage is too blunt for day-to-day review. It says something about the repository, not necessarily about the pull request.

During review, the more useful question is narrower:

Did this change add behavior without tests?

SimpleCov already gives Ruby projects a good foundation. The missing piece is a group that only includes files changed against the reference branch.

A SimpleCov group for changed files

The setup is small: ask Git which Ruby files changed and group those files in the SimpleCov report.

require "pathname"
require "simplecov"

class ChangedRubyFiles
  def self.include?(filename)
    changed_files.include?(filename)
  end

  def self.changed_files
    @changed_files ||= `git diff --name-only main`.split("\n").filter_map do |file|
      path = Pathname.new(Rails.root.join(file))
      path.to_s if path.extname == ".rb"
    end
  end
end

SimpleCov.start("rails") do
  add_group "Current branch" do |source_file|
    ChangedRubyFiles.include?(source_file.filename)
  end
end

Use master instead of main if that is still the repository’s default branch.

How to use the signal

This should not become another vanity metric. It should make review sharper.

If a changed file appears in the group with poor coverage, the reviewer has a concrete place to look. If the missing lines are trivial, ignore them. If they are decision branches, failure paths, or permission checks, test them.

This also helps with legacy systems. You do not need to fix the entire repository before improving the discipline around new changes.

Caveats

This technique is intentionally small. It will not understand generated files, moved files, or every monorepo layout without adjustment. It also measures line execution, not test quality.

That is fine. The aim is not perfect coverage. The aim is to make the current branch harder to review lazily.

Addendum: SimpleCov and branch coverage today

SimpleCov can report branch coverage, but line coverage is still the default primary coverage type.

If you want branch coverage, enable it explicitly.

SimpleCov.start("rails") do
  enable_coverage :branch
end

You can also make branch coverage the primary metric:

SimpleCov.start("rails") do
  enable_coverage :branch
  primary_coverage :branch
end

That changes what SimpleCov emphasizes, but it does not replace the branch-focused group from this note.

These are two different questions:

  • branch coverage asks whether conditional paths were exercised.
  • changed-file grouping asks whether the current branch added untested code.

In practice, the better setup is often both.

Enable branch coverage, then add a group for changed Ruby files so review stays focused on the code being changed.

Useful reference: SimpleCov coverage criteria and primary coverage.