0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【pytest/coverage.py】pytestでC0/C1カバレッジを分けて確認できるようにしました

0
Posted at

はじめに

Pythonテスティングフレームワークであるpytest, unittestなどで、カバレッジを計測する際にはcoverage.pyというライブラリが内部的に利用されています。

coverage.py v7.11.3までは、分岐網羅率(以下、C1カバレッジ)を単体で確認することはできず、C0カバレッジ単体か、C0/C1カバレッジの合算値しか確認できませんでした。

そこで、C0/C1カバレッジを分けて表示できるようにするPull Requestを作成し、コントリビュートしました
(この変更は、2025/11/18リリースのv7.12.0に取り込まれています。)

この記事では、pytest/unittestでC0/C1カバレッジを分けて確認する手順と、coverage.pyの挙動に関連するポイントについて説明します。

目次

想定読者

  • pytest / unittest でC0カバレッジとC1カバレッジを分けて確認したい方
  • --cov-branchオプションを使っているが、「これ本当にC1カバレッジなのか?」と疑問に感じたことがある方
  • pytestでカバレッジがどのように計測されているかを、ざっくり把握しておきたい方

TL;DR

  • pytest-covなどを使っていて「これ本当に分岐網羅?」と感じている場合は、coverage.py v7.12.0以降にアップデートすると、C0とC1を分けて確認できるようになります
  • 未導入の場合は、pytest-cov を導入して --cov-branch を付けて実行すると、HTMLレポートでC0とC1を分けて確認できるようになります

※ pytestのカバレッジは内部的にcoverage.pyによって計測されています

用語定義

本記事では、以下の用語を使用します。

  • カバレッジ
    • テストによって、どの程度コードが実行・検証されたかを表す割合
  • C0カバレッジ = 命令網羅率
  • C1カバレッジ = 分岐網羅率

これまでのcoverage.pyの仕様

coverage.py v7.11.3までは、--branchやpytest-covの--cov-branchを指定したときに表示されるカバレッジは、C1カバレッジ単体の値ではありませんでした。

この時に表示されるカバレッジは、C0とC1を合算したものになります。

式としては、以下のようになります。

$$
\text{カバレッジ} =
\frac{\text{実行された命令数} + \text{実行された分岐数}}
{\text{総命令数} + \text{総分岐数}}
$$

一方、本来のC1カバレッジは、以下のように分岐だけを分母・分子にして計算します。

$$
\text{C1カバレッジ} =
\frac{\text{実行された分岐数}}
{\text{総分岐数}}
$$

つまり、--cov-branchで表示される値は、純粋なC1カバレッジではなく、「C0とC1カバレッジの合算値」でした。

例えば、以下のPythonコードを考えます。

age.py
def is_minor(age: int) -> bool:
    """Return True if the age is under 18."""
    if age < 18:
        return True
    else:
        return False

18歳未満かどうかで分岐するコードですが、テストコードでは、18歳未満のケースだけを確認します。

test_age.py
"""Tests for is_minor function."""

from age import is_minor


def test_is_minor_with_child_age() -> None:
    """18歳未満の場合、Trueを返すこと."""
    assert is_minor(17) is True

このテストでは、

  • age < 18がTrueになる分岐
  • age < 18がFalseになる分岐

のうち、True側しか通っていません。

つまり、本来のC1カバレッジは50%です。

しかし、v7.11.3まででは、このケースのカバレッジは67%と表示されます。

pytest --cov=. --cov-branch --cov-report html

v7.11.3の場合のカバレッジレポート

このように、--cov-branchを利用していても、
表示される値は、純粋なC1カバレッジではありませんでした。

この問題に対する対応

この問題を解消するため、C0/C1カバレッジを個別に表示できるようにするPull Requestを作成し、coverage.pyにコントリビュートしました。

これらの変更は、2025/11/18リリースのcoverage.py v7.12.0に取り込まれています。

これにより、命令網羅(C0)と分岐網羅(C1)を個別に確認できるようになりました。

v7.12.0でC0/C1が分離表示されたカバレッジレポート

カバレッジ取得の手順

pytest-covを導入していない場合

まず、pytest-covをインストールします。

pipの場合:

pip install pytest-cov

uvの場合:

uv add --dev pytest-cov

その後、以下のコマンドでカバレッジを計測できます。

pytest --cov=. --cov-branch --cov-report html

※ uv環境の場合は uv run pytest を利用してください。

実行後、HTMLレポートがhtmlcov/index.htmlに出力されます。

coverage.py v7.12.0以降では、HTMLレポート上でC0/C1カバレッジを個別に確認できるようになります。

pytest-covを導入済みの場合

pytest-covを既に利用している場合は、coverage.pyをv7.12.0以降へ更新します。
pipの場合:

pip install -U coverage

uvの場合:

uv add --dev coverage@latest

バージョン確認は以下で行えます。

coverage --version

アップデート後、再度pytestを実行します。

pytest --cov=. --cov-branch --cov-report html

※ uv環境の場合は uv run pytest を利用してください。

pytestのカバレッジ取得の仕組み

pytestで--covオプションを利用した場合、内部的には以下のような構成でカバレッジが計測されています。

pytest
  ↓
pytest-cov
  ↓
coverage.py

つまり、pytestで表示されるカバレッジの仕様や表示形式は、最終的にはcoverage.pyの仕様に依存しています。

そのため、今回の変更もpytest-cov側ではなく、coverage.py側へのコントリビュートによって実現されています。

まとめ

  • --cov-branchで表示される値は、以前は命令網羅と分岐網羅の合算値だった
  • coverage.py v7.12.0以降では、C0/C1カバレッジを個別に確認できる
  • pytestのカバレッジは内部的にcoverage.pyによって計測されている

「分岐網羅を確認しているつもりだったが、実際には合算値だった」という違和感を持っていた方は、coverage.pyのバージョンを確認してみてください。

同じ違和感を持っていた方の参考になれば幸いです。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?