测试覆盖率可视化

借助极狐GitLab CI/CD,您可以收集您喜欢的测试或覆盖率分析工具的测试覆盖信息,并在合并请求 (MR) 的文件差异视图中可视化此信息。允许您在合并 MR 之前查看哪些行被测试覆盖,哪些行仍然需要覆盖。

Test Coverage Visualization Diff View

测试覆盖率可视化工作原理

收集覆盖信息是通过极狐GitLab CI/CD 的产物报告功能完成的。 您可以指定一个或多个要收集的覆盖报告,包括通配符路径。 然后系统获取所有文件中的覆盖信息并将其组合在一起。覆盖文件在后台作业中解析,因此从流水线完成到页面上的可视化加载之间可能存在延迟。

为了使覆盖分析起作用,您必须向 artifacts:reports:coverage_report 提供格式正确的 Cobertura XML 报告。 这种格式最初是为 Java 开发的,但大多数其它语言的覆盖分析框架都有插件来添加对它的支持,例如:

其它覆盖分析框架支持开箱即用的格式,例如:

配置后,如果您创建一个触发收集覆盖率报告的流水线的合并请求,则覆盖率将显示在差异视图中,包括来自流水线中任何阶段的任何作业的报告。每行的覆盖率显示如下:

  • covered(绿色):通过测试至少检查过一次的行
  • no test coverage(橙色):已加载但从未执行的行
  • 无覆盖信息:未插入或未加载的行

将鼠标悬停在覆盖栏上可提供更多信息,例如测试检查代码行的次数。

上传测试覆盖率报告不会启用:

您必须单独配置这些。

限制

Cobertura 格式 XML 文件的 100 个 <source> 节点的限制适用。如果您的 Cobertura 报告超过 100 个节点,则合并请求差异视图中可能存在不匹配或无匹配。

单个 Cobertura XML 文件不能超过 10MiB。对于大型项目,将 Cobertura XML 拆分为较小的文件。 提交许多文件时,可能需要几分钟才能在合并请求中显示覆盖范围。

可视化仅在流水线完成后显示。如果流水线有阻塞的手动作业,则流水线在继续之前等待手动作业,并且不被视为完成。 如果阻塞的手动作业未运行,则无法显示可视化。

产物过期

默认情况下,用于在合并请求上绘制可视化的流水线产物在创建后一周过期。

来自子流水线的覆盖率报告

  • 引入于 15.1 版本,功能标志为 ci_child_pipeline_coverage_reports,默认禁用。
  • 在 SaaS 版和私有化部署版上启用于 15.2 版本,功能标志 ci_child_pipeline_coverage_reports 已删除。

如果子流水线中的作业创建了覆盖率报告,则该报告将包含在父流水线的覆盖率报告中。

child_test_pipeline:
  trigger:
    include:
      - local: path/to/child_pipeline_with_coverage.yml

自动类路径校正

  • 引入于 13.8 版本。
  • 功能标志移除于 13.9 版本。

只有当 class 元素的 filename 包含相对于项目根目录的完整路径时,覆盖率报告才能正确匹配更改的文件。但是,在某些覆盖分析框架中,生成的 Cobertura XML 具有相对于类包目录的 filename 路径。

为了对项目根相对 class 路径做出明智的猜测,Cobertura XML 解析器尝试通过以下方式构建完整路径:

  • sources 元素中提取部分 source 路径,并将它们与类 filename 路径组合。
  • 检查项目中是否存在候选路径。
  • 使用匹配的第一个候选者作为类完整路径。

路径校正示例

例如,一个项目具有:

  • test-org/test-project 的完整路径。
  • 与项目根目录相关的以下文件:

    Auth/User.cs
    Lib/Utils/User.cs
    src/main/java
    

位于:

  • Cobertura XML 中,class 元素中的 filename 属性假定该值是项目根目录的相对路径:

    <class name="packet.name" filename="src/main/java" line-rate="0.0" branch-rate="0.0" complexity="5">
    
  • 来自 Cobertura XML 的 sources,格式为 <CI_BUILDS_DIR>/<PROJECT_FULL_PATH>/... 的以下路径:

    <sources>
      <source>/builds/test-org/test-project/Auth</source>
      <source>/builds/test-org/test-project/Lib/Utils</source>
    </sources>
    

