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 onecondition-coveragefraction; Cobertura can't tell you which outcome was missed unless the tool adds a non-standard hint like coverage.py'smissing-branches. - Paths are relative to
<source>. GitLab and Azure DevOps mapfilenameonto 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 byfilename, not byclass 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.