一、fixture基本操作介紹
雖然pytest在unittest的兩組前置後置方法方法基礎上,提供了更全面的總共五組的前置後置方法,但這些方法都是針對各自對應的整個作用域全局生效的,
如果有以下場景:用例 1 需要先登入,用例 2 不需要登入,用例 3 需要先登入。很顯然無法用 setup 和 teardown 來實作了
pytest架構的精髓fixture可以讓我們随心所欲的定制測試用例的前置後置方法
fixture是pytest将測試前後進行預備、清理工作的代碼分離出核心測試邏輯的一種機制
1.基本形式和用法:
@pytest.fixture() 裝飾器用于聲明函數是一個fixture,該fixture的名字預設為函數名,也可以自己指定名稱(詳見參數name解釋)
如果測試用例的參數清單中包含fixture的名字,那麼pytest會根據名字檢測到該fixture,并在測試函數運作之前執行該fixture
fixture可以完成測試任務,也可以傳回資料給測試用例
import pytest
@pytest.fixture(scope="function", params=None, autouse=False, ids=None, name=None)
def func_01():
print("這就定義了一個簡單的fixture")
return '結果'
def test_01(func_01):
print(type(func_01), func_01) # 結果
if __name__ == '__main__':
pytest.main()
2.檢測順序:
檢測順序是:目前測試類 > 子產品(.py檔案)> 目前包中conftest.py > 父包中conftest.py > 根目錄中conftest.py
3.存放位置
- 可以放在測試用例自己的測試檔案裡
- 如果希望多個測試檔案共享fixture,可以放在某個公共目錄下建立一個conftest.py 點選檢視,将fixture放在裡面
4.fixture調用方式
注意事項: 如果不是測試用例,無論哪種調用方式都不會生效(參考func_006) 未傳入fixture,不會執行仍何fixture
1)參數傳參:
将fixture函數名當參數傳入用例(函數名無引号)
支援多個,支援fixture互相調用時給fixture傳參
傳回值:fixture執行完畢将傳回值指派給用例參數名,無傳回值預設為None
2)裝飾器傳參:
支援多個,不支援fixture互相調用時給fixture傳參
傳回值:不能擷取
第一種:傳入名字,@pytest.mark.usefixtures("fixture1", "fixture2") (字元串格式,帶引号的)
第二種:多個可以使用@pytest.mark.usefixture()進行疊加,先執行的放底層,後執行的放上層
View Code
3)autouse=True,自動調用,詳見下一節: 其他參數使用
5.fixture執行個體化順序
- 進階别scope的fixture在低級别scope的fixture之前執行個體化(session > package > module > class > function)
- 具有相同scope的fixture遵循測試函數中聲明的順序
- 遵循fixture之間的依賴關系【在fixture_A裡面依賴的fixture_B優先執行個體化,然後到fixture_A執行個體化】
- (autouse=True)自動使用的fixture将在顯式使用(傳參或裝飾器)的fixture之前執行個體化
上面的規則是基本前提,當遇到不同級别fixture互相調用的情況時,執行個體化順序會很複雜讓人頭疼:(使用需謹慎)
import pytest
order = []
@pytest.fixture(scope="session")
def s1():
order.append("s1")
@pytest.fixture(scope="session")
def s2():
order.append("s2")
@pytest.fixture(scope="session")
def s3():
order.append("s3")
@pytest.fixture(scope="session")
def s4():
order.append("s4")
@pytest.fixture(scope="session")
def s5(s7):
order.append("s5")
@pytest.fixture(scope="session")
def s6():
order.append("s6")
@pytest.fixture(scope="session")
def s7():
order.append("s7")
@pytest.fixture(scope="module")
def m1():
order.append("m1")
@pytest.fixture(scope="module")
def m2(s5):
order.append("m2")
@pytest.fixture(scope="module")
def m3(s4):
order.append("m3")
@pytest.fixture
def f1(s2, f3):
order.append("f1")
@pytest.fixture
def f2(m2, s3):
order.append("f2")
@pytest.fixture
def f3(s6):
order.append("f3")
def test_order(f2, f1, m3, m1, s1):
print(order) # ['s1', 's3', 's2', 's4', 's7', 's5', 's6', 'm3', 'm1', 'm2', 'f2', 'f3', 'f1']
if __name__ == '__main__':
pytest.main()
View Code
可以先畫出繼承關系圖,這樣就會很明了,按着等級去找就對了:
在執行個體化基本規則大前提下,fixture可能存在不同等級之間的互相調用,這就存在依賴深度等級,如圖:
1.所有的session級必定最先執行,接下來是确定該級别fixture的先後順序:
1)先運作第一等級的session級别,運作s1
2)第二等級按照傳入(f2, f1, m3, m1, s1)傳入順序依次查找,得到 s3 > s2 > s4
3)第三等級按照傳入(f2, f1, m3, m1, s1)傳入順序依次查找,得到 (s5依賴于同scope級别s7)s7 > s5 > s6
2.接下來運作module級别
1)第一等級按照傳入順序 m3 > m1
2)第二等級按照傳入(f2, f1, m3, m1, s1)傳入順序依次查找,得到 m2
3.運作function級别
1)第一等級按照傳入順序 f2 > f3 > f1 (f1依賴于同scope級别f3)
走完以上流程得到最終結果: ['s1', 's3', 's2', 's4', 's7', 's5', 's6', 'm3', 'm1', 'm2', 'f2', 'f3', 'f1']
二、scope參數詳解(fixture的作用範圍)
fixture裡面scope參數可以控制fixture的作用範圍:session > module > class > function(預設)
fixture可互相調用,但要注意:如果級别不同,低級别可以調用進階别,進階别不能調用低級别
- function:每一個調用了該fixture的函數或方法執行前都會執行一次,在測試用例執行之後運作銷毀代碼
- class:每一個類隻調用一次,一個類中可以有多個用例調用,但是隻在最早運作的用例之前調用一次(每一個類外函數用例也都會調用一次)
- module:每一個.py檔案調用一次,該子產品内最先執行的調用了該fixture的用例執行前運作且隻運作一次
- session:是多個檔案調用一次,可以跨.py檔案調用,每個.py檔案就是module
1.scope=function 函數級(預設級别)
最基本的fixture
1.執行時機
每一個調用了該fixture的函數或方法執行前都會執行一次,在測試用例執行之後運作銷毀代碼
2.fixture可互相調用(參考test_002的login_and_logout)
1)fixture可以像測試用例的參數傳參一樣調用其他的fixture,并擷取其傳回值
2)多個fixture的執行順序和測試用例調用的情況是一樣的
3)隻支援參數傳入fixture,不支援裝飾器傳參
代碼:
import pytest
@pytest.fixture
def login():
print("打開浏覽器")
return 'chrome'
@pytest.fixture()
def logout():
print("關閉浏覽器")
@pytest.fixture()
def login_and_logout(logout, login): # fixture的互相調用,支援調用多個,執行順序:loguot > login > login_and_logout
print(f"先打開{login},又關閉了它")
return f'{login} + {logout}'
def test_005(login): # 第一種傳參:fixture的名字作為參數傳參=============================================================================
print(f'005: login={login}')
class TestLogin:
def test_001(self, login):
print(f"001: login={login}")
def test_003(self, login, logout): # 支援傳多個,執行順序按照傳入順序:login > logout > test_003
print(f"003: login={login} logout={logout}")
def test_002(self, login_and_logout):
print(f"002: login_and_logout={login_and_logout}")
def test_004(self):
print("004:未傳入,不會執行仍何fixture")
@pytest.mark.usefixtures("login", "logout") # 第二種傳參:裝飾器傳參,傳入fixture的str,這種不能擷取傳回值==============
def test_005(self):
print(f"005: 這樣傳參無法擷取到fixture的傳回值")
@pytest.mark.usefixtures("login", "logout")
def func_006(self):
print("006:不是測試用例,加了裝飾器也不會執行fixture")
if __name__ == '__main__':
pytest.main()
View Code
2.scope=class 類級别
1.調用方式:
和function一樣
2.運作時機:
1)類外的獨立函數用例執行前會執行一次,參考test_006
2)類中有用例調用了fixture,則會在最先調用的那個用例執行前執行一次(參考test_002),該類下其他調用了fixture的用例不再執行(參考test_003)
3)未調用該fixture的類和函數,不會運作
2.fixture互相調用規則
1)類級可以調用類級,參考test_004
2)函數級可以調用類級,參考test_008
3)類級不可以調用函數級,參考test_007
代碼:
import pytest
@pytest.fixture(scope='class')
def login():
print("打開浏覽器 -- 類級别fixture")
return 'chrome'
@pytest.fixture()
def logout():
print("關閉浏覽器 -- 函數級fixture")
@pytest.fixture(scope='class')
def class_use_class(login):
print(f"class_use_class -- 類級可以調用類級")
return f'{login}'
@pytest.fixture()
def function_use_class(login):
print(f"function_use_class -- 函數級調用類級")
@pytest.fixture(scope='class')
def class_use_function(logout):
print(f"錯誤示範,類級不可以調用函數級")
def test_006(login):
print(f'006: login={login}')
class TestLogin:
def test_001(self, logout):
print(f"001: login={logout} 調用普通函數級")
def test_002(self, login):
print(f"002:logout={login} 調用類級,該類中login隻會在這裡運作一次")
def test_003(self, login, logout):
print(f"003: login={login} logout={logout} 調用類級和函數級,該類中類級login不會再運作")
@pytest.mark.usefixtures("class_use_class")
def test_004(self, class_use_class):
print(f"004: class_use_class 調用類級-->類再調用類級login,運作過了不會再運作")
# def test_004(self, class_use_class):
# print(f"004: class_use_class={class_use_class} 調用類級-->類再調用類級login,運作過了不會再運作")
def test_007(self, class_use_function):
print(f"007: class_use_function={class_use_function} 錯誤示範,類級不可以調用函數級")
def test_008(self, function_use_class):
print(f"008: function_use_class 調用函數級-->函數級再調用類級login,運作過了不會再運作")
def test_005(self):
print(f"005: 未傳入任何fixture,哪個級别都與我無關")
class TestNoFixture:
def test_009(self):
print('009:未傳入任何fixture,哪個級别都與我無關')
if __name__ == '__main__':
pytest.main()
View Code
3.scope=module 子產品級
1.調用方式:
和function一樣
2.運作時機:
每一個.py檔案調用一次,該子產品内最先執行的調用了該fixture的用例執行前運作且隻運作一次(如test_006 + test_001 + test_003)
2.fixture互相調用規則
1)類級可以調用子產品級,參考test_005
2)函數級可以調用子產品級,參考test_008
3)子產品級不可以調用函數級,和類級參考 test_004 test_007
代碼:
import pytest
@pytest.fixture(scope='module')
def open():
print("打開電腦 -- 子產品級别fixture")
return 'windows'
@pytest.fixture(scope='class')
def login():
print("打開浏覽器 -- 類級别fixture")
return 'chrome'
@pytest.fixture()
def logout():
print("關閉浏覽器 -- 函數級fixture")
@pytest.fixture(scope='module')
def module_use_class(login):
print(f"錯誤示範,子產品級不可以調用類級")
return f'{login}'
@pytest.fixture(scope='module')
def module_use_func(logout):
print(f"錯誤示範,子產品級不可以調用函數級")
return f'{logout}'
@pytest.fixture()
def function_use_module(open):
print(f"function_use_module -- 函數級調用子產品級")
@pytest.fixture(scope='class')
def class_use_module(open):
print(f"class_use_module -- 類級調用子產品級")
def test_006(login):
print(f'006: login={login}')
class TestLogin:
def test_001(self, open):
print(f"001: open={open} 調用子產品級")
def test_002(self, login):
print(f"002:login={login} 調用類級")
def test_003(self, open, login, logout):
print(f"003: open={open} login={login} logout={logout} 調用子產品級、類級和函數級")
@pytest.mark.usefixtures("module_use_class")
def test_004(self):
print(f"004: module_use_class 錯誤示範,子產品級不能調用類級")
def test_007(self, module_use_func):
print(f"007: module_use_func 錯誤示範,子產品級不能調用函數級 ")
def test_008(self, function_use_module):
print(f"008: function_use_module")
def test_005(self, class_use_module):
print(f"005: class_use_module")
class TestNoFixture:
def test_009(self):
print('009:未傳入任何fixture,哪個級别都與我無關')
if __name__ == '__main__':
pytest.main()
View Code
4.scope=session
fixture為session級别是可以跨.py子產品調用的,運作一次程式隻會調用一次
也就是當我們有多個.py檔案的用例的時候,如果多個用例隻需調用一次fixture,那就可以設定為scope="session",并且寫到conftest.py檔案裡作為全局的fixture
conftest.py 點選檢視檔案名稱時固定的,pytest會自動識别該檔案。
放到項目的根目錄下就可以全局調用了,如果放到某個package下,那就在該package内有效
三、其他參數介紹
1.params
一個可選的參數清單,它将導緻被fixture裝飾的測試用例以清單中每個清單項為參數,多次調用fixture功能
1.fixture可以帶參數,params支援清單;
2.預設是None;
3.對于param裡面的每個值,fixture都會去調用執行一次,就像執行for循環一樣把params裡的值周遊一次。
在 pytest 中有一個内建的 fixture 叫做 request,代表 fixture 的調用狀态。request 有一個字段 param,使用類似
@pytest.fixture(param=tasks_list)
的方式給fixture傳參,在 fixture 中使用
request.param
的方式作為傳回值供測試函數調用。其中 tasks_list 包含多少元素,該 fixture 就會被調用幾次,分别作用在每個用到的測試函數上
如下fixture和測試用例都執行三遍相當三個測試用例,場景如:測試三組賬戶密碼登入,登陸用例都是一個隻是每次資料不同
import pytest
tasks_list = [(10,11),(20,22),(33,33)]
@pytest.fixture(params=tasks_list)
def test_data(request):
print(f"fixture得到:賬号:{request.param[0]},密碼:{request.param[1]}" )
return request.param
class TestData:
def test_1(self,test_data):
print("用例:",test_data)
if __name__ == '__main__':
pytest.main()
2.ids
ids通常可以與params一起使用,在沒有指定 id情況下,在輸出時 pytest 會自動生成一個辨別作為測試ID:
- 當params清單項是數字、字元串、布爾值和None時,将使用清單項自身字元串形式表示測試ID,如[True1] [True2] [xxoo] [123] [None]等
- 對于其他對象,pytest會根據參數名稱建立一個字元串,如params中截圖顯示的[test_data0] [test_data1]
- 可以通過使用
關鍵字參數來自定義用于指定測試ID,例如@pytest.fixture(param=tasks_list,ids=task_ids) , ids可以是清單,也可以是函數供 pytest 生成 task 辨別。ids
import pytest
data_list = [(10,11),(20,22),(33,33)]
@pytest.fixture(params=data_list, ids=["a","b","c"])
def tes_data(request):
print(f"fixture得到:賬号:{request.param[0]},密碼:{request.param[1]}" )
return request.param
class TestData:
def test_1(self,tes_data):
print("用例:",tes_data)
if __name__ == '__main__':
pytest.main()
3.autouse
預設False不開啟
當用例很多的時候,每次都傳fixture,會很麻煩。fixture裡面有個參數autouse,預設是False沒開啟的,可以設定為True開啟自動使用fixture功能,這樣用例就不用每次都去傳參了
autouse設定為True,自動調用fixture功能,無需傳仍何參數,作用範圍跟着scope走(謹慎使用)
import pytest
@pytest.fixture(scope='module', autouse=True)
def test1():
print('開始執行module')
@pytest.fixture(scope='class', autouse=True)
def test2():
print('開始執行class')
@pytest.fixture(scope='function', autouse=True)
def test3():
print('開始執行function')
def test_a():
print('---用例a執行---')
def test_d():
print('---用例d執行---')
class TestCase:
def test_b(self):
print('---用例b執行---')
def test_c(self):
print('---用例c執行---')
自動使用fixture
========================================================================================================================= test session starts ==========================================================================================================================
platform win32 -- Python 3.8.0, pytest-6.2.5, py-1.11.0, pluggy-1.0.0 -- E:\python3.8\python.exe
cachedir: .pytest_cache
rootdir: D:\代碼\自動化測試\pytest_test, configfile: pytest.ini, testpaths: ./dir01/dir01_test.py
collected 4 items
dir01/dir01_test.py::test_a
開始執行module
開始執行class
開始執行function
---用例a執行---
PASSED
dir01/dir01_test.py::test_d
開始執行class
開始執行function
---用例d執行---
PASSED
dir01/dir01_test.py::TestCase::test_b
開始執行class
開始執行function
---用例b執行---
PASSED
dir01/dir01_test.py::TestCase::test_c
開始執行function
---用例c執行---
PASSED
================================================================================================================================ PASSES ================================================================================================================================
執行結果
4.name
- fixture的重命名
- 預設為 fixture 裝飾的的函數名,但是 pytest 也允許将fixture重命名
- 如果使用了name,隻能将name傳入,函數名不再生效
四、fixture的teardown後置操作
1.使用yield實作
前幾章中都是前置操作,後置操作需要用到python的 yield來實作,yield文法講解點這裡:>>疊代器生成器<<
- 如果yield前面的代碼,即setup部分已經抛出異常了,則不會執行yield後面的teardown内容
- 如果測試用例抛出異常,yield後面的teardown内容還是會正常執行
import pytest
@pytest.fixture(scope="session")
def open():
# 整個session前置操作setup
print("===打開浏覽器===")
test = "測試變量是否傳回"
yield test
# 整個session後置操作teardown
print("==關閉浏覽器==")
@pytest.fixture
def login(open):
# 方法級别前置操作setup
print(f"輸入賬号,密碼先登入{open}")
name = "==我是賬号=="
pwd = "==我是密碼=="
age = "==我是年齡=="
# 傳回變量
yield name, pwd, age
# 方法級别後置操作teardown
print("登入成功")
def test_s1(login):
print("==用例1==")
# 傳回的是一個元組
print(login)
# 分别指派給不同變量
name, pwd, age = login
print(name, pwd, age)
assert "賬号" in name
assert "密碼" in pwd
assert "年齡" in age
def test_s2(login):
print("==用例2==")
print(login)
if __name__ == '__main__':
pytest.main()
2.使用request.addfinalizer終結函數實作
yield是傳回資料并暫停,後置操作在yield後面。
終結函數是用return傳回,終結函數的後置操作處于前置和return中間。
- 如果request.addfinalizer()前面的代碼,即setup部分已經抛出異常了,則不會執行request.addfinalizer()的teardown内容(和yield相似,應該是最近新版本改成一緻了)
- 可以聲明多個終結函數并調用
import pytest
@pytest.fixture(scope="module")
def test_addfinalizer(request):
# 前置操作setup
print("==打開浏覽器==")
test = "test_addfinalizer"
def fin():
# 後置操作teardown
print("==關閉浏覽器==")
request.addfinalizer(fin)
# 傳回前置操作的變量
return test
def test_anthor(test_addfinalizer):
print("==最新用例==", test_addfinalizer)
if __name__ == '__main__':
pytest.main()
參考:
輝輝輝輝a
小鳳梨