前提
- pytestでmockを使ってUTする際にパターンを忘れてggrことが多いので自分用に雑多に残しておきます。
mockの予備知識
- 外部APIからのレスポンスや、まだ未定義の関数や変数やクラスに対してmock化することで、疑似的に関数から値を受け取る処理を作ることができる。
- mock化する関数自体は関数名と、passのみやreturnのみでもOK
→ ただし、mock化して値を上書きする為のガワは必要なのでモジュール作成や関数は別途作成が必須
実施準備
①Pytestは標準モジュールではないので pip にてインストールが必要
$ pip install pytest pytest-mock
②各プロジェクト・モジュールの準備
└── sandbox/
├── calc.py
└── test/
└── test_calc.py
③テスト実施する階層に移動
$ cd sandbox
④pytestは python -m でpytestを指定してUT実行
※python3 -m でも可
# 標準出力なし
$ python -m pytest test/test_calc.py
# 標準出力あり
$ python -m pytest -s test/test_calc.py
関数ベースの変数の値をmock化
calc.py
# この変数をmock化する True -> False に変更
result_flg = True
def calc():
# このまま実行すると、ここの分岐に入ることはできない
if not result_flg:
return {"result_flg": False}
test_calc.py
import pytest
from unittest.mock import patch
from calc import calc
# 第一引数=変数までのパスと変数名、第二引数=変更したい変数の値を設定
@patch("calc.result_flg", False)
def test_calc(mocker):
# 関数を実行
result = calc()
# assertして実際に変数がFalseになっていることを確認
assert result == {"result_flg": False}
関数ベースの関数をmock化(正常UTパターン)
calc.py
# この関数をmock化する
def calc_exec(num):
pass
def calc(param_dict):
total = 0
for i in range(1,4):
# 関数をmock化することで、変数が受け取る値を変更できる
result = calc_exec(param_dict["num"])
total += result
return total
test_calc.py
from calc import calc
def test_calc(mocker):
# 第一引数=関数までのパス、第二引数=return_valueかside_effectを設定
# [return_value= は固定値での返却]、[side_effect= は柔軟な値返却や例外の返却]
mocker.patch(
"calc.calc_exec",
# 同じ関数名を複数回呼び出した時の1~3回目(forループも含む)の値を第一~第三引数で柔軟に設定
# → 1回目:4, 2回目:6, 3回目:8
side_effect=[2*2, 3*2, 4*2,],
)
# mock化した後にテスト対象の関数を実行する
result = calc(
{
"num": 2
}
)
# 値をassertする
assert result == 18
関数ベースの関数をmock化(例外UTパターン)
calc.py
# 自作の例外クラスを定義
class CalcZeroError(Exception):
pass
# この関数をmock化する
def calc_exec(num):
pass
def calc(param_dict):
# 関数をmock化することで、変数が受け取る値を変更できる
result = calc_exec(param_dict["num"])
if result == 0:
raise CalcZeroError("failed to calc")
test_calc.py
import pytest
from calc import calc
def test_calc(mocker):
# 第一引数=関数までのパス、第二引数=return_valueかside_effectを設定
# [return_value= は固定値での返却]、[side_effect= は柔軟な値返却や例外の返却
mocker.patch(
"calc.calc_exec",
return_value=0,
)
# mock化したあとに、下記処理内でException例外が発生するかを判定
with pytest.raises(Exception) as e:
result = calc(
{
"num": 2
}
)
# 例外発生した際のメッセージを比較
assert str(e.value) == "failed to calc"
parametrizeを使って関数ベースの関数のUTの値を柔軟に変更
- parametrizeを使用する流れ
- paramitrizeで使用するためのリスト内タプルを作成(params)
- タプル内の引数を必要な数作成し、必要な値を記述(関数呼び出しを記載するのも可)-> 今回は4つ
- 使用するテスト関数上部にデコレータでparametrizeと任意の変数名を付与
- testの引数に3.の変数名を受け取る
- test内で柔軟に値を変えたい箇所に引数名を記載する
- 下記テストコード内の★1~3の順で1回ずつテストが走る(計3回)
- 今回のケースでは3回分可変で4つの値が変数に代入されてテスト判定
calc.py
def calc(a, b, c):
return a + b + c
test_calc.py
import pytest
from calc import calc
# parametrizeでテストする値を3パターン リスト内タプルで作成
params = [
# 1ケース目 param_a, param_b, param_c, result の順で設定
(1, 2, 3, 6), # ★1
# 2ケース目 param_a, param_b, param_c, result の順で設定
(3, 4, 7, 14), # ★2
# 3ケース目 param_a, param_b, param_c, result の順で設定
(4, 5, 6, 15), # ★3
]
@pytest.mark.parametrize(
"param_a, param_b, param_c, result",
params)
# paramsのリストから4つの値を受け取る
def test_calc(param_a, param_b, param_c, result):
ans = calc(param_a, param_b, param_c)
# 3回分のケースを1回ずつ比較
assert ans == result