Pytest:需要缓慢初始化的参数化测试

时间:2021-07-06 16:06:49

标签: python pytest

我想使用非常慢的 init 方法对类的随机参数进行测试。测试本身非常快,但需要一个耗时的初始化步骤。 当然。我做这样的事情:

@pytest.mark.parametrize("params", LIST_OF_RANDOMIZED_PARAMS)
def test_one(params):
    state = very_slow_initialization(params)
    assert state.fast_test()

@pytest.mark.parametrize("params", LIST_OF_RANDOMIZED_PARAMS)
def test_two(params):
    state = very_slow_initialization(params)
    assert state.another_fast_test()

从迄今为止我不成功的尝试中我了解到:

  1. 不支持使用参数化的 set_class(params) 方法初始化测试类
  2. 使用初始化类的夹具仍然每次都调用缓慢的初始化
  3. 我可以提前创建一个包含所有初始化状态的列表,但是它们需要大量内存。此外,有时我喜欢在一夜之间进行大量随机测试,然后在第二天早上停止它们。这一点我需要确切地知道我应该进行多少次测试,以便在此之前完成所有初始化。
  4. 如果可能,我更喜欢一个解决方案,它对第一个参数运行两个测试,然后用第二个参数运行,依此类推。

可能有一个非常简单的解决方案。

2 个答案:

答案 0 :(得分:5)

pytest fixtures 是适合您的解决方案。夹具的生命周期可能是单个测试、类、模块或整个测试会话。

<块引用>

夹具管理从简单的单元扩展到复杂的功能测试,允许根据配置和组件选项参数化夹具和测试,或者跨功能、类、模块或整个测试会话范围重用夹具。

根据 Fixture availability paragraph,您需要在类或模块级别定义功能。

考虑使用模块范围的(注意,初始化只启动一次):

import pytest


@pytest.fixture(scope="module")
def heavy_context():
    # Use your LIST_OF_RANDOMIZED_PARAMS randomized parameters here
    # to initialize whatever you want.
    print("Slow fixture initialized")
    return ["I'm heavy"]


def test_1(heavy_context):
    print(f"\nUse of heavy context: {heavy_context[0]}")


def test_2(heavy_context):
    print(f"\nUse of heavy context: {heavy_context[0]}")

测试输出:

...
collecting ... collected 2 items

test_basic.py::test_1 Slow fixture initialized
PASSED                                             [ 50%]
Use of heavy context: I'm heavy

test_basic.py::test_2 PASSED                                             [100%]
Use of heavy context: I'm heavy

现在,如果您需要它是断言安全的(即使测试失败也释放资源),请考虑以上下文管理器的方式创建 heavy_context(更多详细信息请参见:Fixture, Running multiple assert statements safely):

import pytest


@pytest.fixture(scope="module")
def heavy_context():

    print("Slow context initialized")
    obj = ["I'm heavy"]

    # It is mandatory to put deinitialiation into "finally" scope
    # otherwise in case of exception it won't be executed
    try:
        yield obj[0]
    finally:
        print("Slow context released")


def test_1(heavy_context):
    # Pay attention, that in fact heavy_context now
    # is what we initialized as 'obj' in heavy_context
    # function.
    print(f"\nUse of heavy context: {heavy_context}")


def test_2(heavy_context):
    print(f"\nUse of heavy context: {heavy_context}")

输出:

collecting ... collected 2 items

test_basic.py::test_1 Slow context initialized
PASSED                                             [ 50%]
Use of heavy context: I'm heavy

test_basic.py::test_2 PASSED                                             [100%]
Use of heavy context: I'm heavy
Slow context released


============================== 2 passed in 0.01s ===============================

Process finished with exit code 0

答案 1 :(得分:1)

您是否可以在不再次初始化对象的情况下一个接一个地运行测试,例如:

@pytest.mark.parametrize("params", LIST_OF_RANDOMIZED_PARAMS)
def test_one(params):
    state = very_slow_initialization(params)
    assert state.fast_test()
    assert state.another_fast_test()

或为组织使用单独的功能:

@pytest.mark.parametrize("params", LIST_OF_RANDOMIZED_PARAMS)
def test_main(params):
    state = very_slow_initialization(params)

    step_one(state)
    step_two(state)

def step_one(state):
    assert state.fast_test()

def step_two(state):
    assert state.another_fast_test()

虽然它是一个测试脚本,但您仍然可以使用函数来组织您的代码。在具有单独功能的版本中,您甚至可以声明一个固定装置,以防其他测试中也可能需要该状态:

@pytest.fixture(scope="module", params=LIST_OF_RANDOMIZED_PARAMS)
def state(request):
    return very_slow_initialization(request.param)

def test_main(state):
    step_one(state)
    step_two(state)

def step_one(state):
    assert state.fast_test()

def step_two(state):
    assert state.another_fast_test()

我希望我没有在这里做错,但它应该像这样工作。