テストカバレッジの可視化

GitLab CI/CDの助けを借りて、お気に入りのテストツールやカバレッジ分析ツールのテストカバレッジ情報を収集し、マージリクエスト(MR)のファイル差分ビュー内でこの情報を可視化することができます。これにより、MRがマージされる前に、どの行がテストでカバーされ、どの行がまだカバレッジが必要かを確認することができます。

Test Coverage Visualization Diff View

テストカバレッジの可視化の仕組み

カバレッジ情報の収集はGitLabのCI/CDアーティファクトレポート機能を使って行います。収集する1つ以上のカバレッジレポートを、ワイルドカードパスを含めて指定することができます。そしてGitLabは全てのファイルのカバレッジ情報を取得し、結合します。カバレッジファイルはバックグラウンドジョブで解析されるため、パイプラインが完了してからページ上で可視化されるまでに遅延が発生することがあります。

カバレッジ分析が機能するためには、適切なフォーマットのCobertura XMLレポートをartifacts:reports:coverage_report に提供する必要があります。このフォーマットはもともと Java 用に開発されたものですが、他の言語用のカバレッジ分析フレームワークの多くには、そのサポートを追加するプラグインがあります:

他のカバレッジ分析フレームワークもこのフォーマットをサポートしています:

設定後、マージリクエストがカバレッジレポートを収集するパイプラインをトリガーした場合、カバレッジ情報が diff ビューに表示されます。これには、パイプラインのどのステージのどのジョブのレポーターも含まれます。カバレッジは行ごとに表示されます:

  • covered (緑): テストによって少なくとも一度はチェックされた行
  • no test coverage (オレンジ): 読み込まれたが実行されなかった行
  • カバレッジ情報なし: インストゥルメントされていないか、ロードされていない行

カバレッジバーにカーソルを合わせると、その行がテストでチェックされた回数など、さらに詳しい情報が表示されます。

テストカバレッジレポートをアップロードしても有効になりません:

これらは別途設定する必要があります。

限界

Cobertura 形式の XML ファイルには、100<source> ノードという制限が適用されます。Cobertura レポートが 100 ノードを超える場合、マージリクエスト差分ビューで不一致または一致しないことがあります。

1つの Cobertura XML ファイルは 10 MiB 以下です。大きなプロジェクトでは、Cobertura XML を小さなファイルに分割してください。詳細はこのイシューを参照してください。多数のファイルを送信する場合、マージリクエストでカバレッジが表示されるまでに数分かかることがあります。

可視化はパイプラインが完了してから表示されます。パイプラインに手動ジョブがブロックされている場合、パイプラインは続行する前に手動ジョブを待機し、完了したとは見なされません。ブロック手動ジョブが実行されなかった場合、可視化は表示されません。

データの有効期限

GitLab 13.12で導入され、有効期限に関係なく最新のデータが保持されます。

デフォルトでは、マージリクエストでビジュアライゼーションを描画するために使用されたデータの有効期限は、作成から1週間後です。

子パイプラインからのカバレッジレポート

子パイプラインのジョブがカバレッジレポートを作成した場合、そのレポートは親パイプラインのカバレッジレポートに含まれます。

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

クラスパスの自動修正

カバレッジレポートは、class 要素のfilename にプロジェクトルートからの相対パスが含まれている場合にのみ、変更されたファイルに適切にマッチします。しかし、いくつかのカバレッジ分析フレームワークでは、生成されたCobertura XMLは、代わりにクラスパッケージディレクトリへの相対パスfilename

プロジェクト・ルートからの相対パスclass をインテリジェントに推測するために、Cobertura XML パーサーは以下の方法でフル・パスの構築を試みます:

  • sources 要素からsource パスの一部を抽出し、クラスfilename パスと組み合わせます。
  • 候補パスがプロジェクトに存在するかどうかをチェックします。
  • 一致した最初の候補をクラスのフル・パスとして使用します。

パス修正例

例として、以下のような C# プロジェクトがあります:

  • test-org/test-cs-project のフルパス。
  • プロジェクトルートからの相対パス:

     Auth/User.cs
     Lib/Utils/User.cs
    
  • sources Cobertura XMLから、<CI_BUILDS_DIR>/<PROJECT_FULL_PATH>/...

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

