GitLab CI’s code coverage reporting is surprisingly powerful, but it doesn’t actually run your tests or calculate coverage itself; it just parses the output.

Let’s see it in action. Imagine you have a simple Python project with a coverage.py setup. Your gitlab-ci.yml might look like this:

stages:
  - test

coverage_test:
  stage: test
  image: python:3.9
  script:
    - pip install pytest pytest-cov
    - pytest --cov=.
    - coverage report -m > coverage.txt
  artifacts:
    reports:
      coverage_report:
        coverage_format: cobertura
        path: coverage.xml

Before you can get that coverage.xml file, you need to generate it. coverage.py can do this with the --cov-report=xml flag. So, a more realistic script section would be:

stages:
  - test

coverage_test:
  stage: test
  image: python:3.9
  script:
    - pip install pytest pytest-cov
    - pytest --cov=. --cov-report=xml
  artifacts:
    reports:
      coverage_report:
        coverage_format: cobertura
        path: coverage.xml

When this pipeline runs, GitLab CI executes the script commands. pytest --cov=. --cov-report=xml runs your tests and, crucially, generates a coverage.xml file in Cobertura format. This coverage.xml file is then uploaded as an artifact. GitLab CI’s frontend then parses this artifact to display the coverage percentage directly in the merge request and pipeline views.

The problem this solves is gaining visibility into how much of your codebase is actually exercised by your automated tests. Without this, you might have tests that pass, but they could be missing large swathes of your application’s logic, leaving you with a false sense of security. GitLab CI’s integration turns raw coverage data into actionable insights.

Internally, GitLab CI doesn’t care how the coverage.xml file was generated, as long as it adheres to the Cobertura schema. It just needs a file at the specified path with the correct coverage_format. This makes it incredibly flexible, allowing you to use various testing frameworks and coverage tools as long as they can output in the supported formats (Cobertura being the most common). The key levers you control are:

  • coverage_format: This tells GitLab CI what kind of XML file to expect. cobertura is standard.
  • path: This is the file path within your CI job’s artifact directory where the coverage report will be found.
  • The script itself: This is where you invoke your test runner and coverage tool to produce the artifact.

The Cobertura XML format is a structured way to represent coverage data, detailing which files were covered, which lines within those files were executed, and how many times. It’s a widely adopted standard, ensuring compatibility with many coverage tools across different languages.

Many people struggle with the fact that GitLab CI doesn’t compute coverage; it merely consumes a report. If your coverage.xml file is malformed or not generated at all, GitLab CI will simply show no coverage data. You need to ensure your test command produces a valid Cobertura XML artifact.

The next concept you’ll likely encounter is setting up custom parsing for coverage reports if your tools don’t output in a standard format, or if you want to track coverage for specific branches or directories.

Want structured learning?

Take the full Gitlab-ci course →