Cobertura

Cobertura XML is a code coverage report format — usually a file called coverage.xml — that records, for every source file, which lines ran and how many times, plus which branch outcomes were taken. The name comes from Cobertura, a Java coverage tool that is no longer actively developed. The format stayed because CI systems learned to read it, and then every other coverage tool learned to write it.

What is in a file

<?xml version="1.0" ?>
<coverage line-rate="0.8125" branch-rate="0.5" lines-covered="13"
          lines-valid="16" branches-covered="2" branches-valid="4"
          timestamp="1729756800000" version="7.6">
  <sources>
    <source>/home/ci/project/src</source>
  </sources>
  <packages>
    <package name="cart" line-rate="0.8125" branch-rate="0.5">
      <classes>
        <class name="cart.py" filename="cart/cart.py"
               line-rate="0.8125" branch-rate="0.5">
          <methods/>
          <lines>
            <line number="12" hits="9"/>
            <line number="14" hits="9" branch="true"
                  condition-coverage="50% (1/2)" missing-branches="18"/>
            <line number="18" hits="0"/>
          </lines>
        </class>
      </classes>
    </package>
  </packages>
</coverage>
Element / attribute Meaning
<coverage> Root; overall rates and counts for the run
<source> Base directory that filename paths are relative to
<package> A directory, module or namespace
<class filename="…"> One source file (or one class, in Java and .NET)
<method> Optional; a function with its own <lines>
<line number hits> One executable line and how often it ran; hits="0" = missed
branch="true" The line contains a condition
condition-coverage="50% (1/2)" Outcomes taken / outcomes possible on that line
line-rate, branch-rate Fractions from 0 to 1, not percentages
complexity Cyclomatic complexity, often 0 when the tool doesn't compute it

Things that trip people up

  • Rates are fractions. line-rate="0.8125" is 81.25%. Scripts that print it with a % sign are off by a factor of 100.
  • Branches are counted per line. A line with if a and b: reports all of its outcomes in one condition-coverage fraction; Cobertura can't tell you which outcome was missed unless the tool adds a non-standard hint like coverage.py's missing-branches.
  • Paths are relative to <source>. GitLab and Azure DevOps map filename onto the repository. If CI ran in a different directory, or the tool wrote absolute paths, the report parses but no file matches — coverage silently disappears from the merge request diff.
  • One file can appear as several classes. Java and .NET emit a <class> per class, so a file with an inner class shows up twice. Aggregate by filename, not by class name.
  • The top-level rates aren't always reliable. Merged or converted reports sometimes carry stale totals; recompute from the <line> elements when the numbers disagree.

Cobertura vs LCOV

Cobertura LCOV
Encoding XML Plain text, one record per line
Grouping package → class → line source file → line / branch / function
Branch detail Per line: (1/2) Per branch: BRDA:line,block,branch,taken
Common in Python, .NET, Java, GitLab, Azure JavaScript, C/C++, Rust, Flutter
Read natively by GitLab MR diffs, Azure Pipelines Codecov, Coveralls, editor gutters

Most tools can write both. If the destination is GitLab or Azure DevOps, write Cobertura; if it is genhtml or an editor extension, write LCOV.

Reading one without a CI system

Comma's free coverage viewer reads Cobertura, LCOV and Clover in your browser: drop in coverage.xml and get the overall line and branch percentages, a roll-up per directory, and every file sorted least-covered first with its uncovered line ranges — plus a Markdown summary for a pull request. Publish it and the report becomes a link reviewers can comment on, file by file.

Try it

Comma is free — unlimited reports, unlimited commenters, unlimited revision history.

Open the coverage viewer →

Related