pytest-covを用いたカバレッジの算出

これなーに

pytestの利用方法をこちらで書いた。 raishi12.hatenablog.com

今回は、pytestを用いてcoverageを算出する方法をまとめる。

カバレッジとは

システムテストにおけるカバレッジとは以下。
参考: テストカバレッジ(コードカバレッジ)とは - 意味をわかりやすく - IT用語辞典 e-Words

テストカバレッジとは、ソフトウェアテストの進捗を表す尺度の一つで、テスト対象のソースコードのうち、どの程度の割合のコードがテストされたかを表すもの。

すなわち、書いたコード全体に対して何%の箇所をテストできたかを示す数値。
この値が高いほどテストがしっかりとされていると判断できるし、この値が低いとテストがガバガバということになる。

カバレッジの実施方法

pytestとpytest-covを用いる。
調べてみると、poetryと一緒に使ってpyproject.tomlにカバレッジの設定を記載する方法が多いので本記事でもそれに準ずる。(poetry使わなくても良いんだとは思うけど。)

手順

1) pytestとpytest-covをpoetryでインストールする。

poetry add --group dev pytest
poetry add --group dev pytest-cov

2) テストしたい処理に対してテスト内容を検討する。

今回は超簡易的に以下のコードに対するテストを実施することにする。

work.py

# -*- coding: utf-8 -*-
def add_nums(a, b):
    return a + b


def main(a, b):
    c = add_nums(a, b)

    if c == 3:
        print('c is 3')
        return True
    else:
        print('c is not 3')
        return False


if __name__ == '__main__':
    main()

引数a, bを受け取って、a+bを計算し、その合計が3ならTrue・それ以外ならFalseが返るというシンプルな処理。
この場合、テスト項目は、二つの引数が3の場合とそうでない場合の2パターンを試せば良さそう。

3) テストコードを書く。

2)で列挙した内容に対応するテストコードを記載する。

test_work.py

# -*- coding: utf-8 -*-
import pathlib
import sys
sys.path.append(str(pathlib.Path.cwd()))
import src.work as work


class TestWork:
    def test_result_ok(self):
        result = work.main(1, 2)
        assert result, 'Trueじゃないよ。'
        print('test_result is ok.')

    def test_result_ng(self):
        result = work.main(2, 2)
        assert not result, 'Falseじゃないよ。'
        print('test_result is ok.')

if __name__ == '__main__':
    main()

test_result_ok関数で引数に1,2を与えた時のreturnを受け取り、test_result_ng関数で引数に2,2を与えた時のreturnを受け取っている。
これはそれぞれ、True/Falseの結果が期待できるものになっている。

ここまでのprojectのディレクトリ構成は以下のような形。

pairent_dir/
┣ src
┃ ┗ work.py
┣ tests
┃ ┗ test_work.py
┣ poetry.lock
┗ pyproject.toml

4) pyproject.tomlにカバレッジの設定を記載する。

pyproject.tomlに以下を追記する。

[tool.coverage]
    [tool.coverage.run]
    data_file = "report/.coverage"
    branch = true
    parallel = true
    omit = [
        "*utils.py"
        "*/tests/*",
        "*/__init__.py",
    ]
    [tool.coverage.report]
    exclude_lines = [
        "pragma: no cover",
        "if __name__ == .__main__.:",
    ]
    [tool.coverage.html]
    directory = "report/htmlcov/"
    [tool.coverage.xml]
    output = "report/coverage.xml"

それぞれの意味は、以下の通り。

分類 オプション 内容
tool.coverage.run data_file カバレッジレポートファイルの出力先の指定。
tool.coverage.run branch Trueならif文の分岐の通過率をレポートしてくれる。
tool.coverage.run parallel 並列実行の指定。
tool.coverage.run omit テスト対象外にしたいディレクトリやファイルの指定。
tool.coverage.report exclude_lines この文字列がある箇所をテスト対象外とする。(※1)
tool.coverage.html directory カバレッジレポート(HTML)の出力先の指定。
tool.coverage.xml output カバレッジレポート(XML)の出力先の指定。

※1 例えば、以下のようにコメントをコードにつけておくことで、テストが難しいと思われる箇所のテストを明示的にスキップできる。例は超適当。

   [val for val in hoge_list if val % 2 == 0][4:10]. # pragma: no cover

5) pytest-covを実行。

pairent_dirに移動し、以下のコマンドで実行する。

pytest --cov=src/ --cov-report=html --cov-report=xml --cov-report=term

--cov: テスト対象のコードが存在するパスを指定。
--cov-report=html/xml/term: それぞれの形式でのレポートの出力の指定。

これを実行すると以下のような出力を得る。

(pytest-cov-work-py3.9) bash-3.2$ pytest --cov=src/ --cov-report=html --cov-report=xml --cov-report=term
===================================================================================== test session starts =====================================================================================
platform darwin -- Python 3.9.2, pytest-7.4.0, pluggy-1.2.0
rootdir: /Users/shirai_y/work/tmp/pytest_cov_work
plugins: cov-4.1.0
collected 2 items

tests/test_work.py ..                                                                                                                                                                   [100%]

---------- coverage: platform darwin, python 3.9.2-final-0 -----------
Name          Stmts   Miss Branch BrPart  Cover
-----------------------------------------------
src/work.py       9      0      2      0   100%
-----------------------------------------------
TOTAL             9      0      2      0   100%
Coverage HTML written to dir report/htmlcov/
Coverage XML written to file report/coverage.xml


====================================================================================== 2 passed in 0.10s ======================================================================================

pairnt_dir/report配下にreport用のファイルが出来上がる。

6) 結果確認。

5)で既に結果は確認できているが、htmlで出力しているのでブラウザからでも確認できる。
以下を実行すると、localhost:8888にアクセスして結果を確認できる。

python -m http.server 8888 -d report/htmlcov

アクセス直後の画面

各ファイル名をクリックすると、詳細が分かる。今回の例ではエラーはないが、例えばテストできなかった分岐の箇所が赤でハイライトされる。

以上!
カバレッジ100%を目指そうとすると、異常データを人為的に作らないといけなかったりして結構辛いので、その辺も考慮してコードが書けると楽になるのかも〜