pytest(7)-yield與終結函數


通過上一篇文章,我們已經知道了pytest中,可以使用Fixture來完成運行測試用例之前的一些操作如連接數據庫,以及測試執行之后自動去做一些善后工作如清空臟數據、關閉數據庫連接等。

我們已經學會了fixture函數的簡單用法,但其實fixture還提供了兩種非常優雅高效的寫法,來完成測試執行前的處理操作與執行后的處理操作,即使用yieldaddfinalizer來實現。

yield

在fixture中的關鍵字yield主要有兩個作用:

  • yield代替return進行參數的傳遞
  • 起到代碼的分割作用,yield之前的代碼為setup的作用,yield之后的代碼為teardown的作用

yield 與 return

在 pytest 的fixture函數中可以使用yield代替return進行返回,示例如下:

import pytest

@pytest.fixture(autouse=True)
def fixture_one():
    print("執行fixture_one")
    yield 1
    
def test_e(fixture_one):
    print("執行test_e")
    print(fixture_one)
    assert fixture_one == 1


if __name__ == '__main__':
    pytest.main(["-s"])

運行結果如下:

test_case_4.py::test_e 
執行fixture_one
PASSED                                            [100%]執行test_e
1


============================== 1 passed in 0.12s ==============================

從運行結果我們能看到fixture_one會返回1並傳遞給test_e,與return的作用完全一致。但如果僅僅只是這樣使用的話,毫無意義,因為使用return足夠了。所以,在實際的使用過程中我們一般會在yield后面加上teardown的代碼。

yield 與 teardown

yield不進行參數傳遞

對於不需要在前置操作中返回數據的 fixture 函數,加入yield,那么yield之前的代碼為用例執行之前的操作(即setup),yield之后的代碼為用例執行之后的操作(即teardown)。示例如下:

import pytest

@pytest.fixture()
def fixture_demo():
    # setup
    print("\n連接數據庫")
    yield
    # teardown
    print("清空臟數據")

def test_case(fixture_demo):
    print("執行test_case")
    assert True


if __name__ == '__main__':
    pytest.main(["-s"])

運行結果如下:

從結果中我們可以看出來,先執行了setup部分,再執行測試用例,最后執行teardown部分。

yield進行參數傳遞

yield可以將參數傳遞給測試用例。

假設有這樣一個場景,需要用到接口1的返回參數作為接口2的請求參數,即接口2依賴接口1,我們需要寫一條測試用例對接口2進行測試,這個時候可以將接口1的請求寫在前置中,如果是unittest框架則代碼如下:

import unittest
import requests

class TestDemo(unittest.TestCase):

    def setup(self):
        print("請求接口1")
        self.res_1 = requests.get(url=url_1, params=params_1)

    def test_api_2(self):
        print("驗證接口2")
        # 將接口1的返回值self.res_1作為請求參數,請求接口2
        res = requests.post(url=url_2, data=self.res_1)
        # 斷言
        self.assertEqual(res, "接口2預期的返回結果")

    def teardown(self):
        print("清空臟數據")

pytest框架中使用fixture+yield則可編寫如下:

@pytest.fixture()
def get_api_1_result():
    # setup
    res_1 = requests.get(url=url_1, params=params_1)
    yield res_1
    # teardown
    print("清空臟數據")
    

def test_api_2(get_api_1_result):
    print("驗證接口2")
    # 將接口1的返回值res_1作為請求參數,請求接口2
    res = requests.post(url=url_2, data=get_api_1_result)
    # 斷言
    assert res == "接口2預期的返回結果"

其中,fixture 會先通過yield返回res_1,並傳入測試用例test_api_2中,test_api_2運行完成后再去執行yield后面的代碼,即執行print("清空臟數據")

通過以上對比unittestsetupteardown以及參數的傳遞,我們就能很直觀的看出pytestyield的使用方式,此處代碼僅為示例。

yield 的執行順序

有時候我們會遇到一個fixture函數調用另一個或多個fixture函數,且這些函數中可能含有yield,我們先看示例,代碼如下:

import pytest

@pytest.fixture
def fixture_1():
    print("\n執行fixture_1")
    yield 1
    print("\n執行fixture_1的teardown代碼")

@pytest.fixture
def fixture_2(fixture_1):
    print("\n執行fixture_2")
    yield 2
    print("\n執行fixture_2的teardown代碼")

@pytest.fixture
def fixture_add(fixture_1, fixture_2):
    print("\n執行fixture_add")
    result = fixture_1 + fixture_2
    yield result
    print("\n執行fixture_add的teardown代碼")

    
def test_demo(fixture_add):
    print("\n執行測試函數test_demo")
    assert fixture_add == 3


if __name__ == '__main__':
    pytest.main(["-s"])

