在pytest中对测试和固定装置进行参数化时,pytest似乎急切地评估所有参数并在开始执行测试之前构建一些测试列表数据结构。
这是2种情况下的问题:
因此,我的问题是:可能要告诉pytest动态(即懒惰地)评估参数吗?
答案 0 :(得分:1)
关于您的第二个问题-在manual的评论链接中提出的建议似乎恰好应该解决。它允许“仅在运行实际测试时设置昂贵的资源,如数据库连接或子进程”。
但是对于1个问题,似乎没有实现此功能。您可以像这样直接将生成器传递给parametrize
:
@pytest.mark.parametrize('data', data_gen)
def test_gen(data):
...
但是pytest会list()
生成器-> RAM问题也仍然存在。
我还发现了一些github issues,而不是why pytest无法懒惰地处理生成器。这似乎是一个设计问题。因此,由于
,“无法正确地将生成器作为值来管理参数化”“ pytest必须收集所有带有所有元数据的测试... 收集总是在测试运行之前进行。”
在这种情况下,也有一些引用hypothesis
或nose's yield-base tests
。但是,如果您仍然想坚持使用pytest
,可以采取一些解决方法:
import pytest
def get_data(N):
for i in range(N):
yield list(range(N))
N = 3000
data_gen = get_data(N)
@pytest.mark.parametrize('ind', range(N))
def test_yield(ind):
data = next(data_gen)
assert data
因此,在这里您可以对index
进行参数设置(这不是很有用-仅指示必须执行的pytest执行次数)并在下次运行时生成数据。
您也可以将其包装到memory_profiler
:
Results (46.53s):
3000 passed
Filename: run_test.py
Line # Mem usage Increment Line Contents
================================================
5 40.6 MiB 40.6 MiB @profile
6 def to_profile():
7 76.6 MiB 36.1 MiB pytest.main(['test.py'])
与简单明了的对比:
@pytest.mark.parametrize('data', data_gen)
def test_yield(data):
assert data
“吃掉”更多的内存:
Results (48.11s):
3000 passed
Filename: run_test.py
Line # Mem usage Increment Line Contents
================================================
5 40.7 MiB 40.7 MiB @profile
6 def to_profile():
7 409.3 MiB 368.6 MiB pytest.main(['test.py'])
data_gen = get_data(N)
@pytest.fixture(scope='module', params=len_of_gen_if_known)
def fix():
huge_data_chunk = next(data_gen)
return huge_data_chunk
@pytest.mark.parametrize('other_param', ['aaa', 'bbb'])
def test_one(fix, other_param):
data = fix
...
因此,我们在module
范围级别使用固定装置,以便“预设”我们的数据以进行参数化测试。请注意,您可以在此处添加另一个测试,它也将接收生成的数据。只需在test_two之后添加它即可:
@pytest.mark.parametrize('param2', [15, 'asdb', 1j])
def test_two(fix, param2):
data = fix
...
注意:如果您不知道生成的数据数量,则可以使用此技巧:设置一些近似值(最好是比生成的测试计数高一点),如果标记以{{ 1}},当所有数据都已经生成时就会发生。
另一种可能性是使用Factories as fixtures。在这里,您将生成器嵌入夹具中,并在测试中StopIteration
合格直到结束。但这是另一个缺点-pytest会将其视为单个测试(可能包含一堆检查),并且如果生成的数据之一失败将失败。换句话说,如果与参数化方法相比,可能无法访问所有的pytest统计信息/功能。
另一种方法是在循环中使用try
,如下所示:
pytest.main()
答案 1 :(得分:1)
您可能会发现此解决方法很有用:
from datetime import datetime, timedelta
from time import sleep
import pytest
@pytest.mark.parametrize(
'lazy_params',
[
lambda: (datetime.now() - timedelta(days=1), datetime.now()),
lambda: (datetime.now(), datetime.now() + timedelta(days=1)),
],
)
def test_it(lazy_params):
yesterday, today = lazy_params()
print(f'\n{yesterday}\n{today}')
sleep(1)
assert yesterday < today
样本输出:
========================================================================= test session starts ==========================================================================
platform darwin -- Python 3.7.7, pytest-5.3.5, py-1.8.1, pluggy-0.13.1 -- /usr/local/opt/python/bin/python3.7
cachedir: .pytest_cache
rootdir: /Users/apizarro/tmp
collected 2 items
test_that.py::test_it[<lambda>0]
2020-04-14 18:34:08.700531
2020-04-15 18:34:08.700550
PASSED
test_that.py::test_it[<lambda>1]
2020-04-15 18:34:09.702914
2020-04-16 18:34:09.702919
PASSED
========================================================================== 2 passed in 2.02s ===========================================================================
答案 2 :(得分:1)
编辑:我的第一个反应是“这正是参数化夹具的用途”:功能范围夹具是一个懒惰值,恰好在执行测试节点之前通过参数化调用您可以根据需要预定义多个夹具(例如,从数据库密钥列表中选择)。
from pytest_cases import fixture_plus
@fixture_plus
def db():
return <todo>
@fixture_plus
@pytest.mark.parametrize("key", [<list_of keys>])
def sample(db, key):
return db.get(key)
def test_foo(sample):
return sample
话虽这么说,在某些(罕见)情况下,您仍然需要参数化函数中的惰性值,并且您不希望这些参数成为参数化灯具的变体。对于这些情况,pytest-cases
中现在也有一个lazy_value
解决方案。有了它,您就可以在参数值中使用函数,并且只有在执行当前测试时才调用这些函数。
下面是显示两种编码样式的示例(将use_partial布尔arg切换为True以启用另一种选择)
from functools import partial
from random import random
import pytest
from pytest_cases import lazy_value
database = [random() for i in range(10)]
def get_param(i):
return database[i]
def make_param_getter(i, use_partial=False):
if use_partial:
return partial(get_param, i)
else:
def _get_param():
return database[i]
return _get_param
many_lazy_parameters = (make_param_getter(i) for i in range(10))
@pytest.mark.parametrize('a', [lazy_value(f) for f in many_lazy_parameters])
def test_foo(a):
print(a)
请注意,如果您想自定义测试ID,lazy_value
也有一个id
参数。默认值为使用功能__name__
,对部分功能的支持为on the way。
您可以用相同的方式对灯具进行参数设置,但请记住,您必须使用@fixture_plus
而不是@pytest.fixture
。有关详情,请参见pytest-cases
documentation。
我是pytest-cases
的作者;)