パーサー:

  • sources からAuthLib/Utils を抽出し、これらを使ってプロジェクトルートからの相対パスclass を決定します。
  • これらの抽出されたsources とクラス・ファイル名を結合します。たとえば、filename の値がUser.cs であるclass 要素がある場合、パーサーは最初に一致するパスの候補を取ります。これはAuth/User.cs です。
  • class 要素について、抽出された各source パスの一致を 100 回まで試行します。ファイル・ツリーで一致するパスが見つからずにこの制限に達した場合、そのクラスは最終的なカバレッジ・レポートに含まれません。

クラス・パスの自動修正は、以下の Java プロジェクトでも機能します:

  • test-org/test-java-project のフルパス。
  • プロジェクトルートからの相対パス:

     src/main/java/com/gitlab/security_products/tests/App.java
    
  • sources Cobertura XMLから:

     <sources>
       <source>/builds/test-org/test-java-project/src/main/java/</source>
     </sources>
    
  • class 要素のfilename 値はcom/gitlab/security_products/tests/App.java です:

     <class name="com.gitlab.security_products.tests.App" filename="com/gitlab/security_products/tests/App.java" line-rate="0.0" branch-rate="0.0" complexity="6.0">
    
note
クラス・パスの自動修正は、<CI_BUILDS_DIR>/<PROJECT_FULL_PATH>/... という形式のsource パスに対してのみ機能します。パスがこのパターンに従わない場合、source は無視されます。パーサーは、class 要素のfilename には、プロジェクト・ルートからの相対パスがフル・パスで含まれているとみなします。

テストカバレッジの設定例

このセクションでは、さまざまなプログラミング言語のテストカバレッジ設定例を示します。また、coverage-report デモプロジェクトで動作例を見ることができます。

JavaScript の例

次の.gitlab-ci.yml の例では、MochaJavaScript テストとnyccoverage-tooling を使ってカバレッジのアーティファクトを生成しています:

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

Java と Kotlin の例

Mavenの例

以下のJavaまたはKotlinの.gitlab-ci.yml の例では、プロジェクトをビルドするためにMavenを使用し、カバレッジアーティファクトを生成するためにJaCoCoカバレッジツーリングを使用しています。独自のイメージをビルドしたい場合は、Dockerイメージの設定とスクリプトを確認できます。

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を使用しています。独自のイメージをビルドしたい場合は、Dockerイメージの設定とスクリプトを確認できます。

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 example

次の.gitlab-ci.yml の例では、pytest-covを使ってテストカバレッジデータを収集しています:

run tests:
  stage: test
  image: python:3
  script:
    - pip install pytest pytest-cov
    - pytest --cov --cov-report term --cov-report xml: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 設定に依存します。適切なパスに Cobertura を見つけるように.gitlab-ci.yml を設定してください。

C/C++の例

コンパイラとしてgcc またはg++ を使用した C/C++ の次の.gitlab-ci.yml の例では、gcovr を使用して Cobertura XML 形式のカバレッジ出力ファイルを生成しています。

この例では

  • Makefile は、build ディレクトリのcmake によって、前のステージの別のジョブ内部で作成されます。(Makefile を生成するためにautomake を使用する場合は、make testの代わりにmake check を呼び出す必要があります)。
  • 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の例

次の.gitlab-ci.yml 、Goの例です:

  • go test を使ってテストを実行します。
  • gocover-cobertura を使用して、Go のカバレッジ・プロファイルを Cobertura XML フォーマットに変換します。

この例では、Go モジュールを使用することを前提としています。-covermode count オプションはフラグと併用できません-race-raceフラグを-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

  • rspec を使ってテストを実行します。
  • simplecov およびsimplecov-cobertura を使用してカバレッジプロファイルを記録し、Cobertura XML 形式でレポートを作成します。

この例では

  • bundler が依存関係管理に使用されていること。rspec,simplecov およびsimplecov-cobertura gems がGemfileに追加されています。
  • CoberturaFormatterspec_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