簡體   English   中英

如何根據 pytest 中的夾具參數對測試進行參數化?

[英]How to parameterize a test depending on fixture parameters in pytest?

我有一個 Python 程序,它根據輸入規范生成 C 代碼。 我正在用 pytest 編寫測試。 當然,測試策略包括對生成的 C 代碼的一些測試。

對於這些測試,計划如下所示:

  • 我們有一組目錄,每個目錄包含一個規范文件和一組適用的輸入/預期 output 案例。

  • 夾具將處理生成 C 代碼並編譯它。 該夾具將在一組規范文件(由測試腳本以編程方式讀取)上進行參數化。 這樣做的好處是,對於該規范下的所有測試用例,構建只能完成一次(因為構建成本很高)。

  • 測試 function 將從夾具中獲取GeneratedCode object,使用特定輸入運行它,並驗證預期的 output。 這將在一組輸入/輸出案例(也由腳本以編程方式讀取)上進行參數化。

這樣,添加新的測試用例就像添加新的規范或測試用例文件一樣簡單。 無需在測試腳本中復制和粘貼代碼。

我想象它看起來像這樣:

# Get the list of specification files and test cases programmatically
specification_names = get_list_of_specifications()
test_cases = dict()
for spec in specification_names:
    # get_list_of_test_cases() returns a list of (input, output) tuples
    test_cases[spec] = get_list_of_test_cases(spec)

class GeneratedCode:
    def __init__(spec):
        """Generate the C code for spec in a temp directory"""
        self.name = spec
        ...
    
    def build():
        """Build the generated C code"""
        ...
    
    def run(input):
        """Run the code on given input."""
        ...
    
    def cleanup():
        ...

@pytest.fixture(scope="module", params=specification_names)
def generated_code(request):
    code = GeneratedCode(request.param)
    code.build()
    yield code
    code.cleanup()

@pytest.mark.parametrize('test_input,expected_output', test_cases[???])
def test_generated_code(generated_code, test_input, expected_output):
    assert generated_code.run(test_input) == expected_output

當然,這里的問題是@pytest.mark.parametrize()不能每次都使用相同的測試用例集,因為它取決於生成代碼的規范。 如果我們可以獲得當前夾具的參數,我們可以在test_cases字典中查找它,但我不確定如何做到這一點,或者是否有可能。

有沒有辦法做到這一點? 我還有其他方法可以處理這些測試嗎?

通過將規范作為 generate_code 中元組的一部分傳回,可能能夠將數據連接在一起。

@pytest.fixture(scope="module", params=specification_names)
def generated_code(spec):
    code = GeneratedCode(spec)
    code.build()
    yield code, spec
    code.cleanup()

def test_generated_code(generated_code):
    code, spec = generated_code
    test_input, expected_output = test_cases[spec]
    assert generated_code.run(test_input) == expected_output```

我能想到的另一種方法是使用subTest ,如果您可以訪問unittest ,它是 python 標准庫的一部分:

import unittest

class TestSequence(unittest.TestCase):

    def _setup(self, spec):
        self.code = GeneratedCode(spec)
        self.code.build()

    def tearDown(self):
        self.code.cleanup()

    def test_generated_code(self):
        for spec, (test_input, expected_output) in test_cases.items():
            with self.subTest(spec):
                self._setup(spec)
                assert self.code.run(test_input) == expected_output

@pytest.mark.parametrizeindirect參數可以幫助完成這項工作。 它基本上允許參數化測試 function 中的夾具。

specification_names = get_list_of_specifications()
test_cases = []
for spec in specification_names:
    test_cases.extend([(spec, input, output) for (input, output) in
                       get_list_of_test_cases(spec)])

...

@pytest.fixture(scope="module")
def generated_code(request):
    code = GeneratedCode(request.param)
    code.build()
    yield code
    code.cleanup()

@pytest.mark.parametrize(
        'generated_code,test_input,expected_output',
        test_cases,
        indirect=['generated_code'],
        scope="module" # <-- This is important!
)
def test_generated_code(generated_code, test_input, expected_output):
    assert generated_code.run(test_input) == expected_output

注意parametrize裝飾器中的scope="module" 如果未指定,它將默認為'function' ,並且在某些情況下(包括這個),這似乎優先於夾具指定的 scope。

對我來說,細節很模糊。 關於scope甚至對@pytest.mark.parameterize意味着什么的文檔不是很清楚。 但是,似乎如果parametrize中的所有參數都是indirect的,則夾具使用自己的 scope,否則使用來自parametrize的 scope。 但是,如果您有多個測試函數使用相同的夾具和indirect ,無論您指定什么,它們通常會在不同的范圍內結束,我不知道為什么。 這是一個以前有問題的區域,現在可能仍然存在

在任何情況下,上面的代碼都應該做你想做的事情,但最好將夾具 scope 更多地視為性能優化,而不是依賴它來進行正確的測試行為(聽起來你已經在做)。

暫無
暫無

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

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