Pythonテストフレームワーク「pytest」でテストコードを書く【完全ガイド】

はじめまして、まつのです!

機械学習や生成AIの受託開発・AI学習サービス「aipass」の運営をしている株式会社dotConfという会社で、代表をしております!

この記事では、Pythonのテストフレームワーク「pytest」について実装方法をわかりやすく解説します。

pytestとは?

pytestとは、Pythonでユニットテストを書くためのフレームワークです。他にも様々なユニットテスト用のフレームワークはありますが、文法がシンプルで分かりやすいためpytestを使うことをお勧めします。

環境構築

まずは以下を実行して pytestpytest-cov をインストールします。

pip install pytest pytest-cov

テストコードを書いてみる

まずはユニットテストの対象となるソースコードを準備します。ここでは四則演算を行う関数を対象とします。

calc.py
def add(a, b):
    return a + b

def sub(a, b):
    return a - b

def mul(a, b):
    return a * b

def div(a, b):
    if b == 0:
        raise ValueError("division by zero")
    return a / b

続いてこれらの関数に対してテストコードをpytestで書いてみます。

test_calc.py
from calc import add, sub, mul, div
import pytest

def test_add():
    assert add(2, 3) == 5

def test_sub():
    assert sub(5, 3) == 2

def test_mul():
    assert mul(2, 4) == 8

def test_div():
    assert div(10, 2) == 5

def test_div_zero():
    with pytest.raises(ValueError):
        div(10, 0)

テストを実行します

pytest -v
================== test session starts ==================
collected 5 items

test_calc.py::test_add PASSED                    [ 20%]
test_calc.py::test_sub PASSED                    [ 40%]
test_calc.py::test_mul PASSED                    [ 60%]
test_calc.py::test_div PASSED                    [ 80%]
test_calc.py::test_div_zero PASSED               [100%]

=================== 5 passed in 0.03s ====================

結果を見てみると全ての関数で「PASSED」となっており、全て期待通りに実装できていることがわかります。

ここであえてバグのある関数pow()とそれに対するテストコードtest_pow()を追加してみます。pow()はべき乗を計算する関数のつもりが、掛け算になっているというバグにしておきます。

calc.py
def add(a, b):
    return a + b

def sub(a, b):
    return a - b

def mul(a, b):
    return a * b

def div(a, b):
    if b == 0:
        raise ValueError("division by zero")
    return a / b

def pow(a,b):
    # 正しくは、a**b
    return a * b
test_calc.py
from calc import add, sub, mul, div, pow
import pytest

def test_add():
    assert add(2, 3) == 5

def test_sub():
    assert sub(5, 3) == 2

def test_mul():
    assert mul(2, 4) == 8

def test_div():
    assert div(10, 2) == 5

def test_div_zero():
    with pytest.raises(ValueError):
        div(10, 0)

def test_pow():
    # 追加したテストコード
    assert pow(3, 2) == 9

テストを実行してみます。

pytest -v
================== test session starts ==============
collected 6 items

test_calc.py::test_add PASSED                    [ 16%]
test_calc.py::test_sub PASSED                    [ 33%]
test_calc.py::test_mul PASSED                    [ 50%]
test_calc.py::test_div PASSED                    [ 66%]
test_calc.py::test_div_zero PASSED               [ 83%]
test_calc.py::test_pow FAILED                    [100%]


====================== FAILURES ======================
______________________ test_pow ______________________

    def test_pow():
        # 追加したテストコード
>       assert pow(3, 2) == 9
E       assert 6 == 9
E        +  where 6 = pow(3, 2)

test_calc.py:22: AssertionError
=============== short test summary info  ===============
FAILED test_calc.py::test_pow - assert 6 == 9
=============== 1 failed, 5 passed in 0.05s ============

test_powは「FAILED」となっており、バグをテストで検知することができました。

パラメータテスト

同じ関数を複数パターンで検証したい場合、@pytest.mark.parametrizeを使います。
テスト関数を1つ書くだけで、複数の入力・期待値を自動でテストできます。ここでは先ほど作成した足し算を行うadd関数に対するパラメータテストを書いてみます。

import pytest
from calc import add

@pytest.mark.parametrize(
    "a,b,expected",
    [
        (1, 2, 3),
        (0, 0, 0),
        (-1, 1, 0),
        (100, 200, 300),
    ],
    ids=["1+2", "0+0", "-1+1", "100+200"]
)
def test_add_param(a, b, expected):
    assert add(a, b) == expected

パラメータテストを実行します。

pytest -v -k "add_param"
================== test session starts ==================
collected 6 items

test_calc.py::test_add_param[1+2] PASSED          [ 25%]
test_calc.py::test_add_param[0+0] PASSED          [ 50%]
test_calc.py::test_add_param[-1+1] PASSED         [ 75%]
test_calc.py::test_add_param[100+200] PASSED      [100%]

=================== 4 passed in 0.02s ====================

テストカバレッジ

テストされていない関数があるとバグの元となるため、実装したすべての関数はテストを実施する必要があります。pytest-covを利用するとテストコードがない関数やその割合を炙り出すことができます。

pytest --cov=calc -q
---------- coverage: platform linux, python 3.10 ----------
Name      Stmts   Miss  Cover
-----------------------------
calc.py      12      0   100%
-----------------------------
TOTAL        12      0   100%
項目 意味
Name 測定対象のファイル名
Stmts ステートメント(statement)の数=実行可能なコード行数
Miss テスト中に実行されなかった行数
Cover カバレッジ(= 100 × (Stmts - Miss) / Stmts )=網羅率

test_calc.pyからtest_pow()を削除してみましょう。

test_calc.py
from calc import add, sub, mul, div
import pytest

def test_add():
    assert add(2, 3) == 5

def test_sub():
    assert sub(5, 3) == 2

def test_mul():
    assert mul(2, 4) == 8

def test_div():
    assert div(10, 2) == 5

def test_div_zero():
    with pytest.raises(ValueError):
        div(10, 0)

""" 削除
def test_pow():
    # 追加したテストコード
    assert pow(3, 2) == 9
"""

再度カバレッジテストを実行すると、以下のようにテストコードが未実装の関数があることがわかります。

---------- coverage: platform linux, python 3.10 ----------
Name      Stmts   Miss  Cover
-----------------------------
calc.py      12      1    92%
-----------------------------
TOTAL        12      1    92%

まとめ

pytest は「シンプルだけど強力」なテストフレームワークです。assertで直感的に書け、parametrizeで効率的に検証し、pytest-covで品質を可視化できます。

Pythonを学習したい方へ

Pythonを体系的に学び、プロの指導のもとで実践的なAIスキルを習得したい方、
キャリアの幅を広げたい方や複業を目指す方は、ぜひこちらからお問い合わせください。
https://b2c.aipass.tech
https://lin.ee/pbd6SZJ

Zenn以外の情報発信媒体はこちら👇
dotConf, Inc

Discussion