Pytest:需要缓慢初始化的参数化测试
Pytest: parametrizing tests that require a slow initialization
我想用非常慢的 init 方法对 class 的随机参数进行测试。测试本身非常快,但需要一个耗时的初始化步骤。
当然。我这样做:
@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()
从我迄今为止不成功的尝试中我了解到:
- 不支持使用参数化 set_class(params) 方法初始化测试class
- 使用初始化 class 的夹具每次仍然调用缓慢的初始化
- 我可以预先创建一个包含所有初始化状态的列表,但是它们需要大量内存。此外,有时我喜欢在一夜之间运行大量随机测试,然后在第二天早上停止它们。这我需要准确地知道我应该进行多少次测试,以便所有初始化在此之前完成。
- 如果可能的话,我更喜欢对第一个参数运行两个测试,然后对第二个参数运行两个测试的解决方案,依此类推。
可能有一个非常简单的解决方案。
你能不能运行一个接一个地测试而不用再次初始化对象,例如:
@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()
我希望我没有在这里犯错,但它应该是这样的。
pytest fixtures 是适合您的解决方案。夹具的生命周期可能是单个测试、class、模块或整个测试会话。
fixture management scales from simple unit to complex functional testing, allowing to parametrize fixtures and tests according to configuration and component options, or to re-use fixtures across function, class, module or whole test session scopes.
根据 Fixture availability paragraph,您需要在 class 或模块级别定义功能。
考虑使用模块范围的(注意,初始化只启动一次):
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
我想用非常慢的 init 方法对 class 的随机参数进行测试。测试本身非常快,但需要一个耗时的初始化步骤。 当然。我这样做:
@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()
从我迄今为止不成功的尝试中我了解到:
- 不支持使用参数化 set_class(params) 方法初始化测试class
- 使用初始化 class 的夹具每次仍然调用缓慢的初始化
- 我可以预先创建一个包含所有初始化状态的列表,但是它们需要大量内存。此外,有时我喜欢在一夜之间运行大量随机测试,然后在第二天早上停止它们。这我需要准确地知道我应该进行多少次测试,以便所有初始化在此之前完成。
- 如果可能的话,我更喜欢对第一个参数运行两个测试,然后对第二个参数运行两个测试的解决方案,依此类推。
可能有一个非常简单的解决方案。
你能不能运行一个接一个地测试而不用再次初始化对象,例如:
@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()
我希望我没有在这里犯错,但它应该是这样的。
pytest fixtures 是适合您的解决方案。夹具的生命周期可能是单个测试、class、模块或整个测试会话。
fixture management scales from simple unit to complex functional testing, allowing to parametrize fixtures and tests according to configuration and component options, or to re-use fixtures across function, class, module or whole test session scopes.
根据 Fixture availability paragraph,您需要在 class 或模块级别定义功能。
考虑使用模块范围的(注意,初始化只启动一次):
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