pytest(6)-Fixture(固件)
什麼是固件
Fixture 翻譯成中文即是固件的意思。它其實就是一些函數,會在執行測試方法/測試函數之前(或之後)加載運行它們,常見的如接口用例在請求接口前數據庫的初始連接,和請求之後關閉數據庫的操作。
我們之前在APP UI自動化系列中已經介紹過unittest的相關測試固件,如setup、teardown等。而pytest中提供了功能更加豐富的 Fixture,用於實現setup、teardown功能。
定義方式
使用@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.py與test_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作用域分session、module、class、function四個級別。在定義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還有很多更加優雅的用法用於自動化測試項目中,本文只是以最簡單的示例進行說明。


