[英]How to accumulate state across tests in py.test
我目前有一个类似于这些的项目和测试。
class mylib:
@classmethod
def get_a(cls):
return 'a'
@classmethod
def convert_a_to_b(cls, a):
return 'b'
@classmethod
def works_with(cls, a, b):
return True
class TestMyStuff(object):
def test_first(self):
self.a = mylib.get_a()
def test_conversion(self):
self.b = mylib.convert_a_to_b(self.a)
def test_a_works_with_b(self):
assert mylib.works_with(self.a, self.b)
使用py.test 0.9.2,这些测试(或类似测试)通过。 对于更高版本的py.test,test_conversion和test_a_works_with_b失败并且'TestMyStuff没有属性a'。
我猜这是因为在py.test的后续版本中,为每个被测试的方法创建了一个单独的TestMyStuff实例。
编写这些测试的正确方法是什么,以便可以为序列中的每个步骤提供结果,但是可以(必须)使用先前(成功)测试的状态来执行后续测试?
良好的单元测试实践是避免测试中累积的状态。 大多数单元测试框架都会竭尽全力阻止您积累状态。 原因是你希望每个测试都独立存在。 这使您可以运行测试的任意子集,并确保系统处于每个测试的干净状态。
我部分同意Ned的观点,即避免在某种程度上随机分享测试状态是件好事。 但我也认为在测试期间逐步累积状态有时是有用的。
使用py.test,您可以通过明确表示您想要共享测试状态来实现这一点。 您的示例重写为工作:
class State:
""" holding (incremental) test state """
def pytest_funcarg__state(request):
return request.cached_setup(
setup=lambda: State(),
scope="module"
)
class mylib:
@classmethod
def get_a(cls):
return 'a'
@classmethod
def convert_a_to_b(cls, a):
return 'b'
@classmethod
def works_with(cls, a, b):
return True
class TestMyStuff(object):
def test_first(self, state):
state.a = mylib.get_a()
def test_conversion(self, state):
state.b = mylib.convert_a_to_b(state.a)
def test_a_works_with_b(self, state):
mylib.works_with(state.a, state.b)
您可以使用最近的py.test版本运行它。 每个函数都接收一个“状态”对象,“funcarg”工厂最初创建它并将其缓存在模块范围内。 与py.test一起保证测试按文件顺序运行,测试函数可以相反,它们将在测试“状态”上递增地工作。
但是,它有点脆弱,因为如果您通过例如“py.test -k test_conversion”选择仅运行“test_conversion”,那么您的测试将失败,因为第一个测试尚未运行。 我认为进行增量测试的一些方法会很好,所以也许我们最终可以找到一个完全可靠的解决方案。
HTH,holger
这肯定是pytest装置的工作: https ://docs.pytest.org/en/latest/fixture.html
Fixtures允许测试功能轻松接收和处理特定的预先初始化的应用程序对象,而无需关心导入/设置/清理细节。 这是依赖注入的一个主要示例,其中夹具功能扮演注入器的角色,测试功能是夹具对象的消费者。
因此,设置夹具以保持状态的示例如下:
import pytest
class State:
def __init__(self):
self.state = {}
@pytest.fixture(scope='session')
def state() -> State:
state = State()
state.state['from_fixture'] = 0
return state
def test_1(state: State) -> None:
state.state['from_test_1'] = 1
assert state.state['from_fixture'] == 0
assert state.state['from_test_1'] == 1
def test_2(state: State) -> None:
state.state['from_test_2'] = 2
assert state.state['from_fixture'] == 0
assert state.state['from_test_1'] == 1
assert state.state['from_test_2'] == 2
请注意,您可以指定依赖项注入的范围(以及状态)。 在这种情况下,我将其设置为session,另一个选项是module
( scope=function
不适用于您的用例,因为您将失去函数之间的状态。
显然,您可以扩展此模式以保存状态中的其他类型的对象,例如比较来自不同测试的结果。
作为一个警告 - 你仍然希望能够以任何顺序运行你的测试(我的例子违反了这个交换1和2的顺序导致失败)。 但是为了简单起见,我没有说明这一点。
为了补充hpk42的答案 ,您还可以使用pytest-steps来执行增量测试,如果您希望在步骤之间共享某种增量状态/中间结果,这可以帮助您。
使用这个包你不需要把所有的步骤放在一个类中(你可以,但不是必需的),只需用@test_steps
装饰你的“测试套件”功能。
编辑:有一个新的'发电机'模式,使它更容易:
from pytest_steps import test_steps
@test_steps('step_first', 'step_conversion', 'step_a_works_with_b')
def test_suite_with_shared_results():
a = mylib.get_a()
yield
b = mylib.convert_a_to_b(a)
yield
assert mylib.works_with(a, b)
yield
LEGACY回答:
如果要在步骤之间共享StepsDataHolder
对象,可以向测试函数添加steps_data
参数。
然后你的例子会写:
from pytest_steps import test_steps, StepsDataHolder
def step_first(steps_data):
steps_data.a = mylib.get_a()
def step_conversion(steps_data):
steps_data.b = mylib.convert_a_to_b(steps_data.a)
def step_a_works_with_b(steps_data):
assert mylib.works_with(steps_data.a, steps_data.b)
@test_steps(step_first, step_conversion, step_a_works_with_b)
def test_suite_with_shared_results(test_step, steps_data: StepsDataHolder):
# Execute the step with access to the steps_data holder
test_step(steps_data)
最后,请注意,如果其他人使用@depends_on
失败,您可以自动跳过或失败一个步骤,请查看文档以获取详细信息。
(顺便说一下,我是这个包的作者;))
当我花更多时间研究这个问题时,我意识到我的问题有一个隐含的方面,我忽略了。 在大多数情况下,我发现我想在一个类中累积状态,但在测试类完成时丢弃它。
我最终用于某些类,其中类本身表示一个累积状态的进程,我将累积状态存储在类对象本身中。
class mylib:
@classmethod
def get_a(cls):
return 'a'
@classmethod
def convert_a_to_b(cls, a):
return 'b'
@classmethod
def works_with(cls, a, b):
return True
class TestMyStuff(object):
def test_first(self):
self.__class__.a = mylib.get_a()
def test_conversion(self):
self.__class__.b = mylib.convert_a_to_b(self.a)
def test_a_works_with_b(self):
mylib.works_with(self.a, self.b)
这种方法的优点是它保持封装在测试类中的状态(没有必须存在的辅助函数来运行测试),并且对于不同的类来说期望TestMyStuff状态是合适的尴尬当不同的班级运行时出现。
我认为这样讨论的每种方法都有它们的优点,并且打算在最适合的地方使用每种方法。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.