運行結果如下:

rootdir: E:\blog\python接口自動化\apiAutoTest, configfile: pytest.ini
plugins: html-2.1.1, metadata-1.10.0, ordering-0.6, rerunfailures-9.1.1
collecting ... collected 1 item

test_case_4.py::test_demo 
執行fixture_1

執行fixture_2

執行fixture_add
PASSED                                         [100%]
執行測試函數test_demo

執行fixture_add的teardown代碼

執行fixture_2的teardown代碼

執行fixture_1的teardown代碼


============================== 1 passed in 0.12s ==============================

從結果可以看出:

test_demo 測試函數執行之前:先執行了 fixture_1,再執行fixture_2,最后執行fixture_add,注意此時都是執行yield之前的的代碼;

test_demo 測試函數執行之后:先執行了 fixture_add,再執行fixture_2,最后執行fixture_1,注意此時都是執行yield之后的的代碼。

因此,當一個fixture函數調用另一個或多個fixture函數,且fixture函數中含有yield時,被測試函數調用時有如下執行順序:

  • 測試函數執行之前,pytest會根據fixture函數之間的線性關系順序調用,即依次執行yield之前的代碼

  • 而測試函數執行結束后,pytest會根據之前的順序反方向執行fixture函數中yield之后的代碼

finalizer

finalizer即終結器 (終結函數),與unittest中的teardown作用一樣,測試用例執行完成后再執行終結器代碼。

在pytest中,fixture除了使用 yield 進行 teardown 之外,還可以使用request.addfinalizer()定義finalizer來進行后置操作。

使用addfinalizer,需要在定義 fixture 函數時傳入request,並以內嵌函數的形式進行定義。終結函數可以定義一個或多個。

定義單個終結函數

示例如下:

import pytest

@pytest.fixture
def fixture_demo(request):
    print("\nsetup:每個case開始前執行一次")

    # 定義終結函數
    def finalizer_demo():
        print("\nteardown:每個case完成后執行一次")

    # 將finalizer_demo注冊為終結函數
    request.addfinalizer(finalizer_demo)


def test_2(fixture_demo):
    print("\n執行test_2")

def test_1(fixture_demo):
    print("\n執行test_1")


if __name__ == '__main__':
    pytest.main()

運行結果如下:

rootdir: E:\blog\python接口自動化\apiAutoTest, configfile: pytest.ini
plugins: html-2.1.1, metadata-1.10.0, ordering-0.6, rerunfailures-9.1.1
collecting ... collected 2 items

test_module_02\test_case_3.py::test_2 
setup:每個case開始前執行一次
PASSED                             [ 50%]
執行test_2

teardown:每個case完成后執行一次

test_module_02\test_case_3.py::test_1 
setup:每個case開始前執行一次
PASSED                             [100%]
執行test_1

teardown:每個case完成后執行一次


============================== 2 passed in 0.03s ==============================

從結果可以看出來,在測試用例執行完后會執行addfinalizer函數,效果與執行yield后的代碼一致。

定義多個終結函數

示例如下:

import pytest

@pytest.fixture
def fixture_demo(request):
    print("\nsetup:每個case開始前執行一次")

    # 定義終結函數
    def finalizer_demo_1():
        print("\nteardown1:每個case完成后執行一次")

    def finalizer_demo_2():
        print("\nteardown2:每個case完成后執行一次")

    # 注冊為終結函數
    request.addfinalizer(finalizer_demo_1)
    request.addfinalizer(finalizer_demo_2)


def test_2(fixture_demo):
    print("\n執行test_2")

def test_1(fixture_demo):
    print("\n執行test_1")


if __name__ == '__main__':
    pytest.main()

運行結果如下:

rootdir: E:\blog\python接口自動化\apiAutoTest, configfile: pytest.ini
plugins: html-2.1.1, metadata-1.10.0, ordering-0.6, rerunfailures-9.1.1
collecting ... collected 2 items

test_module_02\test_case_3.py::test_2 
setup:每個case開始前執行一次
PASSED                             [ 50%]
執行test_2

teardown2:每個case完成后執行一次

teardown1:每個case完成后執行一次

test_module_02\test_case_3.py::test_1 
setup:每個case開始前執行一次
PASSED                             [100%]
執行test_1

teardown2:每個case完成后執行一次

teardown1:每個case完成后執行一次


============================== 2 passed in 0.02s ==============================

從結果可以看出,上面示例中測試函數執行完成后,先執行了finalizer_demo_2,后執行finalizer_demo_1

所以, 當有多個終結函數被執行時,執行順序與注冊順序是相反的

總結

實際項目中,可以視情況進行選擇,但一般情況下,推薦使用yield,因為這樣代碼更加簡潔高效,且閱讀性更強更容易維護。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM