【pytest系列】- fixture測試夾具詳解

如果想從頭學起pytest,可以去看看這個系列的文章!

//www.cnblogs.com/miki-peng/category/1960108.html

fixture的優勢

​ pytest框架的fixture測試夾具就相當於unittest框架的setup、teardown,但相對之下它的功能更加強大和靈活。

  • 🍓 命名方式靈活,不限於unittest的setup、teardown
  • 🍑 可以實現數據共享,多個模組跨文件共享前置後置
  • 🍋 可以實現多個模組跨文件使用一個session來完成多個用例
  • 🍉 可以實現unittest不能實現的功能,比如unittest中的測試用例和測試用例之間是無法傳遞參數和數據的,但是fixture卻可以解決這個問題

fixture定義及調用

​ 關鍵程式碼:@pytest.fixture(),用於聲明函數是處理前置後置的測試夾具函數。用法如下:

@pytest.fixture()
def my_fixture():	# 記住這個夾具名
    print("我的測試夾具")

​ 測試夾具已經定義好了,那測試用例如何調用呢?有以下三種方式:

  • 🍋 方式一:將fixture名稱作為參數傳給測試用例,可以傳多個fixture,按先後順序執行
  • 🍋 方式二:裝飾器:@pytest.mark.usefixtures(fixture_name)
    • 使用在類上,代表這個類所有測試用例都會調用這個fixture
    • 使用在用例上,代表只有這個用例調用這個fixture
    • 同樣可以使用多個fixture,按先後順序執行
    • 如果fixture有返回值,用這個裝飾器是無法獲取到返回值的,也就無法給用例傳遞數據,只能用方法一
  • 🍋 方式三:fixture設置autouse=True自動調用,同樣fixture有返回值時,無法獲取返回值
import pytest


@pytest.fixture()
def my_fixture():	# 記住這個夾具名
    print("我的測試夾具")

# 方式一
def test_fix1(my_fixture):
    print("這是測試用例1111")
    print("-------分割線------")


# 方式二
# 類中應用
@pytest.mark.usefixtures("my_fixture")
class TestLogin:

    def test_fix2(self):
        print("這是測試用例2222")
        print("-------分割線------")

    def test_fix3(self):
        print("這是測試用例3333")
        print("-------分割線------")

# 測試用例應用
@pytest.mark.usefixtures("my_fixture")
def test_fix4():
    print("這是測試用例4444")
    print("-------分割線------")


# 方式三
def test_fix5():  # 未聲明使用測試夾具
    print("這是測試用例5555")


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

​ 運行結果:

Testing started at 23:12 ...
C:\software\python\python.exe ...

test.py 我的測試夾具
.這是測試用例1111
-------分割線------
我的測試夾具
.這是測試用例2222
-------分割線------
我的測試夾具
.這是測試用例3333
-------分割線------
我的測試夾具
.這是測試用例4444
-------分割線------
.這是測試用例5555
                                                              [100%]

============================== 5 passed in 0.02s ==============================

Process finished with exit code 0

​ 為什麼方式三沒有用到測試夾具呢,因為還沒有設置測試夾具自動調用。fixture裡面有個參數autouse(自動使用的意思),默認是False,當設置為True時,用例就會自動調用該fixture功能,這樣的話寫用例時就不用每次都去傳參了。

@pytest.fixture(autouse=True)	# 設置用例自動調用該fixture
def my_fixture():
    print("我的測試夾具")

fixture作用域

​ 在unittest中還有一個setUpClass和tearDownClass,是作用於類上,在每個測試用例類執行前去執行前置,用例類執行完再執行後置,pytest的fixture同樣有這樣的作用域,且使用更廣泛更靈活。

​ 關鍵程式碼:@pytest.fixture(scope='作用範圍'),參數如下:

  • 🍉 function:默認作用域,每個測試用例都運行一次
  • 🍉 class:每個測試類只執行一次
  • 🍉 module:每個模組只執行一次
  • 🍉 package:每個python包只執行一次
  • 🍉 session:整個會話只執行一次,即運行項目時整個過程只執行一次

優先順序session > package > module > class > function,優先順序高的會在低的fixture之前先實例化。

相同作用域的fixture遵循函數中聲明的先後順序,並遵循fixture之間的依賴關係,如在fixture_A裡面依賴的fixture_B優先實例化,然後再到fixture_A實例化

​ 我們前面舉例fixture的使用時都是使用的默認作用域,下面舉例一下作用域為class的場景:

# 因為用於演示,因此測試夾具直接寫在py文件中
import pytest
from selenium import webdriver


@pytest.fixture(scope='class')
def my_fixture():
    """前置條件"""
    print("前置條件-啟動瀏覽器")
    driver = webdriver.Chrome()
    yield driver
    driver.quit()
    print("後置條件-關閉瀏覽器")


class TestCase:

    def test_case01(self, my_fixture):    # 這裡通過參數傳入my_fixture函數,用例執行前會先去執行my_fixture
        driver = my_fixture    # my_fixture不需要加括弧
        driver.get('//www.baidu.com')
        print('第一個用例')
        assert 1 == 1

    def test_case02(self, my_fixture):    # 這裡通過參數傳入my_fixture函數,用例執行前會先去執行my_fixture
        driver = my_fixture    # my_fixture不需要加括弧
        driver.get('//www.cnblogs.com/')
        print('第二個用例')
        assert 1 == 1


if __name__ == '__main__':
    pytest.main(['test.py', '-s'])

​ 運行結果如下,從運行結果可以看出,整個類只打開了一次瀏覽器。

C:\software\python\python.exe D:/learn/test.py
============================= test session starts =============================
platform win32 -- Python 3.7.3, pytest-5.2.2, py-1.8.0, pluggy-0.13.0
rootdir: D:\learn
plugins: html-2.0.0, metadata-1.8.0
collected 2 items

test.py 前置條件-啟動瀏覽器
第一個用例
.第二個用例
.後置條件-關閉瀏覽器


============================== 2 passed in 9.76s ==============================

Process finished with exit code 0

​ 前面也提到了fixture的另外一個參數autouse,當autouse=True時,用例會自動執行測試夾具,但無法獲取fixture的返回值,如上示例返回的driver就無法傳遞給用例了。

​ 如果你想讓用例自動執行測試夾具且希望driver等參數可以返回給用例時,可以試一下在測試夾具中使用yield關鍵字返回值時把值存儲到臨時變數中,再讓用例去取臨時變數中的值,這裡不作舉例,以下是autouse=True的一個簡單示例:(yield關鍵字在後面的章節會講解)

# 因為用於演示,因此測試夾具直接寫在py文件中
import pytest
from selenium import webdriver


@pytest.fixture(scope='class', autouse=True)	# 所有類自動執行該測試夾具
def my_fixture():
    """前置條件"""
    print("前置條件-啟動瀏覽器")
    driver = webdriver.Chrome()
    yield driver
    driver.quit()
    print("後置條件-關閉瀏覽器")


class TestCase:

    def test_case01(self):	# 不需要傳入測試夾具
        print('第一個用例')
        assert 1 == 1

    def test_case02(self):
        print('第二個用例')
        assert 1 == 1


if __name__ == '__main__':
    pytest.main(['test.py', '-s'])