これは、なにをしたくて書いたもの?
そういえばPythonコードのカバレッジの取得方法を知らないですね、と思ったので少し見てみました。
Coverage.pyというものを使うようです。
Coverage.py
Coverage.pyのWebサイトはこちら。
Coverage.py — Coverage.py 7.13.5 documentation
GitHubリポジトリーはこちら。
GitHub - coveragepy/coveragepy: The code coverage tool for Python · GitHub
Coverage.pyはPythonコードのカバレッジを測定するツールです。
現在のバージョンは7.13.5で、Python 3.10以上をサポートしています。
Pythonのテストといえばpytestで、pytest向けにはpytest-covというプラグインがあるようです。
pytest-cov 7.1.0 documentation
GitHub - pytest-dev/pytest-cov: Coverage plugin for pytest. · GitHub
pytestと組み合わせる場合はpytest-covを使うのかと思いきや、Coverage.pyを見ると「多くの人はpytest-covを使うことを
選ぶが、通常は必要ない」とされています。
Many people choose to use the pytest-cov plugin, but for most purposes, it is unnecessary.
pytest-covの追加機能は以下のようです。
- .coverageファイルの自動消去や結合、デフォルトレポート機能
- パラメーター化を含む完全なテスト名をコンテキストに追加するなど、詳細なカバレッジコンテキストのサポート
- リモートインタープリターを含むpytest-xdist(分散テスト)の機能を使用してカバレッジを取得する
- Coverage.pyとpytestを組み合わせた時に発生するpytestの僅かな振る舞いの差を抑制し、pytestを一貫した動作にする
まずは使ってみるとしましょうか。
環境
今回の環境はこちら。
$ python3 --version Python 3.12.3 $ uv --version uv 0.11.9 (x86_64-unknown-linux-gnu)
Coverage.pyを使ってみる
それではCoverage.pyを使ってみましょう。
uvプロジェクトを作成。
$ uv init --vcs none coveragepy-getting-started $ cd coveragepy-getting-started $ rm main.py
pytestなどをインストール。
$ uv add --dev pytest mypy pyright ruff
Coverage.pyをインストール。
$ uv add --dev coverage
pyproject.toml
[project] name = "coveragepy-getting-started" version = "0.1.0" description = "Add your description here" readme = "README.md" requires-python = ">=3.12" dependencies = [] [dependency-groups] dev = [ "coverage>=7.13.5", "mypy>=1.20.2", "pyright>=1.1.409", "pytest>=9.0.3", "ruff>=0.15.12", ] [tool.pytest.ini_options] pythonpath = ["src"] [tool.mypy] strict = true disallow_any_unimported = true disallow_any_expr = true disallow_any_explicit = true warn_unreachable = true pretty = true [tool.pyright] pythonVersion = "3.12" typeCheckingMode = "strict" deprecateTypingAliases = true reportImplicitOverride = "error" reportImplicitStringConcatenation = "error" reportImportCycles = "error" reportMissingSuperCall = "error" reportPropertyTypeMismatch = "error" reportUnnecessaryTypeIgnoreComment = "error" reportUnreachable = "error"
インストールしたライブラリーなど。
$ uv tree Resolved 16 packages in 1ms coveragepy-getting-started v0.1.0 ├── coverage v7.13.5 (group: dev) ├── mypy v1.20.2 (group: dev) │ ├── librt v0.10.0 │ ├── mypy-extensions v1.1.0 │ ├── pathspec v1.1.1 │ └── typing-extensions v4.15.0 ├── pyright v1.1.409 (group: dev) │ ├── nodeenv v1.10.0 │ └── typing-extensions v4.15.0 ├── pytest v9.0.3 (group: dev) │ ├── iniconfig v2.3.0 │ ├── packaging v26.2 │ ├── pluggy v1.6.0 │ └── pygments v2.20.0 └── ruff v0.15.12 (group: dev)
ディレクトリーを作成。
$ mkdir src tests $ touch tests/__init__.py
テスト対象を用意。
src/calc.py
class Calc: def plus(self, x: int, y: int) -> int: return x + y def minus(self, x: int, y: int) -> int: return x - y def multiply(self, x: int, y: int) -> int: return x * y def divide(self, x: int, y: int) -> float: return x / y
src/message.py
def star_format(message: str) -> str: return f"★★★{message}★★★"
テストコードを用意。
tests/test_calc.py
from calc import Calc def test_plus() -> None: calc = Calc() assert calc.plus(1, 3) == 4 def test_minus() -> None: calc = Calc() assert calc.minus(5, 3) == 2 def test_divide() -> None: calc = Calc() assert calc.divide(10, 5) == 2
tests/test_message.py
from message import star_format def test_format() -> None: assert star_format("Hello World") == "★★★Hello World★★★"
テストが足りていませんが、まあ意図的です。
では、カバレッジを取得してみましょう。
バージョン確認。
$ uv run coverage --version Coverage.py, version 7.13.5 with C extension Full documentation is at https://coverage.readthedocs.io/en/7.13.5
pytestを使ってテストを実行。この時にcoverage runを経由することになります。
$ uv run coverage run -m pytest
実行すると、.coverageというファイルができます。
.coverageファイルがあると、レポート表示ができます。
$ uv run coverage report Name Stmts Miss Cover ------------------------------------------- src/calc.py 9 1 89% src/message.py 2 0 100% tests/__init__.py 0 0 100% tests/test_calc.py 10 0 100% tests/test_message.py 3 0 100% ------------------------------------------- TOTAL 24 1 96%
カバーしていない箇所を表示するには、--show-missingまたは-mオプションを指定します。
$ uv run coverage report -m Name Stmts Miss Cover Missing ----------------------------------------------------- src/calc.py 9 1 89% 9 src/message.py 2 0 100% tests/__init__.py 0 0 100% tests/test_calc.py 10 0 100% tests/test_message.py 3 0 100% ----------------------------------------------------- TOTAL 24 1 96%
HTMLレポートも作成できます。
$ uv run coverage html Wrote HTML report to htmlcov/index.html
htmlcovというディレクトリーが作成され、この中にHTMLレポートが配置されます。
こんなレポートになりました。


