一、韌體使用背景
在執行測驗用例時,我們常常需要在測驗用例執行的前后去完成一些額外的操作,例如針對于 Web 測驗,在用例執行前需要打開瀏覽器,完成用戶登錄等一系列前置操作;在用例執行完成后,要清除瀏覽器快取,關閉瀏覽器...... Pytest 框架提供的韌體機制(又稱為夾具)可以幫我們實作一系列的前后置操作,
我們先創建一套測驗用例:

二、前后置函式
1. 測驗方法級別
setup 和 teardown 方法作用于每一個測驗方法,每個測驗方法執行之前都會先去執行 setup 方法,執行之后都會再去執行 teardown 方法,
1 # cases/test_cases.py 2 class TestCase: 3 ? 4 def setup(self): 5 print("\n測驗方法執行前做對應的操作!!!") 6 ? 7 def teardown(self): 8 print("\n測驗方法執行后做對應的操作!!!") 9 ? 10 def test_case_001(self): 11 print("模擬測驗案例001") 12 ? 13 def test_case_002(self): 14 print("模擬測驗案例002") 15 ? 16 def test_case_003(self): 17 print("模擬測驗案例003")

需要注意的是:
-
如果前后置方法是定義在測驗類內部的,那么作用的物件是當前測驗類中的每一個測驗方法,其他測驗類和外部的測驗方法將不會被影響,
1 # cases/test_cases.py 2 class TestCase: 3 ? 4 def setup(self): 5 print("\n測驗方法執行前做對應的操作!!!") 6 ? 7 def teardown(self): 8 print("\n測驗方法執行后做對應的操作!!!") 9 ? 10 def test_case_001(self): 11 print("模擬測驗案例001") 12 ? 13 def test_case_002(self): 14 print("模擬測驗案例002") 15 ? 16 def test_case_003(self): 17 print("模擬測驗案例003") 18 ? 19 ? 20 class TestCase2: 21 def test_case_004(self): 22 print("模擬測驗案例004") 23 ? 24 def test_case_005(self): 25 print("模擬測驗案例005") 26 ? 27 ? 28 def test_outer_case(): 29 print("模擬測驗外部的測驗方法")

-
如果前后置方法是定義在測驗類外部的,那么作用的物件是定義在外部的測驗方法,測驗類中的測驗方法不會被影響,
1 # cases/test_cases.py 2 def setup(): 3 print("\n測驗方法執行前做對應的操作!!!") 4 ? 5 ? 6 def teardown(): 7 print("\n測驗方法執行后做對應的操作!!!") 8 ? 9 class TestCase: 10 ? 11 def test_case_001(self): 12 print("模擬測驗案例001") 13 ? 14 def test_case_002(self): 15 print("模擬測驗案例002") 16 ? 17 def test_case_003(self): 18 print("模擬測驗案例003") 19 ? 20 ? 21 class TestCase2: 22 def test_case_004(self): 23 print("模擬測驗案例004") 24 ? 25 def test_case_005(self): 26 print("模擬測驗案例005") 27 ? 28 ? 29 def test_outer_case(): 30 print("模擬測驗外部的測驗方法")

2. 測驗類級別
setup_class 和 teardown_class 方法作用于當前的測驗類,每個測驗類執行之前都會先去執行 setup_class 方法,執行之后都會再去執行 teardown_class 方法,測驗方法并不會受到這兩個方法的影響,
1 # cases/test_cases.py 2 class TestCase: 3 ? 4 def test_case_001(self): 5 print("模擬測驗案例001") 6 ? 7 def test_case_002(self): 8 print("模擬測驗案例002") 9 ? 10 def test_case_003(self): 11 print("模擬測驗案例003") 12 ? 13 ? 14 class TestCase2: 15 def setup_class(self): 16 print("\n測驗方法執行前做對應的操作!!!") 17 ? 18 def teardown_class(self): 19 print("\n測驗方法執行后做對應的操作!!!") 20 ? 21 def test_case_004(self): 22 print("模擬測驗案例004") 23 ? 24 def test_case_005(self): 25 print("模擬測驗案例005")

