首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >惰性参数化伴发热试验

惰性参数化伴发热试验
EN

Stack Overflow用户
提问于 2019-01-12 20:20:55
回答 3查看 5.5K关注 0票数 7

在pytest中参数化测试和夹具时,pytest似乎热切地评估所有参数,并在开始执行测试之前构造一些测试列表数据结构。

在以下两种情况下,这是一个问题:

  1. 当您有许多参数值(例如来自生成器)时,生成器和测试本身可能运行得很快,但是所有这些参数值占用了所有内存。
  2. 当参数化使用不同类型的昂贵资源的夹具时,您只能同时运行一个资源(例如,因为他们在同一个端口或类似的地方收听)。

因此,我的问题是:是否可以告诉pytest在飞行中评估参数(即懒惰)?

EN

回答 3

Stack Overflow用户

发布于 2019-07-21 23:22:11

至于你在评论链接中提出的两个问题,手册似乎就是一个人应该做的事情。它允许“只在实际测试运行时设置昂贵的资源,如DB连接或子进程”。

但对于一个问题,似乎没有实现这样的功能。您可以将生成器直接传递给parametrize,如下所示:

代码语言:javascript
复制
@pytest.mark.parametrize('data', data_gen)
def test_gen(data):
    ...

但是pytest将list()的生成器->内存问题也在这里继续存在。

我还发现了一些github 问题,而不是更多地了解为什么 pytest,而不是懒洋洋地处理生成器。这似乎是设计上的问题。因此,“不可能正确地管理参数化,将生成器作为值”,因为

"pytest必须收集所有元数据的所有测试.收集总是在测试运行之前发生“。

在这种情况下,也有一些是指hypothesisnose's yield-base tests。但是,如果您仍然想坚持使用pytest,那么有一些解决办法:

  1. 如果您不知何故知道生成的params的数量,您可以执行以下操作:
代码语言:javascript
复制
import pytest

def get_data(N):
    for i in range(N):
        yield list(range(N))

N = 3000
data_gen = get_data(N)
代码语言:javascript
复制
@pytest.mark.parametrize('ind', range(N))
def test_yield(ind):
    data = next(data_gen)
    assert data

在这里,您将参数化到index (这并不是很有用的--仅仅是指示它必须执行的数量最多),并在下一次运行中生成数据。您也可以将其包装到memory_profiler中。

代码语言:javascript
复制
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'])

和直截了当的比较:

代码语言:javascript
复制
@pytest.mark.parametrize('data', data_gen)
def test_yield(data):
    assert data

它“吃掉”更多的记忆:

代码语言:javascript
复制
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'])
  1. 如果您同时想对另一个参数化测试,您可以对前面的子句做一些概括,如下所示:
代码语言:javascript
复制
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之后添加它:

代码语言:javascript
复制
@pytest.mark.parametrize('param2', [15, 'asdb', 1j])
def test_two(fix, param2):
    data = fix
    ...

注意:如果您不知道生成的数据的数量,可以使用这个技巧:设置一些近似值(如果它比生成的测试计数高一点的话)和“标记”测试,如果它在StopIteration中停止,那么它将在所有生成的数据都生成时发生。

  1. 另一种可能是使用工厂作为固定装置。在这里,您将您的生成器嵌入夹具和try产量在您的测试,直到它还没有结束。但是这里还有另一个缺点-- pytest将它作为单个测试(可能包含大量的内部检查)来处理,如果生成的数据中有一个失败,它就会失败。换句话说,如果与参数化方法相比,并不是所有的pytest统计/特性都可以访问。
  2. 而另一个是在循环中使用pytest.main(),如下所示:
代码语言:javascript
复制
# data_generate
# set_up test
pytest.main(['test'])
  1. 不是迭代器本身,而是如果有参数化测试的话,节省更多时间/RAM的方法:只需在测试中移动一些参数化。示例:
代码语言:javascript
复制
@pytest.mark.parametrize("one", list_1)
@pytest.mark.parametrize("two", list_2)
def test_maybe_convert_objects(self, one, two):
    ...

改为:

代码语言:javascript
复制
@pytest.mark.parametrize("one", list_1)
def test_maybe_convert_objects(self, one):
    for two in list_2:
        ...

它类似于工厂,但更容易实现。此外,它不仅减少了RAM的多次,而且收集的时间,以及。这里的缺点-对于pytest,它将是对所有two值的一个测试。它与“简单”测试一起顺利工作--如果一个人的内部有一些特殊的xmark或者其他什么东西,那么可能会出现问题。

我还打开了相应的问题,可能会出现一些关于这个问题的额外信息/调整。

票数 3
EN

Stack Overflow用户

发布于 2020-06-12 16:05:02

编辑:我的第一个反应是“这正是参数化的夹具”:函数作用域的--在执行测试节点之前调用懒惰值,并且通过参数化夹具,您可以根据需要预定义任意数量的变量(例如,从数据库键列表中)。

代码语言:javascript
复制
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以启用另一种选择)

代码语言:javascript
复制
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)

注意,如果您希望自定义测试if,lazy_value也有一个id参数。默认情况是使用函数__name__,对部分函数的支持是在路上

您可以用同样的方式将固定装置参数化,但请记住,您必须使用@fixture_plus而不是@pytest.fixture。详情请参见文档

顺便说一下,我是pytest-cases的作者;)

票数 3
EN

Stack Overflow用户

发布于 2020-04-15 18:44:34

您可能会发现这个解决方法很有用:

代码语言:javascript
复制
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

样本输出:

代码语言:javascript
复制
========================================================================= 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
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/54163532

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档