簡體   English   中英

在 Python 中動態參數化多個測試

[英]Parametrizing multiple tests dynamically in Python

我正在嘗試使用 Pytest 編寫動態測試套件,其中測試數據保存在單獨的文件中,例如 YAML 文件或 a.csv。 我想運行多個測試,所有這些測試都是從同一個文件中參數化的。 假設我有一個測試文件test_foo.py ,它看起來像這樣:

import pytest

@pytest.mark.parametrize("num1, num2, output", ([2, 2, 4], [3, 7, 10], [48, 52, 100]))
def test_addnums(num1, num2, output):
    assert foo.addnums(num1, num2) == output

@pytest.mark.parametrize("foo, bar", ([1, 2], ['moo', 'mar'], [0.5, 3.14]))
def test_foobar(foo, bar):
    assert type(foo) == type(bar)

使用參數化裝飾器,我可以在 pytest 中運行多個測試,並且可以按預期工作:

test_foo.py::test_addnums[2-2-4] PASSED                                                                                                                                                            
test_foo.py::test_addnums[3-7-10] PASSED                                                                                                                                                           
test_foo.py::test_addnums[48-52-100] PASSED                                                                                                                                                        
test_foo.py::test_foobar[1-2] PASSED                                                                                                                                                               
test_foo.py::test_foobar[moo-mar] PASSED                                                                                                                                                           
test_foo.py::test_foobar[0.5-3.14] PASSED

但我想動態參數化這些測試。 我的意思是,我想將所有測試的測試數據寫入一個單獨的文件中,這樣當我運行 pytest 時,它會將我寫入的所有測試數據應用於每個測試 function。 假設我有一個看起來像這樣的 YAML 文件:

test_addnums:
  params: [num1, num2, output]
  values:
    - [2, 2, 4]
    - [3, 7, 10]
    - [48, 52, 100]

test_foobar:
  params: [foo, bar]
  values:
    - [1, 2]
    - [moo, mar]
    - [0.5, 3.14]

然后我想閱讀這個 YAML 文件並使用數據來參數化我的測試文件中的所有測試函數。

我知道pytest_generate_tests鈎子,我一直在嘗試使用它來動態加載測試。 我嘗試將之前傳遞給parametrize裝飾器的相同參數和數據值添加到metafunc.parametrize掛鈎中:

def pytest_generate_tests(metafunc):
    metafunc.parametrize("num1, num2, output", ([2, 2, 4], [3, 7, 10], [48, 52, 100]))
    metafunc.parametrize("foo, bar", ([1, 2], ['moo', 'mar'], [0.5, 3.14]))

def test_addnums(num1, num2, output):
    assert foo.addnums(num1, num2) == output

def test_foobar(foo, bar):
    assert type(foo) == type(bar)

但是,這不起作用,因為 pytest 嘗試將測試數據應用於每個 function:

collected 0 items / 1 error                                           

=============================== ERRORS ================================
____________________ ERROR collecting test_foo.py _____________________
In test_addnums: function uses no argument 'foo'
======================= short test summary info =======================
ERROR test_foo.py
!!!!!!!!!!!!!!! Interrupted: 1 error during collection !!!!!!!!!!!!!!!!
========================== 1 error in 0.16s ===========================

我想知道的是:如何使用 pytest 動態參數化多個測試 我已經使用 pdb 自省了 pytest,據我所知, metafunc只知道您在文件中定義的第一個測試。 在上面的示例中,首先定義了test_addnums ,因此當我在 pdb 調試器中打印vars(metafunc)時,它會顯示以下值:

(Pdb) pp vars(metafunc)
{'_arg2fixturedefs': {},
 '_calls': [<_pytest.python.CallSpec2 object at 0x7f4330b6e860>,
            <_pytest.python.CallSpec2 object at 0x7f4330b6e0b8>,
            <_pytest.python.CallSpec2 object at 0x7f4330b6e908>],
 'cls': None,
 'config': <_pytest.config.Config object at 0x7f43310dbdd8>,
 'definition': <FunctionDefinition test_addnums>,
 'fixturenames': ['num1', 'num2', 'output'],
 'function': <function test_addnums at 0x7f4330b5a6a8>,
 'module': <module 'test_foo' from '<PATH>/test_foo.py'>}

但是,如果我切換test_foobartest_addnums函數,並顛倒parametrize調用的順序,它會顯示有關test_foobar的信息。