三、裝飾器實作
使用前后置函式的方式作用的物件都是一個模塊內或者是一個測驗類中的全體物件,沒有辦法做到只作用于部分物件,Pytest 提供了 @pytest.fixture() 方法來實作部分用例的前后置操作,
1. 簡單使用
-
第一步,先自定義一個生成器方法
1 def my_fixture(): 2 print("前置操作") 3 yield 4 print("后置操作")
-
第二步,添加裝飾器方法
1 import pytest 2 ? 3 @pytest.fixture() 4 def my_fixture(): 5 print("前置操作") 6 yield 7 print("后置操作")
-
第三步,將函式名作為引數,傳遞給需要做前后置操作的測驗方法,測驗方法在執行前會先去執行生成器函式中
yield的前半部分代碼;測驗方法執行完成后,會去執行生成器函式中yield的后半部分代碼,
1 # cases/test_cases.py 2 import pytest 3 ? 4 @pytest.fixture() 5 def my_fixture(): 6 print("前置操作") 7 yield 8 print("后置操作") 9 ? 10 class TestCase: 11 ? 12 def test_case_001(self, my_fixture): 13 print("模擬測驗案例001") 14 ? 15 def test_case_002(self): 16 print("模擬測驗案例002") 17 ? 18 def test_case_003(self): 19 print("模擬測驗案例003")

2. 相關引數詳解
2.1 autouse
值為 True 時,表示韌體自動執行,即不需要在對應的測驗方法中參考也會觸發,默認值為 False
1 # cases/test_cases.py 2 import pytest 3 ? 4 @pytest.fixture(autouse=True) 5 def my_fixture(): 6 print("\n前置操作") 7 yield 8 print("\n后置操作") 9 ? 10 ? 11 class TestCase: 12 # 并未傳入韌體使用 13 def test_case_001(self): 14 print("模擬測驗案例001") 15 ? 16 def test_case_002(self): 17 print("模擬測驗案例002") 18 ? 19 def test_case_003(self): 20 print("模擬測驗案例003")

2.2 scope
表示的是被 @pytest.fixture 標記的方法的作用域,有以下幾個值:
-
function:作用于測驗方法級別,每個函式或方法都會呼叫
1 # cases/test_cases.py 2 import pytest 3 4 @pytest.fixture(scope="function", autouse=True) 5 def my_fixture(): 6 print("\n前置操作") 7 yield 8 print("\n后置操作") 9 10 11 class TestCase: 12 13 def test_case_001(self): 14 print("模擬測驗案例001") 15 16 def test_case_002(self): 17 print("模擬測驗案例002") 18 19 def test_case_003(self): 20 print("模擬測驗案例003")

-
class:作用于測驗類級別,測驗類執行時會執行一次韌體
1 # cases/test_cases.py 2 import pytest 3 4 @pytest.fixture(scope="class", autouse=True) 5 def my_fixture(): 6 print("\n前置操作") 7 yield 8 print("\n后置操作") 9 10 class TestCase: 11 12 def test_case_001(self): 13 print("模擬測驗案例001") 14 15 def test_case_002(self): 16 print("模擬測驗案例002") 17 18 def test_case_003(self): 19 print("模擬測驗案例003") 20 21 class TestCase2: 22 23 def test_case_004(self): 24 print("模擬測驗案例004") 25 26 def test_case_005(self): 27 print("模擬測驗案例005")

-
module:作用于測驗模塊,測驗模塊(即
py檔案)執行時會執行一次韌體

-
session:作用于會話,可以跨多個
.py檔案,若多個模塊中的用例都呼叫了 fixture,只會運行一次
1 ####################### cases/test_cases.py ######################## 2 import pytest 3 4 @pytest.fixture(scope="session", autouse=True) 5 def my_fixture(): 6 print("\n前置操作") 7 yield 8 print("\n后置操作") 9 10 class TestCase: 11 12 def test_case_001(self): 13 print("模擬測驗案例001") 14 15 def test_case_002(self): 16 print("模擬測驗案例002") 17 18 def test_case_003(self): 19 print("模擬測驗案例003") 20 21 ####################### cases/test_cases_2.py ############################## 22 class TestCase2: 23 24 def test_case_004(self): 25 print("模擬測驗案例004") 26 27 def test_case_005(self): 28 print("模擬測驗案例005")

2.3 params
使用裝飾器方的韌體,還可以進行引數傳遞,引數的型別支持以下四種:
-
串列
-
元組
-
字典串列:[{},{},{}]
-
字典元組:({},{},{})
我們先列印一下測驗方法中接收的韌體名的值是啥
1 import pytest 2 3 @pytest.fixture(scope="function") 4 def my_fixture(): 5 print("一些操作......") 6 7 class TestCase: 8 9 def test_case_001(self, my_fixture): 10 print(f"模擬測驗案例001----{my_fixture}") 11 12 def test_case_002(self): 13 print("模擬測驗案例002") 14 15 def test_case_003(self): 16 print("模擬測驗案例003")

