Bazel によるコード カバレッジ

問題を報告する ソースを表示 Nightly · 8.0 . 7.4 . 7.3 · 7.2 · 7.1 · 7.0 · 6.5

Bazel には、bazel coverage でテストできるリポジトリのコードカバレッジ レポートを生成する coverage サブコマンドがあります。さまざまな言語エコシステムの特殊性により、特定のプロジェクトでこれを機能させることは必ずしも簡単ではありません。

このページでは、カバレッジ レポートの作成と表示の一般的なプロセスについて説明します。また、構成がよく知られている言語については、言語固有のメモも記載しています。最初に一般的なセクションを読み、次に特定の言語の要件を確認することをおすすめします。リモート実行のセクションも参照してください。このセクションでは、追加の考慮事項について説明しています。

多くのカスタマイズが可能ですが、このドキュメントでは、現在最もサポートされているルートである lcov レポートの生成と使用に焦点を当てています。

カバレッジ レポートを作成する

準備

カバレッジ レポートを作成する基本的なワークフローは次のとおりです。

  • テスト対象を含む基本的なリポジトリ
  • 言語固有のコード カバレッジ ツールがインストールされた toolchain
  • 正しい「計測」構成

前者 2 つは言語固有で、ほとんど簡単ですが、後者は複雑なプロジェクトではより困難になる可能性があります。

この場合の「計測」とは、特定のターゲットに使用されるカバレッジ ツールを指します。Bazel では、--instrumentation_filter フラグを使用して、特定のサブセットのファイルに対してこの機能を有効にできます。このフラグは、計測を有効にしてテストされるターゲットのフィルタを指定します。テストのインストルメンテーションを有効にするには、--instrument_test_targets フラグが必要です。

デフォルトでは、bazel はターゲット パッケージとの一致を試み、関連するフィルタを INFO メッセージとして出力します。

実行カバレッジ

カバレッジ レポートを生成するには、bazel coverage --combined_report=lcov [target] を使用します。これにより、ターゲットのテストが実行され、各ファイルの lcov 形式のカバレッジ レポートが生成されます。

完了すると、bazel は、生成されたすべてのカバレッジ ファイルを収集して 1 つに統合するアクションを実行します。このファイルは最終的に $(bazel info output_path)/_coverage/_coverage_report.dat の下に作成されます。

テストが失敗した場合もカバレッジ レポートが生成されますが、失敗したテストには適用されません。レポートされるのは、合格したテストのみです。

記事の表示

カバレッジ レポートは、人が読める形式ではない lcov 形式でのみ出力されます。そこから、genhtml ユーティリティ(lcov プロジェクトの一部)を使用して、ウェブブラウザで表示できるレポートを生成できます。

genhtml --output genhtml "$(bazel info output_path)/_coverage/_coverage_report.dat"

genhtml はソースコードも読み取り、これらのファイルで不足しているカバレッジにアノテーションを付けます。これが機能するには、genhtml が bazel プロジェクトのルートで実行されることが想定されます。

結果を表示するには、genhtml ディレクトリに生成された index.html ファイルを任意のウェブブラウザで開きます。

genhtml ツールや lcov カバレッジ形式の詳細については、lcov プロジェクトをご覧ください。

リモート実行

リモート テスト実行で実行する場合は、現在いくつかの注意事項があります。

  • レポートの組み合わせアクションは、まだリモートで実行できません。これは、Bazel がカバレッジ出力ファイルをグラフの一部と見なさないためです(この問題を参照)。そのため、組み合わせアクションへの入力として正しく処理できません。この問題を回避するには、--strategy=CoverageReport=local を使用します。
    • 注: Bazel が local,remote を試すように設定されている場合、Bazel が戦略を解決する方法により、代わりに --strategy=CoverageReport=local,remote のようなものを指定することが必要になる場合があります。
  • --remote_download_minimal などのフラグも、前述の結果として使用できません。
  • 現在、テストが以前にキャッシュに保存されている場合、Bazel はカバレッジ情報を作成できません。この問題を回避するには、カバレッジ実行用に --nocache_test_results を特別に設定できますが、もちろんテスト時間の点で大きなコストが発生します。
  • --experimental_split_coverage_postprocessing--experimental_fetch_all_coverage_outputs
    • 通常、カバレッジはテスト アクションの一部として実行されるため、デフォルトでは、リモート実行の出力としてすべてのカバレッジが返されるわけではありません。これらのフラグはデフォルトをオーバーライドして、カバレッジ データを取得します。詳しくは、こちらの問題をご覧ください。