(Pdb) pp vars(metafunc)
{'_arg2fixturedefs': {},
 '_calls': [<_pytest.python.CallSpec2 object at 0x7f6d20d5e828>,
            <_pytest.python.CallSpec2 object at 0x7f6d20d5e860>,
            <_pytest.python.CallSpec2 object at 0x7f6d20d5e898>],
 'cls': None,
 'config': <_pytest.config.Config object at 0x7f6d212cbd68>,
 'definition': <FunctionDefinition test_foobar>,
 'fixturenames': ['foo', 'bar'],
 'function': <function test_foobar at 0x7f6d20d4a6a8>,
 'module': <module 'test_foo' from '<PATH>/test_foo.py'>}

所以看起來 metafunc 實際上並沒有在我的測試文件中存儲關於每個測試 function 的信息。 因此我不能使用夾具function fixturenames ,因為它們僅適用於一個特定的 function,而不是全部。

如果是這種情況,那么我如何訪問所有其他測試功能並單獨參數化它們?

You can do this using pytest_generate_tests , as you have tried, you just have to select the correct parameters for parametrization for each function (I put the result of parsing the yaml into a global dict for simplicity):

all_params = {
    "test_addnums": {
        "params": ["num1", "num2", "output"],
        "values":
            [
                [2, 2, 4],
                [3, 7, 10],
                [48, 52, 100]
            ]
    },
    "test_foobar":
        {
            "params": ["foo", "bar"],
            "values": [
                [1, 2],
                ["moo", "mar"],
                [0.5, 3.14]
            ]
        }
}


def pytest_generate_tests(metafunc):
    fct_name = metafunc.function.__name__
    if fct_name in all_params:
        params = all_params[fct_name]
        metafunc.parametrize(params["params"], params["values"])


def test_addnums(num1, num2, output):
    assert num1 + num2 == output


def test_foobar(foo, bar):
    assert type(foo) == type(bar)

這是相關的output:

$python -m pytest -v param_multiple_tests.py
...
collected 6 items

param_multiple_tests.py::test_addnums[2-2-4] PASSED
param_multiple_tests.py::test_addnums[3-7-10] PASSED
param_multiple_tests.py::test_addnums[48-52-100] PASSED
param_multiple_tests.py::test_foobar[1-2] PASSED
param_multiple_tests.py::test_foobar[moo-mar] PASSED
param_multiple_tests.py::test_foobar[0.5-3.14] PASSED
===================== 6 passed in 0.27s =======================

我認為您在文檔中遺漏的是pytest_generate_tests分別為每個測試調用。 更常見的使用方法是檢查夾具名稱而不是測試名稱,例如:

def pytest_generate_tests(metafunc):
    if "foo" in metafunc.fixturenames and "bar" in metafunc.fixturenames:
         metafunc.parametrize(["foo", "bar"], ...)

為此,我編寫了一個名為parametrize_from_file的 package。 它的工作原理是提供一個與@pytest.mark.parametrize基本上做同樣事情的裝飾器,除了它從外部文件讀取參數。 我認為這種方法比亂用pytest_generate_tests簡單得多。

這是查找您上面提供的示例數據的方式。 首先,我們需要重新組織數據,使頂層是一個以測試名稱為鍵的字典,第二層是測試用例列表,第三層是參數名稱到參數值的字典:

test_addnums:
  - num1: 2
    num2: 2
    output: 4

  - num1: 3
    num2: 7
    output: 10

  - num1: 48
    num2: 52
    output: 100

test_foobar:
  - foo: 1
    bar: 2

  - foo: boo
    bar: mar

  - foo: 0.5
    bar: 3.14

接下來,我們只需要將@parametrize_from_file裝飾器應用於測試:

import parametrize_from_file

@parametrize_from_file
def test_addnums(num1, num2, output):
    assert foo.addnums(num1, num2) == output

@parametrize_from_file
def test_foobar(foo, bar):
    assert type(foo) == type(bar)

這假設@parameterize_from_file能夠在默認位置找到參數文件,該文件是與測試腳本具有相同基本名稱的文件(例如test_things.{yml,toml,nt}用於test_things.py )。 但您也可以手動指定路徑。

值得一提的parametrize_from_file的其他一些功能,通過pytest_generate_tests實現自己會很煩人:

  • 您可以在每個測試用例的基礎上指定 id 和標記。
  • 您可以將模式應用於測試用例。 我經常用它來eval代碼的片段。
  • 您可以在同一個測試 function 中同時使用@parametrize_from_file@pytest.mark.parametrize任意次數。
  • 如果有關參數文件的任何內容沒有意義(例如錯誤的組織、缺少名稱、不一致的參數集等),您將收到很好的錯誤消息

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM