如何參數化固定裝置和測試函數¶
pytest 能在多個層級參數化測試
@pytest.mark.parametrize 允許某人在測試函數或類別中定義多組參數和固定裝置。
pytest_generate_tests 允許某人定義自訂參數化架構或擴充功能。
@pytest.mark.parametrize
:參數化測試函數¶
內建的pytest.mark.parametrize 裝飾器能為測試函數參數化參數。以下是實作檢查特定輸入是否會導致預期輸出的測試函數的典型範例
# content of test_expectation.py
import pytest
@pytest.mark.parametrize("test_input,expected", [("3+5", 8), ("2+4", 6), ("6*9", 42)])
def test_eval(test_input, expected):
assert eval(test_input) == expected
在此,@parametrize
裝飾器定義三個不同的(test_input,expected)
元組,因此test_eval
函數將會使用它們輪流執行三次
$ pytest
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
rootdir: /home/sweet/project
collected 3 items
test_expectation.py ..F [100%]
================================= FAILURES =================================
____________________________ test_eval[6*9-42] _____________________________
test_input = '6*9', expected = 42
@pytest.mark.parametrize("test_input,expected", [("3+5", 8), ("2+4", 6), ("6*9", 42)])
def test_eval(test_input, expected):
> assert eval(test_input) == expected
E AssertionError: assert 54 == 42
E + where 54 = eval('6*9')
test_expectation.py:6: AssertionError
========================= short test summary info ==========================
FAILED test_expectation.py::test_eval[6*9-42] - AssertionError: assert 54...
======================= 1 failed, 2 passed in 0.12s ========================
注意
參數值會原樣傳遞給測試(完全不複製)。
例如,如果你傳遞清單或字典作為參數值,而且測試案例程式碼對其進行變異,變異將會反映在後續的測試案例呼叫中。
注意
pytest 預設會跳脫參數化中 unicode 字串使用的所有非 ASCII 字元,因為它有幾個缺點。然而,如果你想在參數化中使用 unicode 字串,並在終端機中看到它們原本的樣子(未跳脫),請在pytest.ini
中使用這個選項
[pytest]
disable_test_id_escaping_and_forfeit_all_rights_to_community_support = True
但請記住,這可能會造成不必要的副作用,甚至會產生錯誤,具體取決於所使用的作業系統和目前安裝的外掛程式,因此請自行承擔風險。
如本範例中所設計,只有一組輸入/輸出值無法通過簡單的測試函式。而且與測試函式引數一樣,你可以在追蹤中看到 input
和 output
值。
請注意,你也可以對類別或模組使用 parametrize 標記(請參閱 如何使用屬性標記測試函式),這會呼叫多個具有引數集的函式,例如
import pytest
@pytest.mark.parametrize("n,expected", [(1, 2), (3, 4)])
class TestClass:
def test_simple_case(self, n, expected):
assert n + 1 == expected
def test_weird_simple_case(self, n, expected):
assert (n * 1) + 1 == expected
若要將模組中的所有測試參數化,你可以指定給 pytestmark
全域變數
import pytest
pytestmark = pytest.mark.parametrize("n,expected", [(1, 2), (3, 4)])
class TestClass:
def test_simple_case(self, n, expected):
assert n + 1 == expected
def test_weird_simple_case(self, n, expected):
assert (n * 1) + 1 == expected
也可以在 parametrize 中標記個別測試實例,例如使用內建 mark.xfail
# content of test_expectation.py
import pytest
@pytest.mark.parametrize(
"test_input,expected",
[("3+5", 8), ("2+4", 6), pytest.param("6*9", 42, marks=pytest.mark.xfail)],
)
def test_eval(test_input, expected):
assert eval(test_input) == expected
讓我們執行此操作
$ pytest
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
rootdir: /home/sweet/project
collected 3 items
test_expectation.py ..x [100%]
======================= 2 passed, 1 xfailed in 0.12s =======================
先前導致失敗的一個參數集現在顯示為「xfailed」(預期會失敗)測試。
如果提供給 parametrize
的值產生一個空清單 - 例如,如果它們是由某些函式動態產生的 - 則 pytest 的行為由 empty_parameter_set_mark
選項定義。
若要取得多個參數化引數的所有組合,你可以堆疊 parametrize
裝飾器
import pytest
@pytest.mark.parametrize("x", [0, 1])
@pytest.mark.parametrize("y", [2, 3])
def test_foo(x, y):
pass
這將執行測試,其中引數設定為 x=0/y=2
、x=1/y=2
、x=0/y=3
和 x=1/y=3
,以裝飾器的順序用盡參數。
基本 pytest_generate_tests
範例¶
有時候你可能想要實作自己的參數化方案,或實作一些動態機制來決定固定裝置的參數或範圍。為此,你可以使用 pytest_generate_tests
鉤子,它會在收集測試函式時呼叫。透過傳入的 metafunc
物件,你可以檢查請求的測試內容,最重要的是,你可以呼叫 metafunc.parametrize()
來造成參數化。
例如,假設我們想要執行一個測試,採用我們想要透過新的 pytest
命令列選項設定的字串輸入。我們先撰寫一個簡單的測試,接受 stringinput
固定裝置函式引數
# content of test_strings.py
def test_valid_string(stringinput):
assert stringinput.isalpha()
現在我們新增一個 conftest.py
檔案,其中包含新增命令列選項和我們測試函式的參數化
# content of conftest.py
def pytest_addoption(parser):
parser.addoption(
"--stringinput",
action="append",
default=[],
help="list of stringinputs to pass to test functions",
)
def pytest_generate_tests(metafunc):
if "stringinput" in metafunc.fixturenames:
metafunc.parametrize("stringinput", metafunc.config.getoption("stringinput"))
如果我們現在傳遞兩個 stringinput 值,我們的測試將執行兩次
$ pytest -q --stringinput="hello" --stringinput="world" test_strings.py
.. [100%]
2 passed in 0.12s
我們也用會導致測試失敗的字串輸入來執行
$ pytest -q --stringinput="!" test_strings.py
F [100%]
================================= FAILURES =================================
___________________________ test_valid_string[!] ___________________________
stringinput = '!'
def test_valid_string(stringinput):
> assert stringinput.isalpha()
E AssertionError: assert False
E + where False = <built-in method isalpha of str object at 0xdeadbeef0001>()
E + where <built-in method isalpha of str object at 0xdeadbeef0001> = '!'.isalpha
test_strings.py:4: AssertionError
========================= short test summary info ==========================
FAILED test_strings.py::test_valid_string[!] - AssertionError: assert False
1 failed in 0.12s
正如預期,我們的測試函數失敗。
如果您未指定字串輸入,它將被略過,因為 metafunc.parametrize()
將使用空參數清單呼叫
$ pytest -q -rs test_strings.py
s [100%]
========================= short test summary info ==========================
SKIPPED [1] test_strings.py: got empty parameter set ['stringinput'], function test_valid_string at /home/sweet/project/test_strings.py:2
1 skipped in 0.12s
請注意,當使用不同的參數集多次呼叫 metafunc.parametrize
時,這些集合中的所有參數名稱不能重複,否則會引發錯誤。
更多範例¶
如需更多範例,您可能想查看 更多參數化範例。