解析器:

  • sources 中提取 AuthLib/Utils 并使用它们来确定相对于项目根目录的 class 路径。
  • 结合这些提取的 sources 和类文件名。例如,有一个 class 元素的 filename 值为 User.cs,则解析器会采用第一个匹配的候选路径,即 Auth/User.cs
  • 对于每个 class 元素,尝试为每个提取的 source 路径查找匹配项,最多 100 次迭代。如果在文件树中没有找到匹配的路径就达到了这个限制,那么这个类就不会包含在最终的覆盖率报告中。
note 自动类路径更正仅适用于格式为 <CI_BUILDS_DIR>/<PROJECT_FULL_PATH>/...source 路径。 如果路径不遵循此模式,则忽略 source。解析器假定 class 元素的 filename 包含相对于项目根目录的完整路径。

示例测试覆盖率配置

本节提供不同编程语言的测试覆盖率配置示例。

JavaScript 示例

以下 .gitlab-ci.yml 示例使用 Mocha JavaScript 测试和 nyc 覆盖率工具生成覆盖率产物:

test:
  script:
    - npm install
    - npx nyc --reporter cobertura mocha
  artifacts:
    reports:
      coverage_report:
        coverage_format: cobertura
        path: coverage/cobertura-coverage.xml

Java and Kotlin 示例

Maven 示例

以下用于 Java 或 Kotlin 的 .gitlab-ci.yml 示例使用 Maven 来构建项目和 JaCoCo 覆盖率工具来生成覆盖率产物。

极狐GitLab 需要 Cobertura 格式的产物,因此您必须在上传之前执行一些脚本。test-jdk11 作业测试代码并生成 XML 工件。coverage-jdk-11 作业将产物转换为 Cobertura 报告:

test-jdk11:
  stage: test
  image: maven:3.6.3-jdk-11
  script:
    - mvn $MAVEN_CLI_OPTS clean org.jacoco:jacoco-maven-plugin:prepare-agent test jacoco:report
  artifacts:
    paths:
      - target/site/jacoco/jacoco.xml

coverage-jdk11:
  # Must be in a stage later than test-jdk11's stage.
  # The `visualize` stage does not exist by default.
  # Please define it first, or choose an existing stage like `deploy`.
  stage: visualize
  image: registry.gitlab.com/haynes/jacoco2cobertura:1.0.7
  script:
    # convert report from jacoco to cobertura, using relative project path
    - python /opt/cover2cover.py target/site/jacoco/jacoco.xml $CI_PROJECT_DIR/src/main/java/ > target/site/cobertura.xml
  needs: ["test-jdk11"]
  artifacts:
    reports:
      coverage_report:
        coverage_format: cobertura
        path: target/site/cobertura.xml

Gradle 示例

以下用于 Java 或 Kotlin 的 .gitlab-ci.yml 示例使用 Gradle 构建项目和 JaCoCo 覆盖率工具来生成覆盖率产物。

极狐GitLab 需要 Cobertura 格式的产物,因此您必须在上传之前执行一些脚本。test-jdk11 作业测试代码并生成 XML 产物。coverage-jdk-11 作业将产物转换为 Cobertura 报告:

test-jdk11:
  stage: test
  image: gradle:6.6.1-jdk11
  script:
    - 'gradle test jacocoTestReport' # jacoco must be configured to create an xml report
  artifacts:
    paths:
      - build/jacoco/jacoco.xml

coverage-jdk11:
  # Must be in a stage later than test-jdk11's stage.
  # The `visualize` stage does not exist by default.
  # Please define it first, or chose an existing stage like `deploy`.
  stage: visualize
  image: registry.gitlab.com/haynes/jacoco2cobertura:1.0.7
  script:
    # convert report from jacoco to cobertura, using relative project path
    - python /opt/cover2cover.py build/jacoco/jacoco.xml $CI_PROJECT_DIR/src/main/java/ > build/cobertura.xml
  needs: ["test-jdk11"]
  artifacts:
    reports:
      coverage_report:
        coverage_format: cobertura
        path: build/cobertura.xml

Python 示例

以下 Python 的 .gitlab-ci.yml 示例使用 pytest-cov 收集测试覆盖率数据,coverage.py 将报告转换为使用完整的相对路径。没有转换就不会显示信息。

此示例假设您的包的代码在 src/ 中并且您的测试在 tests.py 中:

