如何在py.test中跨测试累积状态

Jas*_*mbs 4 python testing pytest

我目前有一个类似于这些的项目和测试.

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)
Run Code Online (Sandbox Code Playgroud)

使用py.test 0.9.2,这些测试(或类似测试)通过.对于更高版本的py.test,test_conversion和test_a_works_with_b失败并且'TestMyStuff没有属性a'.

我猜这是因为在py.test的后续版本中,为每个被测试的方法创建了一个单独的TestMyStuff实例.

编写这些测试的正确方法是什么,以便可以为序列中的每个步骤提供结果,但是可以(必须)使用先前(成功)测试的状态来执行后续测试?

Ned*_*der 8

良好的单元测试实践是避免测试中累积的状态.大多数单元测试框架都会竭尽全力阻止您积累状态.原因是你希望每个测试都独立存在.这使您可以运行测试的任意子集,并确保系统处于每个测试的干净状态.


hpk*_*k42 7

我部分同意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)
Run Code Online (Sandbox Code Playgroud)

您可以使用最近的py.test版本运行它.每个函数都接收一个"状态"对象,"funcarg"工厂最初创建它并将其缓存在模块范围内.与py.test一起保证测试按文件顺序运行,测试函数可以相反,它们将在测试"状态"上递增地工作.

但是,因为如果你只选择"test_conversion"通过如"py.test -k test_conversion"的运行则因为第一次测试还没有运行测试将失败这是一个有点脆弱.我认为进行增量测试的一些方法会很好,所以也许我们最终可以找到一个完全可靠的解决方案.

HTH,holger


Sam*_*art 7

这肯定是 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
Run Code Online (Sandbox Code Playgroud)

请注意,您可以指定依赖项注入的范围(以及状态)。在这种情况下,我已将其设置为会话,另一个选项是module(scope=function不适用于您的用例,因为您会在函数之间丢失状态。

显然,您可以扩展此模式以在状态中保存其他类型的对象,例如比较不同测试的结果。

作为警告 - 您仍然希望能够以任何顺序运行您的测试(我的示例违反了 this 交换 1 和 2 的顺序导致失败)。但是,为了简单起见,我没有说明。