自動化測試框架pytest教程16-高級參數化

使用複雜的值

有時你可能想用數據結構或對象作爲參數化的值。

ch16/test_ids.py

@pytest.mark.parametrize(
    "starting_card",
    [
        Card("foo", state="todo"),
        Card("foo", state="in prog"),
        Card("foo", state="done"),
    ],
)
def test_card(cards_db, starting_card):
    index = cards_db.add_card(starting_card)
    cards_db.finish(index)
    card = cards_db.get_card(index)
    assert card.state == "done"
  • 執行
$ pytest -v test_ids.py::test_card
============================= test session starts =============================
platform win32 -- Python 3.9.13, pytest-7.1.2, pluggy-1.0.0 -- D:\ProgramData\Anaconda3\python.exe
cachedir: .pytest_cache
Using --randomly-seed=3603486280
rootdir: D:\code\pytest_quick, configfile: pytest.ini
plugins: allure-pytest-2.12.0, Faker-4.18.0, tep-0.8.2, anyio-3.5.0, cov-4.0.0, randomly-3.12.0, repeat-0.9.1, xdist-3.1.0
collecting ... collected 3 items

test_ids.py::test_card[starting_card1] PASSED                            [ 33%]
test_ids.py::test_card[starting_card2] PASSED                            [ 66%]
test_ids.py::test_card[starting_card0] PASSED                            [100%]

============================== 3 passed in 0.22s ==============================

對於那些沒有明顯字符串值的對象,pytest會對其進行編號。"starting_card0","starting_card1",等等。

創建自定義標識符

你可以通過使用ids參數定義函數來生成標識符。通常情況下,內置的str或repr函數可以正常工作。

讓我們嘗試使用str作爲一個ID函數。

ch16/test_ids.py

card_list = [
    Card("foo", state="todo"),
    Card("foo", state="in prog"),
    Card("foo", state="done"),
]

@pytest.mark.parametrize("starting_card", card_list, ids=str)
def test_id_str(cards_db, starting_card):
    ...
    index = cards_db.add_card(starting_card)
    cards_db.finish(index)
    card = cards_db.get_card(index)
    assert card.state == "done"

這裏我們添加了ids=str。

  • 執行

$ pytest -v test_ids.py::test_id_str
============================= test session starts =============================
platform win32 -- Python 3.9.13, pytest-7.1.2, pluggy-1.0.0 -- D:\ProgramData\Anaconda3\python.exe
cachedir: .pytest_cache
Using --randomly-seed=3244471004
rootdir: D:\code\pytest_quick, configfile: pytest.ini
plugins: allure-pytest-2.12.0, Faker-4.18.0, tep-0.8.2, anyio-3.5.0, cov-4.0.0, randomly-3.12.0, repeat-0.9.1, xdist-3.1.0
collecting ... collected 3 items

test_ids.py::test_id_str[Card(summary='foo', owner=None, state='done', id=None)] PASSED [ 33%]
test_ids.py::test_id_str[Card(summary='foo', owner=None, state='in prog', id=None)] PASSED [ 66%]
test_ids.py::test_id_str[Card(summary='foo', owner=None, state='todo', id=None)] PASSED [100%]

============================== 3 passed in 0.19s ==============================

讓我們來定義我們自己的ID函數。它需要接收Card對象並返回字符串。而我們將把id設置爲我們的新函數。

ch16/test_ids.py

def card_state(card):
    return card.state

@pytest.mark.parametrize("starting_card", card_list, ids=card_state)
def test_id_func(cards_db, starting_card):
    ...
    index = cards_db.add_card(starting_card)
    cards_db.finish(index)
    card = cards_db.get_card(index)
    assert card.state == "done"
  • 執行
$ pytest -v test_ids.py::test_id_func
============================= test session starts =============================
platform win32 -- Python 3.9.13, pytest-7.1.2, pluggy-1.0.0 -- D:\ProgramData\Anaconda3\python.exe
cachedir: .pytest_cache
Using --randomly-seed=1314635729
rootdir: D:\code\pytest_quick, configfile: pytest.ini
plugins: allure-pytest-2.12.0, Faker-4.18.0, tep-0.8.2, anyio-3.5.0, cov-4.0.0, randomly-3.12.0, repeat-0.9.1, xdist-3.1.0
collecting ... collected 3 items

