天天看點

pytest的fixture的詳細使用 (更靈活進階的前/後置處理方法)

一、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()      
pytest的fixture的詳細使用 (更靈活進階的前/後置處理方法)

 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()進行疊加,先執行的放底層,後執行的放上層

pytest的fixture的詳細使用 (更靈活進階的前/後置處理方法)

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互相調用的情況時,執行個體化順序會很複雜讓人頭疼:(使用需謹慎)

pytest的fixture的詳細使用 (更靈活進階的前/後置處理方法)
pytest的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

可以先畫出繼承關系圖,這樣就會很明了,按着等級去找就對了:

pytest的fixture的詳細使用 (更靈活進階的前/後置處理方法)
在執行個體化基本規則大前提下,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,不支援裝飾器傳參      

代碼:

pytest的fixture的詳細使用 (更靈活進階的前/後置處理方法)
pytest的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

pytest的fixture的詳細使用 (更靈活進階的前/後置處理方法)

 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      

代碼:

pytest的fixture的詳細使用 (更靈活進階的前/後置處理方法)
pytest的fixture的詳細使用 (更靈活進階的前/後置處理方法)
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

pytest的fixture的詳細使用 (更靈活進階的前/後置處理方法)

 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      

代碼:

pytest的fixture的詳細使用 (更靈活進階的前/後置處理方法)
pytest的fixture的詳細使用 (更靈活進階的前/後置處理方法)
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

pytest的fixture的詳細使用 (更靈活進階的前/後置處理方法)

 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()      
pytest的fixture的詳細使用 (更靈活進階的前/後置處理方法)

2.ids

ids通常可以與params一起使用,在沒有指定 id情況下,在輸出時 pytest 會自動生成一個辨別作為測試ID:

  • 當params清單項是數字、字元串、布爾值和None時,将使用清單項自身字元串形式表示測試ID,如[True1] [True2] [xxoo] [123] [None]等
  • 對于其他對象,pytest會根據參數名稱建立一個字元串,如params中截圖顯示的[test_data0] [test_data1]
  • 可以通過使用​

    ​ids​

    ​關鍵字參數來自定義用于指定測試ID,例如@pytest.fixture(param=tasks_list,ids=task_ids) , ids可以是清單,也可以是函數供 pytest 生成 task 辨別。
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()      
pytest的fixture的詳細使用 (更靈活進階的前/後置處理方法)

3.autouse

預設False不開啟

當用例很多的時候,每次都傳fixture,會很麻煩。fixture裡面有個參數autouse,預設是False沒開啟的,可以設定為True開啟自動使用fixture功能,這樣用例就不用每次都去傳參了

autouse設定為True,自動調用fixture功能,無需傳仍何參數,作用範圍跟着scope走(謹慎使用)

pytest的fixture的詳細使用 (更靈活進階的前/後置處理方法)
pytest的fixture的詳細使用 (更靈活進階的前/後置處理方法)
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

pytest的fixture的詳細使用 (更靈活進階的前/後置處理方法)
pytest的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傳入,函數名不再生效
pytest的fixture的詳細使用 (更靈活進階的前/後置處理方法)

 四、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()      
pytest的fixture的詳細使用 (更靈活進階的前/後置處理方法)

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()      
pytest的fixture的詳細使用 (更靈活進階的前/後置處理方法)

參考:​

pytest的fixture的詳細使用 (更靈活進階的前/後置處理方法)

輝輝輝輝a

小鳳梨