4
2

More than 1 year has passed since last update.

Pytestを使って自分なりに解説(mock/parametrize)

Last updated at Posted at 2022-12-12

前提

  • 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を使用する流れ
    1. paramitrizeで使用するためのリスト内タプルを作成(params)
    2. タプル内の引数を必要な数作成し、必要な値を記述(関数呼び出しを記載するのも可)-> 今回は4つ
    3. 使用するテスト関数上部にデコレータでparametrizeと任意の変数名を付与
    4. testの引数に3.の変数名を受け取る
    5. test内で柔軟に値を変えたい箇所に引数名を記載する
    6. 下記テストコード内の★1~3の順で1回ずつテストが走る(計3回)
    7. 今回のケースでは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
4
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
2