在韌體中我們嘗試回傳一個值試試:
1 import pytest 2 3 @pytest.fixture(scope="function") 4 def my_fixture(): 5 print("一些操作......") 6 return "success" 7 8 class TestCase: 9 10 def test_case_001(self, my_fixture): 11 print(f"模擬測驗案例001----{my_fixture}") 12 13 def test_case_002(self): 14 print("模擬測驗案例002") 15 16 def test_case_003(self): 17 print("模擬測驗案例003")

可見在測驗方法中傳入韌體名,除了可以執行韌體對應的操作,還可以拿到韌體的回傳值,那么想結合 params 引數在韌體中傳值就十分容易了:
1 import pytest 2 3 @pytest.fixture(scope="function", params=["aaa", "bbb", "ccc"]) 4 def my_fixture(request): # 固定寫法,使用引數時必須接收一個request變數 5 print("一些操作......") 6 return request.param # 固定寫法,回傳引數 7 8 class TestCase: 9 10 def test_case_001(self, my_fixture): 11 print(f"模擬測驗案例001----{my_fixture}") 12 13 def test_case_002(self): 14 print("模擬測驗案例002") 15 16 def test_case_003(self): 17 print("模擬測驗案例003")

被標記的測驗方法被執行了三次,是因為傳的引數有三個值,每執行一次就會傳一個值過去,
如果韌體要執行前后置操作,就不能用 return 回傳值了,要使用 yield:
1 import pytest 2 3 @pytest.fixture(scope="function", params=["aaa", "bbb", "ccc"]) 4 def my_fixture(request): # 固定寫法,使用引數時必須接收一個request變數 5 print("前置操作......") 6 yield request.param # 固定寫法,回傳引數 7 print("后置操作......") 8 9 class TestCase: 10 11 def test_case_001(self, my_fixture): 12 print(f"模擬測驗案例001----{my_fixture}") 13 14 def test_case_002(self): 15 print("模擬測驗案例002") 16 17 def test_case_003(self): 18 print("模擬測驗案例003")
2.4 ids
當韌體使用 params 進行傳值時,給每一個引數值設定一個單獨的 id(意義不是很大)
1 import pytest 2 3 @pytest.fixture(scope="function", params=["aaa", "bbb", "ccc"], ids=["parm_1", "parm_2", "parm_3"]) 4 def my_fixture(request): # 固定寫法,使用引數時必須接收一個request變數 5 print("前置操作......") 6 yield request.param # 固定寫法,回傳引數 7 print("后置操作......") 8 9 class TestCase: 10 11 def test_case_001(self, my_fixture): 12 print(f"模擬測驗案例001----{my_fixture}") 13 14 def test_case_002(self): 15 print("模擬測驗案例002") 16 17 def test_case_003(self): 18 print("模擬測驗案例003")

2.5 name
給標記的韌體方法起一個別名,后續傳參都使用該別名,
1 import pytest 2 3 @pytest.fixture(scope="function", name="init") 4 def my_fixture(): 5 print("前置操作......") 6 yield 7 print("后置操作......") 8 9 class TestCase: 10 # 測驗方法中傳參不再是韌體函式的名字,而是別名 11 def test_case_001(self, init): 12 print(f"模擬測驗案例001") 13 14 def test_case_002(self): 15 print("模擬測驗案例002") 16 17 def test_case_003(self): 18 print("模擬測驗案例003")
注意:一旦使用別名,就不能再使用原來的函式名作為引數傳遞了,否則會報錯,
1 import pytest 2 3 @pytest.fixture(scope="function", name="init") 4 def my_fixture(): 5 print("前置操作......") 6 yield 7 print("后置操作......") 8 9 class TestCase: 10 # 韌體方法起了別名init,但是測驗方法中仍然使用原韌體函式名my_fixture作為引數傳參 11 def test_case_001(self, my_fixture): 12 print(f"模擬測驗案例001") 13 14 def test_case_002(self): 15 print("模擬測驗案例002") 16 17 def test_case_003(self): 18 print("模擬測驗案例003")

四、conftest.py 實作
為了避免代碼的冗余,測驗用例公共的前后置操作往往會抽離出來而不是重復寫在每個用例之中,在 pytest 中,全域公共的前后置操作要求寫在一個名為 conftest.py 的檔案中,該檔案主要有以下特點:
-
conftest.py檔案是單獨存放的一個夾具組態檔,且檔案名不能更改,必須是這個名字; -
conftest.py常常和fixture裝飾器聯合使用,實作測驗專案用例全域的前后置操作,且定義在conftest.py中的韌體函式可以被不同的 py 檔案參考; -
原則上,
conftest.py檔案需要和執行的用例放在同一層級上(實際上也可以放到其他目錄中),且不需要做任何的import操作,
下面我們就用幾個實際的示例來感受以下 conftest.py 的強大:
首先,我們創建一套測驗用例環境:

我們先忽略conftest.py ,還是按照一開始使用裝飾器定義韌體的方式創建用例:
1 ############# product/test_product.py ############# 2 import pytest 3 4 @pytest.fixture(name="init_product") 5 def init(): 6 print("\n產品測驗前置操作....") 7 yield 8 print("\n產品測驗后置操作....") 9 10 def test_product_case(init_product): 11 print("模擬產品測驗......") 12 13 14 ############# user/test_user.py ################## 15 import pytest 16 17 @pytest.fixture(name="init_user") 18 def init(): 19 print("\n用戶測驗前置操作....") 20 yield 21 print("\n用戶測驗后置操作....") 22 23 def test_user_case(init_user): 24 print("模擬用戶測驗......")
此時,我們嘗試在兩個測驗用例里面分別呼叫對方用例中的韌體函式:
1 ############# product/test_product.py ############# 2 import pytest 3 4 @pytest.fixture(name="init_product") 5 def init(): 6 print("\n產品測驗前置操作....") 7 yield 8 print("\n產品測驗后置操作....") 9 10 def test_product_case(init_product,init_user): 11 print("模擬產品測驗......") 12 13 14 ############# user/test_user.py ################## 15 import pytest 16 17 @pytest.fixture(name="init_user") 18 def init(): 19 print("\n用戶測驗前置操作....") 20 yield 21 print("\n用戶測驗后置操作....") 22 23 def test_user_case(init_user,init_product): 24 print("模擬用戶測驗......")

結果是顯而易見的,由于韌體不是定義在當前的 py 檔案中的,跨 py 檔案參考且不匯入,肯定報錯,接下來我們將每個模塊的韌體分別遷移到各自的 conftest.py 下:
1 ############# product/conftest.py ############# 2 import pytest 3 4 @pytest.fixture(name="init_product") 5 def init(): 6 print("\n產品測驗前置操作....") 7 yield 8 print("\n產品測驗后置操作....") 9 10 11 ############# product/test_product.py ############# 12 def test_product_case(init_product): 13 print("模擬產品測驗......") 14 15 16 17 ############# user/conftest.py ################## 18 import pytest 19 20 @pytest.fixture(name="init_user") 21 def init(): 22 print("\n用戶測驗前置操作....") 23 yield 24 print("\n用戶測驗后置操作....") 25 26 27 ############# user/test_user.py ################## 28 def test_user_case(init_user): 29 print("模擬用戶測驗......")

執行用例通過,可見測驗用例在執行時會去自己模塊下的 conftest.py 中尋找對應的韌體執行,當然,此時想要呼叫對方的韌體還是沒法呼叫的:
1 ############# product/test_product.py ############# 2 def test_product_case(init_product, init_user): 3 print("模擬產品測驗......") 4 5 6 ############# user/test_user.py ################## 7 def test_user_case(init_user, init_product): 8 print("模擬用戶測驗......")

要想實作可以全域呼叫,可以將兩個模塊中的韌體方法遷移到根目錄下的 conftest.py 中:
1 # 根目錄下的 conftest.py 2 import pytest 3 4 @pytest.fixture(name="init_product") 5 def product_init(): 6 print("\n產品測驗前置操作....") 7 yield 8 print("\n產品測驗后置操作....") 9 10 11 @pytest.fixture(name="init_user") 12 def user_init(): 13 print("\n用戶測驗前置操作....") 14 yield 15 print("\n用戶測驗后置操作....")
此時,我們嘗試在兩個測驗用例里面分別呼叫對方用例中的韌體函式:
1 ############# product/test_product.py ############# 2 def test_product_case(init_product, init_user): 3 print("模擬產品測驗......") 4 5 6 ############# user/test_user.py ################## 7 def test_user_case(init_user, init_product): 8 print("模擬用戶測驗......")

執行用例通過,且韌體執行的順序取決于韌體名引數在測驗方法中傳參的位置,先傳入的就先執行,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/545404.html
標籤:Python
