簡體   English   中英

如何在py.test中跨測試累積狀態

[英]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,另一個選項是modulescope=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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM