pytest(6)-Fixture(韌體)

給你一頁白紙發表於2022-02-15

什麼是韌體

Fixture 翻譯成中文即是韌體的意思。它其實就是一些函式,會在執行測試方法/測試函式之前(或之後)載入執行它們,常見的如介面用例在請求介面前資料庫的初始連線,和請求之後關閉資料庫的操作。

我們之前在APP UI自動化系列中已經介紹過 unittest 的相關測試韌體,如setupteardown等。而 pytest 中提供了功能更加豐富的Fixture,用於實現setupteardown功能。

定義方式

使用@pytest.fixture()進行定義,簡單示例如下:

import pytest

@pytest.fixture()
def before():
    print("連線資料庫")

呼叫方式

呼叫單個fixture函式

  • 方式一,使用fixture函式名作為引數

    import pytest
    
    @pytest.fixture()
    def before():
        print("連線資料庫")
    
    
    # 呼叫before
    def test_01(before):
        print("執行test_01")
    
  • 方式二,使用 @pytest.mark.usefixtures('fixture函式名')裝飾器

    import pytest
    
    @pytest.fixture()
    def before():
        print("連線資料庫")
    
    # 呼叫before
    @pytest.mark.usefixtures('before')
    def test_01():
        print("執行test_01")
    
  • 方式三,使用autouse引數自動執行fixture函式

    import pytest
    
    # fixture函式定義的時候使用autouse引數,作用域範圍內的測試用例會自動呼叫該fixture函式
    @pytest.fixture(autouse=True)
    def before():
        print("連線資料庫")
    
        
    # 自動呼叫before
    def test_01():
        print("執行test_01")
    

三種方式呼叫後的結果都如下:

我們可以看到,先執行了fixture函式,再執行測試函式。

呼叫多個fixture函式

import pytest

@pytest.fixture()
def before():
    print("連線資料庫")

@pytest.fixture()
def before_s():
    print("初始化資料")


def test_01(before, before_s):
    print("執行test_01")

呼叫多個 fixture 函式時,由前至後依次執行,所以test_01()呼叫時先執行before,再執行before_s

對fixture函式重新命名

定義fixture函式時,可以利用name引數進行重新命名,方便用於呼叫,示例如下:

import pytest

@pytest.fixture(name='db')
def connect_order_db():
    print("連線資料庫")


def test_01(db):
    print("執行test_01")

使用fixture傳遞測試資料

在執行完fixture函式後,有時需要將該fixture中得到到某些資料傳遞給測試函式/測試方法,用於後續的執行。

fixture中提供普通傳遞和引數化傳遞兩種資料傳遞方式。

普通傳遞

示例如下:

import pytest

@pytest.fixture()
def before():
    print("連線資料庫")
    return "連線成功!"


def test_01(before):
    print("執行test_01")
    assert before == "連線成功!"

注意,如果自定義的fixture函式有返回值,需要使用上面說的方式一呼叫才能獲取fixture函式的返回值並傳入測試函式中,方式二就無法獲取返回值。

引數化傳遞

fixture函式進行引數化時,需要使用引數params,並且需要傳入引數request,簡單示例如下:

import pytest

test_params = [1, 2, 0]
@pytest.fixture(params=test_params)
def before(request):
    result = request.param
    return result

def test_02(before):
    print("執行test_02")
    assert before


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

執行結果:

可以看到,因為所呼叫的fixture函式進行了引數化,雖然只有一個測試函式但執行了3次。

conftest.py

上面我們舉的例子都是把fixture函式放在測試用例模組裡面,但如果很多測試模組需要引用同一個fixture函式怎麼辦,這是時候就需要把它放在命名為conftest的模組裡,這樣同級或以下目錄中的測試用例便能呼叫這些自定義的fixture函式。

例如,有如下目錄:

├─testcase
│  │
│  ├─test_module_01
│  │      test_case_1.py
│  │      test_case_2.py
│  │
│  ├─test_module_02
│  │      test_case_3.py

test_module_01 中的test_case_1.pytest_case_2.py都需要呼叫同一個 fixture 函式,那麼我們只需要在 test_module_01 中新建conftest.py並編寫這個fixture函式即可,示例如下:

├─testcase
│  │
│  ├─test_module_01
│  │      conftest.py
│  │      test_case_1.py
│  │      test_case_2.py
│  │
│  ├─test_module_02
│  │      test_case_3.py

conftest.py:

import pytest

@pytest.fixture(autouse=True)
def before():
    print("連線資料庫")

test_case_1.py

def test_01():
    print("執行test_01")

test_case_2.py

def test_02():
    print("執行test_02")

這樣,執行這兩個模組的測試用例時會自動先去呼叫conftest.py中的before()函式。

假設 test_module_02 中的 test_case_3.py 也需要呼叫這個before()函式,那麼這個時候我們就需要在上一層即 testcase 中新建conftest.py並編寫這個before()函式,才能在 test_case_3.py 中呼叫,如下:

├─testcase
│  │  conftest.py
│  │
│  ├─test_module_01
│  │      conftest.py
│  │      test_case_1.py
│  │      test_case_2.py
│  │
│  ├─test_module_02
│  │      test_case_3.py

conftest.py只作用於同級以下目錄中的測試模組,且需要注意,當以下層級中存在了另一個conftest.py,那麼以下層級將由另一個conftest.py檔案接管。

作用域

pytest 的 fixture 作用域分sessionmoduleclassfunction四個級別。在定義 fixture 函式的時候通過scope引數指定作用範圍,預設為function

  • session,每次會話執行一次
  • module,每個測試模組執行一次
  • class,每個測試類執行一次
  • function,每個測試方法執行一次

注意,對於單獨定義的測試函式,class、function 都會起作用,可以從下列示例中看出來。

測試目錄結構如下:

├─apiAutoTest
│  │  run.py
│  │
│  ├─testcase
│  │  │  conftest.py
│  │  │
│  │  ├─test_module_02
│  │  │  │  conftest.py
│  │  │  │  test_case_3.py
│  │  │  │  test_case_4.py

其中conftest.py程式碼如下:

import pytest

@pytest.fixture(scope="session", autouse=True)
def session_fixture():
    print("這是一個作用於session的fixture")

@pytest.fixture(scope="module", autouse=True)
def module_fixture():
    print("這是一個作用於module的fixture")

@pytest.fixture(scope="class", autouse=True)
def class_fixture():
    print("這是一個作用於class的fixture")

@pytest.fixture(scope="function", autouse=True)
def function_fixture():
    print("這是一個作用於function的fixture")

test_case_3.py程式碼如下:

import pytest

class TestOrder:

    def test_a(self):
        print("test_a")
        
    def test_b(self):
        print("test_b")

def test_c():
    print("test_c")

test_case_4.py程式碼如下:

def test_e():
    print("test_e")

run.py程式碼如下:

import pytest

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

執行run.py,結果如下:

collected 4 items

testcase\test_module_02\test_case_3.py 
這是一個作用於session的fixture
這是一個作用於module的fixture
這是一個作用於class的fixture
這是一個作用於function的fixture
test_a
.這是一個作用於function的fixture
test_b
.這是一個作用於class的fixture
這是一個作用於function的fixture
test_c
.
testcase\test_module_02\test_case_4.py 
這是一個作用於module的fixture
這是一個作用於class的fixture
這是一個作用於function的fixture
test_e
.

============================== 4 passed in 0.04s ==============================

從結果可以看出來:

  • 作用於session的fixture函式只在所有測試用例執行之前呼叫了一次
  • 作用於module的fixture函式在每個測試模組執行之前呼叫了一次
  • 作用於class的fixture函式在每個測試類執行之前呼叫了一次
  • 作用於function的fixture函式在每個測試方法/測試函式執行之前呼叫了一次

注意,在定義的測試函式(如test_c()test_e())執行之前也會呼叫scope=class的fixture函式。

總結

與 unittest 框架比較,pytest 中的Fixture更加豐富,可擴充套件性更高。

Fixture還有很多更加優雅的用法用於自動化測試專案中,本文只是以最簡單的示例進行說明。

相關文章