設定もしてみましょう。
Configuration reference — Coverage.py 7.13.5 documentation
pyproject.tomlにこんな感じに追加。
[tool.coverage.run] branch = true command_line = "-m pytest" [tool.coverage.report] show_missing = true omit = [ "tests/*" ]
ブランチカバレッジを取得する、coverage runのみを指定した時にpytestを実行するようにする、レポート表示の際にカバー
していない箇所を表示する、testsディレクトリーはレポートから除外する、です。
結果を見た方が早いですね。
$ uv run coverage run =========================================================================== test session starts ============================================================================ platform linux -- Python 3.12.3, pytest-9.0.3, pluggy-1.6.0 rootdir: /path/to/coveragepy-getting-started configfile: pyproject.toml collected 4 items tests/test_calc.py ... [ 75%] tests/test_message.py . [100%] ============================================================================ 4 passed in 0.02s ============================================================================= $ uv run coverage report Name Stmts Miss Branch BrPart Cover Missing ------------------------------------------------------------ src/calc.py 9 1 0 0 89% 9 src/message.py 2 0 0 0 100% ------------------------------------------------------------ TOTAL 11 1 0 0 91%
こんなところでしょうか。
pytest-covを使ってみる
せっかくなので、pytest-covも使ってみましょう。
お題はCoverage.pyをそのまま使う時と同じにして、変化を見るようにしてみましょう。
uvプロジェクトの作成。
$ uv init --vcs none pytest-cov-getting-started $ cd pytest-cov-getting-started $ rm main.py
pytestなどの追加。
$ uv add --dev pytest mypy pyright ruff
pytest-covのインストール。
$ uv add --dev pytest-cov
pyproject.toml
[project] name = "pytest-cov-getting-started" version = "0.1.0" description = "Add your description here" readme = "README.md" requires-python = ">=3.12" dependencies = [] [dependency-groups] dev = [ "mypy>=1.20.2", "pyright>=1.1.409", "pytest>=9.0.3", "pytest-cov>=7.1.0", "ruff>=0.15.12", ] [tool.pytest.ini_options] pythonpath = ["src"] [tool.mypy] strict = true disallow_any_unimported = true disallow_any_expr = true disallow_any_explicit = true warn_unreachable = true pretty = true [tool.pyright] pythonVersion = "3.12" typeCheckingMode = "strict" deprecateTypingAliases = true reportImplicitOverride = "error" reportImplicitStringConcatenation = "error" reportImportCycles = "error" reportMissingSuperCall = "error" reportPropertyTypeMismatch = "error" reportUnnecessaryTypeIgnoreComment = "error" reportUnreachable = "error"
インストールしたライブラリーの一覧。
$ uv tree Resolved 17 packages in 1ms pytest-cov-getting-started v0.1.0 ├── mypy v1.20.2 (group: dev) │ ├── librt v0.10.0 │ ├── mypy-extensions v1.1.0 │ ├── pathspec v1.1.1 │ └── typing-extensions v4.15.0 ├── pyright v1.1.409 (group: dev) │ ├── nodeenv v1.10.0 │ └── typing-extensions v4.15.0 ├── pytest v9.0.3 (group: dev) │ ├── iniconfig v2.3.0 │ ├── packaging v26.2 │ ├── pluggy v1.6.0 │ └── pygments v2.20.0 ├── pytest-cov v7.1.0 (group: dev) │ ├── coverage v7.13.5 │ ├── pluggy v1.6.0 │ └── pytest v9.0.3 (*) └── ruff v0.15.12 (group: dev)
pytest-covにCoverage.pyもpytestも含まれています。
テスト対象およびテストコードはまったく同じなので省略。
pytestのヘルプを見ると
$ uv run pytest --help
カバレッジに関する内容が追加されています。
coverage reporting with distributed testing support: --cov=[SOURCE] Path or package name to measure during execution (multi-allowed). Use --cov= to not do any source filtering and record everything. --cov-reset Reset cov sources accumulated in options so far. --cov-report=TYPE Type of report to generate: term, term-missing, annotate, html, xml, json, markdown, markdown-append, lcov (multi-allowed). term, term-missing may be followed by ":skip-covered". annotate, html, xml, json, markdown, markdown-append and lcov may be followed by ":DEST" where DEST specifies the output location. Use --cov-report= to not generate any output. --cov-config=PATH Config file for coverage. Default: .coveragerc --no-cov-on-fail Do not report coverage if test run fails. Default: False --no-cov Disable coverage report completely (useful for debuggers). Default: False --cov-fail-under=MIN Fail if the total coverage is less than MIN. --cov-append Do not delete coverage but append to current. Default: False --cov-branch Enable branch coverage. --cov-precision=COV_PRECISION Override the reporting precision. --cov-context=CONTEXT Dynamic contexts to use. "test" for now.
pytestをそのまま実行するだけでは、特になにも変わりません。
$ uv run pytest =========================================================================== test session starts ============================================================================ platform linux -- Python 3.12.3, pytest-9.0.3, pluggy-1.6.0 rootdir: /path/to/pytest-cov-getting-started configfile: pyproject.toml plugins: cov-7.1.0 collected 4 items tests/test_calc.py ... [ 75%] tests/test_message.py . [100%] ============================================================================ 4 passed in 0.01s =============================================================================
--covオプションをつけると、カバレッジを取得するようになります。
$ uv run pytest --cov =========================================================================== test session starts ============================================================================ platform linux -- Python 3.12.3, pytest-9.0.3, pluggy-1.6.0 rootdir: /path/to/pytest-cov-getting-started configfile: pyproject.toml plugins: cov-7.1.0 collected 4 items tests/test_calc.py ... [ 75%] tests/test_message.py . [100%] ============================================================================== tests coverage ============================================================================== _____________________________________________________________ coverage: platform linux, python 3.12.3-final-0 ______________________________________________________________ Name Stmts Miss Cover ------------------------------------------- src/calc.py 9 1 89% src/message.py 2 0 100% tests/__init__.py 0 0 100% tests/test_calc.py 10 0 100% tests/test_message.py 3 0 100% ------------------------------------------- TOTAL 24 1 96% ============================================================================ 4 passed in 0.04s =============================================================================
--cov=[パス]で指定すると、その対象に絞ってカバレッジを表示します。
※--cov [パス]でも可
$ uv run pytest --cov=src =========================================================================== test session starts ============================================================================ platform linux -- Python 3.12.3, pytest-9.0.3, pluggy-1.6.0 rootdir: /path/to/pytest-cov-getting-started configfile: pyproject.toml plugins: cov-7.1.0 collected 4 items tests/test_calc.py ... [ 75%] tests/test_message.py . [100%] ============================================================================== tests coverage ============================================================================== _____________________________________________________________ coverage: platform linux, python 3.12.3-final-0 ______________________________________________________________ Name Stmts Miss Cover ------------------------------------ src/calc.py 9 1 89% src/message.py 2 0 100% ------------------------------------ TOTAL 11 1 91% ============================================================================ 4 passed in 0.03s =============================================================================
こう見ると、pytest-covはpytestの実行時に合わせてカバレッジの処理もやってしまうというプラグインになりますね。
--cov-report=[タイプ]でレポートの種類を指定できます。--cov-report=term-missingではカバーしていない箇所を表示します。
$ uv run pytest --cov=src --cov-report=term-missing =========================================================================== test session starts ============================================================================ platform linux -- Python 3.12.3, pytest-9.0.3, pluggy-1.6.0 rootdir: /path/to/pytest-cov-getting-started configfile: pyproject.toml plugins: cov-7.1.0 collected 4 items tests/test_calc.py ... [ 75%] tests/test_message.py . [100%] ============================================================================== tests coverage ============================================================================== _____________________________________________________________ coverage: platform linux, python 3.12.3-final-0 ______________________________________________________________ Name Stmts Miss Cover Missing ---------------------------------------------- src/calc.py 9 1 89% 9 src/message.py 2 0 100% ---------------------------------------------- TOTAL 11 1 91% ============================================================================ 4 passed in 0.03s =============================================================================
こんな感じで繰り返し指定も可能です。この例ではHTMLレポートも合わせて生成しています。
$ uv run pytest --cov=src --cov-report=term-missing --cov-report=html =========================================================================== test session starts ============================================================================ platform linux -- Python 3.12.3, pytest-9.0.3, pluggy-1.6.0 rootdir: /path/to/pytest-cov-getting-started configfile: pyproject.toml plugins: cov-7.1.0 collected 4 items tests/test_calc.py ... [ 75%] tests/test_message.py . [100%] ============================================================================== tests coverage ============================================================================== _____________________________________________________________ coverage: platform linux, python 3.12.3-final-0 ______________________________________________________________ Name Stmts Miss Cover Missing ---------------------------------------------- src/calc.py 9 1 89% 9 src/message.py 2 0 100% ---------------------------------------------- TOTAL 11 1 91% Coverage HTML written to dir htmlcov ============================================================================ 4 passed in 0.04s =============================================================================
その他の設定も含めて、ドキュメントはこちら。
Configuration - pytest-cov 7.1.0 documentation
pyproject.tomlではtool.pytest.ini_optionsセクションのaddoptsで指定できます。
[tool.pytest.ini_options] pythonpath = ["src"] addopts = "--cov=src --cov-branch --cov-report=term-missing --cov-report=html"
項目によってはtool.coverage.runやtool.coverage.reportなどで指定もできますが、どうも中途半端になるので今回はすべて
addoptsにまとめました。
これでuv run pytestを実行するだけでカバレッジの取得とレポート出力まで行われます。
$ uv run pytest =========================================================================== test session starts ============================================================================ platform linux -- Python 3.12.3, pytest-9.0.3, pluggy-1.6.0 rootdir: /path/to/pytest-cov-getting-started configfile: pyproject.toml plugins: cov-7.1.0 collected 4 items tests/test_calc.py ... [ 75%] tests/test_message.py . [100%] ============================================================================== tests coverage ============================================================================== _____________________________________________________________ coverage: platform linux, python 3.12.3-final-0 ______________________________________________________________ Name Stmts Miss Branch BrPart Cover Missing ------------------------------------------------------------ src/calc.py 9 1 0 0 89% 9 src/message.py 2 0 0 0 100% ------------------------------------------------------------ TOTAL 11 1 0 0 91% Coverage HTML written to dir htmlcov ============================================================================ 4 passed in 0.04s =============================================================================
おわりに
Coverage.pyを使ってPythonコードのカバレッジを取得してみました。
pytest-covについては、今回のような使い方だと確かにCoverage.pyを直接使うでも構わないかも…とは思いました。
とりあえず、基本的な使い方はわかりました。