言語固有の構成

Java

Java は、デフォルトの構成ですぐに動作するはずです。bazel ツールチェーンには、JUnit など、リモート実行に必要なものがすべて含まれています。

Python

前提条件

Python でカバレッジを実行するには、いくつかの前提条件があります。

変更した coverage.py を使用する

これを行う方法の 1 つは、rules_python を使用する方法です。これにより、requirements.txt ファイルを使用できるようになります。ファイルにリストされている要件は、pip_install リポジトリ ルールを使用して bazel ターゲットとして作成されます。

requirements.txt には次のエントリが必要です。

git+https://github.com/ulfjack/coveragepy.git@lcov-support

rules_pythonpip_installrequirements.txt ファイルは、WORKSPACE ファイルで次のように使用する必要があります。

load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")

http_archive(
    name = "rules_python",
    url = "https://github.com/bazelbuild/rules_python/releases/download/0.5.0/rules_python-0.5.0.tar.gz",
    sha256 = "cd6730ed53a002c56ce4e2f396ba3b3be262fd7cb68339f0377a45e8227fe332",
)

load("@rules_python//python:pip.bzl", "pip_install")

pip_install(
   name = "python_deps",
   requirements = "//:requirements.txt",
)

BUILD ファイルで次のように設定すると、coverage.py の要件をテストターゲットで使用できます。

load("@python_deps//:requirements.bzl", "entry_point")

alias(
    name = "python_coverage_tools",
    actual = entry_point("coverage"),
)

py_test(
    name = "test",
    srcs = ["test.py"],
    env = {
        "PYTHON_COVERAGE": "$(location :python_coverage_tools)",
    },
    deps = [
        ":main",
        ":python_coverage_tools",
    ],
)

完全な Python ツールチェーンを使用している場合は、すべての py_test ターゲットにカバレッジ依存関係を追加する代わりに、カバレッジ ツールをツールチェーン構成に追加できます。

pip_install ルールは Python ツールチェーンに依存しているため、coverage モジュールの取得には使用できません。代わりに、WORKSPACE を追加します。

http_archive(
    name = "coverage_linux_x86_64"",
    build_file_content = """
py_library(
    name = "coverage",
    srcs = ["coverage/__main__.py"],
    data = glob(["coverage/*", "coverage/**/*.py"]),
    visibility = ["//visibility:public"],
)
""",
    sha256 = "84631e81dd053e8a0d4967cedab6db94345f1c36107c71698f746cb2636c63e3",
    type = "zip",
    urls = [
        "https://files.pythonhosted.org/packages/74/0d/0f3c522312fd27c32e1abe2fb5c323b583a5c108daf2c26d6e8dfdd5a105/coverage-6.4.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl",
    ],
)

次のように Python ツールチェーンを構成します。

py_runtime(
    name = "py3_runtime_linux_x86_64",
    coverage_tool = "@coverage_linux_x86_64//:coverage",
    files = ["@python3_9_x86_64-unknown-linux-gnu//:files"],
    interpreter = "@python3_9_x86_64-unknown-linux-gnu//:bin/python3",
    python_version = "PY3",
)

py_runtime_pair(
    name = "python_runtimes_linux_x86_64",
    py2_runtime = None,
    py3_runtime = ":py3_runtime_linux_x86_64",
)

toolchain(
    name = "python_toolchain_linux_x86_64",
    exec_compatible_with = [
        "@platforms//os:linux",
        "@platforms//cpu:x86_64",
    ],
    toolchain = ":python_runtimes_linux_x86_64",
    toolchain_type = "@bazel_tools//tools/python:toolchain_type",
)