run tests:
  stage: test
  image: python:3
  script:
    - pip install pytest pytest-cov
    - coverage run -m pytest
    - coverage report
    - coverage xml
  coverage: '/(?i)total.*? (100(?:\.0+)?\%|[1-9]?\d(?:\.\d+)?\%)$/'
  artifacts:
    reports:
      coverage_report:
        coverage_format: cobertura
        path: coverage.xml

PHP 示例

以下 PHP 的 .gitlab-ci.yml 示例使用 PHPUnit 来收集测试覆盖率数据并生成报告。

使用最小的 phpunit.xml 文件,您可以运行测试并生成 coverage.xml

run tests:
  stage: test
  image: php:latest
  variables:
    XDEBUG_MODE: coverage
  before_script:
    - apt-get update && apt-get -yq install git unzip zip libzip-dev zlib1g-dev
    - docker-php-ext-install zip
    - pecl install xdebug && docker-php-ext-enable xdebug
    - php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
    - php composer-setup.php --install-dir=/usr/local/bin --filename=composer
    - composer install
    - composer require --dev phpunit/phpunit phpunit/php-code-coverage
  script:
    - php ./vendor/bin/phpunit --coverage-text --coverage-cobertura=coverage.cobertura.xml
  artifacts:
    reports:
      coverage_report:
        coverage_format: cobertura
        path: coverage.cobertura.xml

Codeception,通过 PHPUnit,也支持用 run 生成 Cobertura 报告。生成文件的路径取决于单元测试套件--coverage-cobertura 选项和 paths 配置。配置 .gitlab-ci.yml 在适当的路径中找到 Cobertura。

C/C++ 示例

以下 .gitlab-ci.yml 示例适用于带有 gccg++ 的 C/C++,因为编译器使用 gcovr 生成 Cobertura XML 格式的覆盖率输出文件。

此示例假设:

  • Makefile 是在前一阶段的另一个作业中,由 cmakebuild 目录中创建的。(如果使用 automake 生成 Makefile,则需要调用 make check 而不是 make test。)
  • cmake(或 automake)设置了编译器选项 --coverage
run tests:
  stage: test
  script:
    - cd build
    - make test
    - gcovr --xml-pretty --exclude-unreachable-branches --print-summary -o coverage.xml --root ${CI_PROJECT_DIR}
  coverage: /^\s*lines:\s*\d+.\d+\%/
  artifacts:
    name: ${CI_JOB_NAME}-${CI_COMMIT_REF_NAME}-${CI_COMMIT_SHA}
    expire_in: 2 days
    reports:
      coverage_report:
        coverage_format: cobertura
        path: build/coverage.xml

Go 示例

以下 Go 使用的 .gitlab-ci.yml 示例:

此示例假定正在使用 Go 模块。请注意,-covermode count 选项不适用于 -race 标志。 如果您想在使用 -race 标志的同时生成代码覆盖率,则必须切换到比 -covermode count 慢的-covermode atomic。有关详细信息,请参阅 此博客文章

run tests:
  stage: test
  image: golang:1.17
  script:
    - go install
    - go test ./... -coverprofile=coverage.txt -covermode count
    - go get github.com/boumenot/gocover-cobertura
    - go run github.com/boumenot/gocover-cobertura < coverage.txt > coverage.xml
  artifacts:
    reports:
      coverage_report:
        coverage_format: cobertura
        path: coverage.xml

Ruby 示例

以下用于 Ruby 的 .gitlab-ci.yml 示例使用:

此示例假设:

  • bundler 被用于依赖管理。rspecsimplecovsimplecov-cobertura gem 已添加到您的 Gemfile 中。
  • CoberturaFormatter 已添加到 spec_helper.rb 文件中的SimpleCov.formatters 配置中。
run tests:
  stage: test
  image: ruby:3.1
  script:
    - bundle install
    - bundle exec rspec
  artifacts:
    reports:
      coverage_report:
        coverage_format: cobertura
        path: coverage/coverage.xml

故障排除

未显示测试覆盖率可视化

如果测试覆盖率可视化未显示在差异视图中,您可以检查覆盖率报告本身并验证:

  • 您在差异视图中查看的文件在覆盖率报告中提及。
  • 报告中的 sourcefilename 节点遵循预期结构,匹配仓库中的文件。

默认情况下,报告产物不可下载。如果您希望可以从作业详细信息页面下载报告,请将您的覆盖率报告添加到产物 paths 中:

artifacts:
  paths:
    - coverage/cobertura-coverage.xml
  reports:
    coverage_report:
      coverage_format: cobertura
      path: coverage/cobertura-coverage.xml