max*_*zig 6 python performance pytest python-3.x
在 pytest 中参数化测试和夹具时,pytest 似乎急切地评估所有参数并在开始执行测试之前构建一些测试列表数据结构。
这是在 2 种情况下的问题:
因此我的问题是:是否有可能告诉 pytest 即时评估参数(即懒惰)?
至于你的第二个问题——在手册的评论链接中提出的问题似乎正是人们应该做的。它允许“仅在实际测试运行时设置昂贵的资源,例如数据库连接或子进程”。
但对于 1 个问题,此类功能似乎尚未实现。您可以直接将生成器传递给parametrize像这样:
@pytest.mark.parametrize('data', data_gen)
def test_gen(data):
...
Run Code Online (Sandbox Code Playgroud)
但是 pytest 将list()生成器 -> RAM 问题在这里也仍然存在。
我还发现了一些 github问题,而不是进一步阐明为什么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)
Run Code Online (Sandbox Code Playgroud)
@pytest.mark.parametrize('ind', range(N))
def test_yield(ind):
data = next(data_gen)
assert data
Run Code Online (Sandbox Code Playgroud)
因此,在这里您进行参数化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'])
Run Code Online (Sandbox Code Playgroud)
并与直接比较:
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'])
Run Code Online (Sandbox Code Playgroud)
哪个“吃掉”更多的内存:
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'])
Run Code Online (Sandbox Code Playgroud)
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
...
Run Code Online (Sandbox Code Playgroud)
因此,我们在module范围级别使用固定装置,以便“预设”我们的数据以进行参数化测试。请注意,您可以在此处添加另一个测试,它也将接收生成的数据。只需将其添加到 test_two 之后:
@pytest.mark.parametrize('data', data_gen)
def test_yield(data):
assert data
Run Code Online (Sandbox Code Playgroud)
注意:如果您不知道生成的数据的数量,您可以使用这个技巧:设置一些近似值(如果它比生成的测试计数高一点更好),并且如果它停止,则“标记”测试通过,StopIteration当所有数据已经生成。
另一种可能性是使用工厂作为固定装置。在这里,您将生成器嵌入到夹具中并try在测试中进行产量,直到它结束。但这里有另一个缺点 - pytest 会将其视为单个测试(内部可能有一堆检查),并且如果生成的数据之一失败,则会失败。换句话说,如果与参数化方法相比,并非所有 pytest 统计数据/功能都可以访问。
另一种是pytest.main()在循环中使用类似这样的东西:
# data_generate
# set_up test
pytest.main(['test'])
Run Code Online (Sandbox Code Playgroud)
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'])
Run Code Online (Sandbox Code Playgroud)
改成:
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
...
Run Code Online (Sandbox Code Playgroud)
它与工厂类似,但更容易实现。此外,它不仅多次减少 RAM,还减少收集元信息的时间。这里的缺点 - 对于 pytest 来说,这将是对所有two值的一次测试。它可以顺利地进行“简单”测试 - 如果xmark内部有一些特殊的东西或其他东西可能会出现问题。
我还打开了相应的问题,可能会出现有关此问题的一些附加信息/调整。