Python | テスト・設計・品質:カバレッジ

Python Python
スポンサーリンク

カバレッジって何?まずは一言でイメージをつかむ

テストの「カバレッジ(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)

もう一歩踏み込んだ指標が「分岐カバレッジ」です。

iffor などの「分岐」が、
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% でもバグは残りうるので、「地図」として使い、現実的な目標値とセットで運用するのが大事。

タイトルとURLをコピーしました