pytestのカバレッジ初級編 — 実務で迷わない最短ルート
カバレッジとは「テストがどのコードをどれだけ通ったかを数値化する指標」である。
なぜカバレッジを測るのか(目的と効果)
- 回 regressions の早期発見:未テストの行・分岐は不具合の温床。
- レビューの焦点化:未実行の行番号が“穴”を可視化し、レビューや追加テストの優先度がつく。
- 継続的改善:CIで基準値を設定し、下回ったら失敗させることで品質を維持。
カバレッジの基本概念(行・分岐・条件)
カバレッジは大きく分けて、コードを実行したか(行)と、条件分岐の真偽両方を通したか(分岐)で捉えると理解が早い。
カバレッジ種類の比較
種類 | 測る対象 | 何が分かる | 強み | 注意点 | pytestオプション |
---|---|---|---|---|---|
行(Line) | 実行された行の割合 | どの行が実行されたか | セットアップが容易、理解しやすい | 条件の片側だけでも“通った”扱い | --cov=PKG |
分岐(Branch) | 条件のTrue/False | 各分岐の網羅状況 | 例外経路やガード節の穴を炙れる | やや遅くなる、目標設定が難しい | --cov-branch |
条件(Condition) | 複合条件の項目単位 |
a and b の a/b それぞれ |
より厳密 | 初級は過剰になりやすい | (coverage.pyの詳細設定) |
初級編の実務では 「行 + 分岐」 を抑えれば十分。
最小コマンドと読み方(まずはこれ)
pytest -q -ra \
--cov=my_app \
--cov-branch \
--cov-report=term-missing
-
-q
: 出力を簡潔に -
-ra
: skip/xfail などの理由をまとめ表示 -
--cov=my_app
: 対象パッケージを指定 -
--cov-branch
: 分岐カバレッジを有効化 -
--cov-report=term-missing
: ターミナルに未実行の行番号を出す
出力の読み方(例)
Name Stmts Miss Branch BrPart Cover Missing
------------------------------------------------------------
my_app/core.py 80 5 22 3 89% 45-47, 90, 113
-
Miss
: 未実行の行数 -
Branch
: 分岐数 /BrPart
: 片側だけ通った分岐の数 -
Missing
: 未実行行の具体的な行番号
レポート形式の比較(用途別)
レポート | 使いどころ | 特徴 | 具体例 |
---|---|---|---|
term |
ざっくり進捗確認 | 標準出力に要約 | CIログに軽量表示 |
term-missing |
穴潰しの作業 | 未実行行の行番号が出る | ローカルでの追加テスト設計に最適 |
html |
視覚的に穴を追跡 | 行ごとに色分けハイライト |
--cov-report=html で htmlcov/ を開く |
xml |
ツール連携 | CIのゲートやダッシュボード | --cov-report=xml |
穴を埋める作業は
term-missing
/ チーム共有はhtml
が強い。
目標値と失敗基準(CIでのゲーティング)
pytest --cov=my_app --cov-branch --cov-fail-under=85
-
初期目標の目安:
- 行カバレッジ: 80–90%(現実的ライン)
- 分岐カバレッジ: 60–80%(まずは主要分岐を通す)
-
クリティカルなモジュール(計算・料金・権限)は 局所的に 100% を狙う。
「全体 85%、重要部は 100%」の二層目標が実務的。
ありがちな落とし穴と回避策
- 100% = バグゼロではない:仕様漏れ・相互作用は別問題。プロパティテストや境界値と併用。
-
ガード節が未通過:
if not x: return
の否定側が未テストになりがち → 異常系テストを追加。 -
例外経路の未網羅:
try/except
の except ブロックが空きやすい → 擬似的に例外を起こす。 - I/O・時刻依存:モック/フリーズタイムで安定化。
-
並列実行(xdist)時の集計:
--cov
はプロセス間結合をサポート。必要に応じて--cov-context=test
でテスト別文脈も可。 -
型ヒント分岐・デバッグ行:
if TYPE_CHECKING:
/ ログのみ行は# pragma: no cover
で除外可。
実践レシピ(初導入~継続運用)
-
最小導入:上記の最小コマンドで実行し、
term-missing
の穴を確認。 - 高リスク優先:ドメイン核心部(料金、在庫、権限)から穴を埋める。
- 異常系の追加:分岐カバレッジを底上げ(False側/例外経路)。
-
CI化:
--cov-fail-under
を80→85→90と段階引き上げ。 - 差分カバレッジ:PRでは変更行/近傍の分岐を必ず通す(レビューの約束事に)。
.coveragerc の最小例(コピペでOK)
[run]
branch = True
source = my_app
omit =
*/migrations/*
*/__main__.py
*/_experiments/*
[report]
show_missing = True
exclude_lines =
pragma: no cover
if __name__ == .__main__.:
if TYPE_CHECKING:
-
branch=True
で分岐カバレッジをデフォルト有効化。 -
omit
で対象外(マイグレーションなど)を除外。 -
exclude_lines
で実務上ノイズになる行をスキップ。
ミニサンプル(分岐の穴を可視化)
対象コード calc.py
def tier(price: int) -> str:
if price < 0:
raise ValueError("negative")
if price == 0:
return "free"
return "paid" if price < 100 else "enterprise"
テスト test_calc.py
(初期)
from calc import tier
def test_free():
assert tier(0) == "free"
def test_paid():
assert tier(10) == "paid"
実行
pytest -q -ra --cov=. --cov-branch --cov-report=term-missing
想定出力(抜粋)
calc.py 6 1 2 1 83% 2
- 行2(
price < 0
)の例外経路が未実行、分岐の片側も未通過。
穴を埋めるテスト追加
def test_negative():
import pytest
with pytest.raises(ValueError):
tier(-1)
def test_enterprise():
assert tier(100) == "enterprise"
→ 再実行で Miss=0
, BrPart=0
へ。分岐が“両側”通ったことを確認。
レポートの粒度を使い分ける
- ローカルで穴埋め:
--cov-report=term-missing
- 俯瞰して共有:
--cov-report=html
をCIの成果物へ - ツール連携:
--cov-report=xml
をCodecov/Sonar等に
図:計測の流れ(シンプルモデル)
┌──────────┐ ┌──────────────┐ ┌──────────────┐
│ Tests │──▶│ coverage.py │──▶│ Reports │
│ (pytest) │ │ (測定エンジン) │ │ (term/html/xml) │
└──────────┘ └──────────────┘ └──────────────┘
▲ │
│ ▼
Code ─────────▶ Execution Tracing
実務Tips(時短と安定化)
-
遅いテストは階層で分ける:
markers
で unit/slow/integration を切り分け、単体はカバレッジ必須、統合は参考値。 -
パラメタライズで分岐網羅:
@pytest.mark.parametrize
で True/False/境界値を一気に通す。 - フレーク対策:ランダム性はseed固定、時間はフリーズ、ネットワークはモック。
- 生成コード・型用分岐は除外:ノイズを削り、意味のある分子/分母を作る。
よくある質問(FAQ)
- Q: 100%必須? → 不要。コストとリスクのバランス。中核ロジックは100%、周辺は80%目標が現実的。
- Q: 低カバレッジのままでもいい? → 新規コードは基準厳しめ、既存は差分で上げる方針がおすすめ。
-
Q: xdist併用で壊れる? → 現行の
pytest-cov
はプロセス間集約をサポート。CIでは--cov-context=test
が有用な場合あり。
最小チェックリスト(コミット前)
-
pytest -q -ra --cov=PKG --cov-branch --cov-report=term-missing
-
--cov-fail-under=XX
を満たすか - 未実行行に対して 異常系/境界値 を追加したか
-
クリティカルモジュールは分岐の片側残しが無いか(
BrPart=0
) -
除外設定の妥当性(
pragma: no cover
の乱用禁止)
まとめ(要点)
- 「行+分岐」を観るのが初級の最短ルート。
-
term-missing
で穴を特定→ 異常系/境界値で埋める。 - CIに
--cov-fail-under
を入れて継続的に上げる。
今日から使うなら:このページのコマンドと
.coveragerc
をそのまま投入し、Missing
とBrPart
をゼロにすることから始めよう。