【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'])