使用 mockito 替代 unitest.mock

kingron發表於2024-06-26

前言

節選並翻譯自 Testing with Python (part 6): Fake it... - Bite code!


介紹

mockito 是一個 pytest 外掛,相比於 unitest.mock,具有如下特點:

  • 所有物件都自動為 autospec,無需擔心模擬物件被錯誤的呼叫而不被發現
  • 不使用字串引數作為模擬物件,可以減少拼寫錯誤
  • 提供的返回值或丟擲錯誤更加冗長,但也更明確和具體

安裝

使用 pip 安裝即可:

pip install pytest-mockito

unitest.mock 的對比

比如對以下內容中的 main 方法測試,檔名為 my_life_work.py

def transform(param):
    return param * 2

def check(param):
    return "bad" not in param

def calculate(param):
    return len(param)

def main(param, option):
    if option:
        param = transform(param)
    if not check(param):
        raise ValueError("Woops")
    return calculate(param)

使用 unitest.mock,寫法如下:

import pytest

from unittest.mock import patch
from my_life_work import main

@patch("my_life_work.transform")
@patch("my_life_work.check")
@patch("my_life_work.calculate")
def test_main(calculate, check, transform):
    check.return_value = True
    calculate.return_value = 5

    assert main("param", False) == calculate.return_value
    transform.assert_not_called()
    check.assert_called_with("param")
    calculate.assert_called_once_with("param")

    transform.return_value = "paramparam"
    calculate.return_value = 10
    assert main("param", True) == calculate.return_value
    transform.assert_called_with("param")
    check.assert_called_with("paramparam")
    calculate.assert_called_with("paramparam")

    with pytest.raises(ValueError):
        check.side_effect = ValueError
        main("bad_param", False)
        check.assert_called_with("param")
        transform.assert_not_called()
        calculate.assert_not_called()

再看下使用 mockito 的寫法:

import pytest
import my_life_work # 匯入以使用 mock
from my_life_work import main

def test_main(expect):

    expect(my_life_work, times=1).check("param").thenReturn(True)
    expect(my_life_work, times=1).calculate("param").thenReturn(5)
    # '...' 表示任意的引數,這裡不期望 transform 會被呼叫
    expect(my_life_work, times=0).transform(...)
    assert main("param", False) == 5

    expect(my_life_work, times=1).transform("param").thenReturn("paramparam")
    expect(my_life_work, times=1).check("paramparam").thenReturn(True)
    expect(my_life_work, times=1).calculate("paramparam").thenReturn(10)
    assert main("param", True) == 10

    expect(my_life_work, times=1).check("bad_param").thenReturn(False)
    expect(my_life_work, times=0).transform(...)
    expect(my_life_work, times=0).calculate(...)
    with pytest.raises(ValueError):
        main("bad_param", False)

mockito 中,原來 mock 的寫法

@patch("my_life_work.check")
...
check.return_value = True
check.assert_called_with("param")

寫法變為:

expect(my_life_work, times=1).check("param").thenReturn(True)

更清晰,也不容易出錯。


參考

  1. Testing with Python (part 6): Fake it... - Bite code!
  2. pytest-mockito · PyPI

相關文章