test_ids.py::test_id_func[done] PASSED                                   [ 33%]
test_ids.py::test_id_func[in prog] PASSED                                [ 66%]
test_ids.py::test_id_func[todo] PASSED                                   [100%]

============================== 3 passed in 0.18s ==============================

許多ID函數會很短。如果是單行函數,用lambda函數效果很好。

ch16/test_ids.py

@pytest.mark.parametrize(
    "starting_card", card_list, ids=lambda c: c.state
)
def test_id_lambda(cards_db, starting_card):
    ...
    index = cards_db.add_card(starting_card)
    cards_db.finish(index)
    card = cards_db.get_card(index)
    assert card.state == "done"

輸出將看起來是一樣的。

ids功能在參數化的固定程序和pytest_generate_tests中也是可用的。還有兩種方法可以創建自定義標識符:pytest.param和id列表。

在pytest.param中添加一個ID
在標記文件、類和參數中,我們用pytest.param爲參數化的值添加標記。pytest.param也可以用來添加ID。在下面的例子中,我們將爲一個參數添加一個 "特殊 "的ID。

ch16/test_ids.py
c_list = [
Card("foo", state="todo")。
" pytest.param(Card("foo", state="in prog"), id="special") 。
Card("foo", state="done"),
]

@pytest.mark.parametrize("starting_card", c_list, ids=card_state)
def test_id_param(card_db, starting_card):
...
這個方法在與其他方法結合時特別有用。在這個例子中,我們用pytest.param指定了一個 "特殊 "的ID,並讓ids=cards_state()生成其他的ID。

結果測試運行看起來像這樣。

$ pytest -v test_ids.py::test_id_param
========================= 測試會話開始 ==========================
收集了3個項目

test_ids.py::test_id_param[todo] PASSED [ 33%]
test_ids.py::test_id_param[special] PASSED [ 66%]
test_ids.py::test_id_param[done] PASSED [100%]

========================== 3在0.02s內通過 ===========================
如果你只有一兩個需要特殊處理的ID,使用pytest.param是非常好的。如果你想手工編寫所有的ID,pytest.param會很麻煩。如果你想爲所有的值編寫自定義的ID,使用一個列表可能更容易維護。

你可以給ids提供一個列表,而不是函數。

ch16/test_ids.py

id_list = ["todo", "in prog", "done"]


@pytest.mark.parametrize("starting_card", card_list, ids=id_list)
def test_id_list(cards_db, starting_card):
    ...
    index = cards_db.add_card(starting_card)
    cards_db.finish(index)
    card = cards_db.get_card(index)
    assert card.state == "done"

你必須格外小心,以保持列表的同步。否則,ID就會出錯。保持ID和值在一起的一個方法是使用ID作爲字典的鍵。然後你可以用 .keys() 作爲 ID 的列表,用 .values() 作爲參數的列表。當ID不容易用函數生成時,以這種方式使用字典特別有用。

text_variants = {
    "Short": "x",
    "With Spaces": "x y z",
    "End In Spaces": "x    ",
    "Mixed Case": "SuMmArY wItH MiXeD cAsE",
    "Unicode": "¡¢£¤¥¦§¨©ª«¬®¯°±²³´µ¶·¸¹º»¼½¾",
    "Newlines": "a\nb\nc",
    "Tabs": "a\tb\tc",
}


@pytest.mark.parametrize(
    "variant", text_variants.values(), ids=text_variants.keys()
)
def test_summary_variants(cards_db, variant):
    i = cards_db.add_card(Card(summary=variant))
    c = cards_db.get_card(i)
    assert c.summary == variant

使用動態值

ch16/test_param_gen.py

import pytest
from cards import Card

def text_variants():
    variants = {
        "Short": "x",
        "With Spaces": "x y z",
        "End in Spaces": "x    ",
        "Mixed Case": "SuMmArY wItH MiXeD cAsE",
        "Unicode": "¡¢£¤¥¦§¨©ª«¬®¯°±²³´µ¶·¸¹º»¼½¾",
        "Newlines": "a\nb\nc",
        "Tabs": "a\tb\tc",
    }
    for key, value in variants.items():
        yield pytest.param(value, id=key)


