简体   繁体   English

在pytest中使用参数的笛卡尔积进行参数化测试

[英]parameterized test with cartesian product of arguments in pytest

Just wondering, is there any (more) elegant way of parameterizing with the cartesian product? 只是想知道,有没有(更多)优雅的方式参与笛卡尔积? This is what I figured out so far: 这是我到目前为止所发现的:

numbers    = [1,2,3,4,5]
vowels     = ['a','e','i','o','u']
consonants = ['x','y','z']

cartesian = [elem for elem in itertools.product(*[numbers,vowels,consonants])]

@pytest.fixture(params=cartesian)
def someparams(request):
  return request.param

def test_something(someparams):
  pass

At least I'd like to encapsulate numbers, vowels, consonants and cartesian in the fixture function. 至少我想在夹具功能中封装数字,元音,辅音和笛卡儿。

You can apply multiple parametrize arguments, in which case they will generate a product of all parameters: 您可以应用多个parametrize参数,在这种情况下,它们将生成所有参数的乘积:

import pytest

numbers = [1,2,3,4,5]
vowels = ['a','e','i','o','u']
consonants = ['x','y','z']


@pytest.mark.parametrize('number', numbers)
@pytest.mark.parametrize('vowel', vowels)
@pytest.mark.parametrize('consonant', consonants)
def test(number, vowel, consonant):
    pass

I can think of two ways to do this. 我可以想到两种方法来做到这一点。 One uses parametrized fixtures, and one parametrizes the test function. 一个使用参数化夹具,一个参数化测试功能。 It's up to you which one you find more elegant. 这取决于你找到哪一个更优雅。

Here is the test function parametrized: 这是参数化的测试函数:

import itertools
import pytest

numbers = [1,2,3,4,5]
vowels = ['a','e','i','o','u']
consonants = ['x','y','z']


@pytest.mark.parametrize('number,vowel,consonant',
    itertools.product(numbers, vowels, consonants)
)
def test(number, vowel, consonant):
    pass

Of note, the second argument to the parametrize decorator can be an iterable, not just a list. 值得注意的是,参数化装饰器的第二个参数可以是可迭代的,而不仅仅是一个列表。

Here is how you do it by parametrizing each fixture: 以下是通过参数化每个夹具来实现的方法:

import pytest

numbers = [1,2,3,4,5]
vowels = ['a','e','i','o','u']
consonants = ['x','y','z']


@pytest.fixture(params=numbers)
def number(request):
    return request.param

@pytest.fixture(params=vowels)
def vowel(request):
    return request.param

@pytest.fixture(params=consonants)
def consonant(request):
    return request.param


def test(number, vowel, consonant):
    pass

Your intuition was correct. 你的直觉是正确的。 By parametrizing each of multiple fixtures, pytest takes care of creating all the permutations that arise. 通过参数化多个灯具中的每一个,pytest负责创建出现的所有排列。

The test output is identical. 测试输出完全相同。 Here is a sample (I ran py.test with the -vv option): 这是一个示例(我使用-vv选项运行py.test):

test_bar.py:22: test[1-a-x] PASSED
test_bar.py:22: test[1-a-y] PASSED
test_bar.py:22: test[1-a-z] PASSED
test_bar.py:22: test[1-e-x] PASSED
test_bar.py:22: test[1-e-y] PASSED
test_bar.py:22: test[1-e-z] PASSED
test_bar.py:22: test[1-i-x] PASSED

I think besides an elegant solution, you should also consider both the amount of time each option will take and the amount of code you'll have to maintain. 我认为除了优雅的解决方案之外,您还应该考虑每个选项将花费的时间量以及您必须维护的代码量。

Possible solutions 可能的解决方案

  1. Using parametrize once with itertools (provided by Frank T) 使用itertools进行一次parametrize (由Frank T提供)
  2. Using 3 fixtures (provided by Frank T) 使用3个灯具(由Frank T提供)
  3. Using parametrize 3 times (provided by Bruno Oliveira) 使用parametrize 3次(由Bruno Oliveira提供)
  4. Using 1 fixture and itertools (provided in the question) 使用1个fixture和itertools(在问题中提供)

Solution 1 解决方案1

@pytest.mark.parametrize('number, vowel, consonant',
                         itertools.product(numbers, vowels, consonants))
def test(number, vowel, consonant):
    pass

Solution 2 解决方案2

@pytest.fixture(params=numbers)
def number(request): return request.param

@pytest.fixture(params=vowels)
def vowel(request): return request.param

@pytest.fixture(params=consonants)
def consonant(request): return request.param


def test(number, vowel, consonant):
    pass

Solution 3 解决方案3

@pytest.mark.parametrize('number', numbers)
@pytest.mark.parametrize('vowel', vowels)
@pytest.mark.parametrize('consonant', consonants)
def test(number, vowel, consonant):
    pass

Solution 4 解决方案4

@pytest.fixture(params=cartesian)
def someparams(request):
  return request.param

def test_something(someparams):
  pass

When it comes to elegance, I consider that Solution 3 is the best option because it has less code maintain, and it does not require to import itertools . 在优雅方面,我认为解决方案3是最佳选择,因为它具有较少的代码维护,并且不需要导入itertools After that Solution 1 is the best choice because you don't need to write fixtures as Solution 4 , and Solution 2 . 之后, 解决方案1是最佳选择,因为您不需要将解决方案4作为解决方案4解决方案2 Solution 4 is probably better than Solution 2 because it requires less code to maintain. 解决方案4可能比解决方案2更好,因为它需要更少的代码来维护。

When it comes to performance I run each solution using numbers = list(range(100)) , and I got the following results: 在性能方面,我使用numbers = list(range(100))运行每个解决方案,并得到以下结果:

|  Solution  |  Time    | 
| Solution 1 |  3.91s   |
| Solution 2 |  3.59s   |
| Solution 3 |  3.54s   |
| Solution 4 |  3.09s   |

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM