カバレッジって何?まずは一言でイメージをつかむ
テストの「カバレッジ(coverage)」は、
「テストを実行したときに、あなたのコードのどこまでが実際に実行されたか」を数値で教えてくれる指標です。
ざっくり言うと、
どの行がテストで通ったか
どの if 分岐が通っていないか
どのファイルがまったくテストされていないか
を可視化してくれる“懐中電灯”みたいなものです。
テストを書いたつもりでも、
実はバグが潜んでいる「通っていない行」が残っていることはよくあります。
カバレッジは、それをあぶり出すための道具です。
いちばんシンプルな例で「カバレッジの意味」を見る
テスト対象のコードを用意する
まずは、わざと分岐を含んだ関数を作ります。
# discount.py
def calc_discount(price: int, is_member: bool) -> int:
if price < 0:
raise ValueError("price must be >= 0")
if is_member:
return int(price * 0.8) # 会員は 20% 引き
else:
return price
Pythonこの関数には、3 つのパターンがあります。
価格がマイナス(エラー)
会員(20% 引き)
非会員(割引なし)
これをテストしてみます。
テストを一部だけ書いてみる
あえて「足りないテスト」を書きます。
# test_discount.py
from discount import calc_discount
def test_calc_discount_member():
assert calc_discount(1000, True) == 800
def test_calc_discount_non_member():
assert calc_discount(1000, False) == 1000
Pythonここでは、正常系(会員・非会員)はテストしていますが、
「price < 0 のときにエラーになる」パターンはテストしていません。
テストを実行すると、もちろん両方とも成功します。
でも、「本当に全部のパターンをテストできているか?」は、
この時点では分かりません。
ここでカバレッジの出番です。
coverage.py と pytest を組み合わせて使う
カバレッジを測る実行イメージ
実際には coverage.py というツールを使うことが多いですが、
pytest なら pytest-cov プラグインを入れて、
pytest --cov=.
のように実行すると、
「どのファイルが何%カバーされているか」が表示されます。
例えば、さっきの状態だと、discount.py はこんな感じのイメージになります。
行ベースで見ると、
if price < 0: の行は一度も実行されていないraise ValueError(...) の行も一度も実行されていない
会員・非会員の分岐は両方通っている
つまり、「エラー系のパス」がテストされていないことが分かります。
カバレッジは、「どこがテストされていないか」を教えてくれるので、
「どこにテストを追加すべきか」のヒントになります。
カバレッジの種類(行・分岐)をざっくり理解する
行カバレッジ(Line Coverage)
一番よく使われるのが「行カバレッジ」です。
「このファイルの何行中、何行がテスト中に実行されたか」をパーセンテージで出します。
例えば、
100 行あるファイルのうち、80 行がテストで実行された
→ 行カバレッジ 80%
という感じです。
さっきの discount.py なら、
if price < 0: と raise ValueError(...) の 2 行が未実行
それ以外は実行済み
という状態になります。
分岐カバレッジ(Branch Coverage)
もう一歩踏み込んだ指標が「分岐カバレッジ」です。
if や for などの「分岐」が、
True 側・False 側の両方とも通ったかどうかを見ます。
例えば、
if is_member:
...
else:
...
Pythonこの if について、
is_member=True のパスを通ったか
is_member=False のパスを通ったか
をそれぞれチェックします。
行カバレッジだけだと、
「if の行自体は実行されたけど、片方の分岐しか通っていない」
というケースを見逃すことがあります。
分岐カバレッジは、
「条件の両側をちゃんとテストできているか?」を確認するのに役立ちます。
カバレッジを上げるために「足りないテスト」を追加する
さっきの例にエラー系のテストを足す
price < 0 のパスをテストしてみます。
# test_discount.py
import pytest
from discount import calc_discount
def test_calc_discount_member():
assert calc_discount(1000, True) == 800
def test_calc_discount_non_member():
assert calc_discount(1000, False) == 1000
def test_calc_discount_negative_price():
with pytest.raises(ValueError):
calc_discount(-1, True)
Pythonこれで、
if price < 0: の条件が True になるパスraise ValueError(...) の行
もテストで通るようになります。
カバレッジを再度測ると、discount.py の行カバレッジは 100% に近づきます。
ここで大事なのは、
「カバレッジを上げるためにテストを書く」のではなく、
「カバレッジを見て、テストし忘れているパターンに気づく」
という使い方です。
「カバレッジ 100%」の落とし穴と、正しい向き合い方
100% でもバグは残る、という現実
カバレッジは便利ですが、「100% ならバグがない」という意味ではありません。
例えば、こういうコードを考えます。
def add(a, b):
return a - b # 本当は a + b にしたかった
Pythonこれに対して、
def test_add():
assert add(1, 2) == 3
Pythonと書いてテストを実行すると、当然失敗します。
ここで実装をこう直します。
def add(a, b):
return a + b
Pythonテストを再実行すると成功し、
行カバレッジも 100% になります。
でも、もし本当は「大きな数」「負の数」「0」など、
もっといろいろなパターンを考えるべき仕様だったとしたら、
テストはまだ足りていません。
カバレッジは「どの行が実行されたか」しか見ていません。
「十分なパターンをテストしたか」までは教えてくれない、というのが重要なポイントです。
カバレッジは「地図」であって「ゴール」ではない
カバレッジを正しく使うコツは、
「テストが当たっていない場所を知るための地図」
として扱うことです。
この if の False 側、テストしてないな
この例外パス、一度も通してないな
このファイル、そもそもテストが 1 本もないな
といった「抜け」を見つけるための道具です。
逆に、
カバレッジ 100% を目標にして、意味のないテストを量産する
実質何も検証していないテストで行数だけ稼ぐ
みたいな使い方をすると、本末転倒になります。
初心者がカバレッジとどう付き合うといいか
まずは「測ってみる」ことから始める
最初のステップはシンプルで、
今書いているテストを、そのままカバレッジ付きで実行してみる
ことです。
そうすると、
意外とテストが当たっていないファイルがある
エラー系のパスがほとんどテストされていない
一部のモジュールだけカバレッジが極端に低い
といった「現状」が見えてきます。
そこから、
この関数の異常系テストを足そう
この if のもう片方のパスもテストしよう
この重要なモジュールには、そもそも単体テストを書こう
という「次の一手」が見えてきます。
目標値は「100%」じゃなくて「現実的なライン」
現場では、例えば、
重要なロジックは 90% 以上
全体として 70〜80% くらい
など、現実的なラインを決めることが多いです。
個人開発や学習中なら、
まずは「テストを書いたファイルは 80% 以上を目指す」
そのうえで、「特に重要なところは 90% 以上を意識する」
くらいの感覚で十分です。
大事なのは、「カバレッジを上げること」ではなく、
「テストの抜けを減らすこと」です。
まとめ(カバレッジは“テストの当たり具合”を見せるライト)
カバレッジを初心者目線で整理すると、こうなります。
カバレッジは、「テスト実行中にコードのどこが実際に実行されたか」を数値で見せてくれる指標。
行カバレッジは「何行中何行が通ったか」、分岐カバレッジは「if などの条件の両側を通したか」を教えてくれる。
カバレッジを見て、「テストしていないパス(特にエラー系・境界値)」に気づき、テストを追加するのが正しい使い方。
カバレッジ 100% でもバグは残りうるので、「地図」として使い、現実的な目標値とセットで運用するのが大事。