@pytest.mark.parametrize("variant", text_variants())
def test_summary(cards_db, variant):
    i = cards_db.add_card(Card(summary=variant))
    c = cards_db.get_card(i)
    assert c.summary == variant

  • 執行
$ pytest test_param_gen.py -v
============================= test session starts =============================
platform win32 -- Python 3.9.13, pytest-7.1.2, pluggy-1.0.0 -- D:\ProgramData\Anaconda3\python.exe
cachedir: .pytest_cache
Using --randomly-seed=1784156481
rootdir: D:\code\pytest_quick, configfile: pytest.ini
plugins: allure-pytest-2.12.0, Faker-4.18.0, tep-0.8.2, anyio-3.5.0, cov-4.0.0, randomly-3.12.0, repeat-0.9.1, xdist-3.1.0
collecting ... collected 7 items

test_param_gen.py::test_summary[Mixed Case] PASSED                       [ 14%]
test_param_gen.py::test_summary[Tabs] PASSED                             [ 28%]
test_param_gen.py::test_summary[Newlines] PASSED                         [ 42%]
test_param_gen.py::test_summary[End in Spaces] PASSED                    [ 57%]
test_param_gen.py::test_summary[Short] PASSED                            [ 71%]
test_param_gen.py::test_summary[Unicode] PASSED                          [ 85%]
test_param_gen.py::test_summary[With Spaces] PASSED                      [100%]

============================== 7 passed in 0.41s ==============================

使用多個參數

ch16/test_multiple.py

from cards import Card

summaries = ["short", "a bit longer"]
owners = ["First", "First M. Last"]
states = ["todo", "in prog", "done"]


@pytest.mark.parametrize(
    "summary, owner, state",
    [
        ("short", "First", "todo"),
        ("short", "First", "in prog"),
        # ...
    ],
)
def test_add_lots(cards_db, summary, owner, state):
    """Make sure adding to db doesn't change values."""
    i = cards_db.add_card(Card(summary, owner=owner, state=state))
    card = cards_db.get_card(i)

    expected = Card(summary, owner=owner, state=state)
    assert card == expected


@pytest.mark.parametrize("state", states)
@pytest.mark.parametrize("owner", owners)
@pytest.mark.parametrize("summary", summaries)
def test_stacking(cards_db, summary, owner, state):
    """Make sure adding to db doesn't change values."""
    ...
    expected = Card(summary, owner=owner, state=state)
    i = cards_db.add_card(Card(summary, owner=owner, state=state))
    card = cards_db.get_card(i)
    assert card == expected

  • 執行
$ pytest test_multiple.py::test_add_lots -v
============================= test session starts =============================
platform win32 -- Python 3.9.13, pytest-7.1.2, pluggy-1.0.0 -- D:\ProgramData\Anaconda3\python.exe
cachedir: .pytest_cache
Using --randomly-seed=3994018912
rootdir: D:\code\pytest_quick, configfile: pytest.ini
plugins: allure-pytest-2.12.0, Faker-4.18.0, tep-0.8.2, anyio-3.5.0, cov-4.0.0, randomly-3.12.0, repeat-0.9.1, xdist-3.1.0
collecting ... collected 2 items

test_multiple.py::test_add_lots[short-First-in prog] PASSED              [ 50%]
test_multiple.py::test_add_lots[short-First-todo] PASSED                 [100%]

============================== 2 passed in 0.15s ==============================

$ pytest test test_multiple.py::test_stacking -v
============================= test session starts =============================
platform win32 -- Python 3.9.13, pytest-7.1.2, pluggy-1.0.0 -- D:\ProgramData\Anaconda3\python.exe
cachedir: .pytest_cache
Using --randomly-seed=1036957166
rootdir: D:\code\pytest_quick, configfile: pytest.ini
plugins: allure-pytest-2.12.0, Faker-4.18.0, tep-0.8.2, anyio-3.5.0, cov-4.0.0, randomly-3.12.0, repeat-0.9.1, xdist-3.1.0
collecting ... ERROR: file or directory not found: test

collected 0 items

============================ no tests ran in 0.05s ============================

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章