【pytest】(十)fixture引數化-巧用params和ids優雅的建立測試資料
我們都知道引數化。
比如我要測試一個查詢介面/test/get_goods_list
,這個介面可以查詢到商品的資訊。
在請求中,我可以根據請引數goods_status的不同傳值,可以查詢到對應狀態的商品資料,比如:1-未銷售
、2-銷售中
、3-已售罄
。
那麼在編寫自動化測試case的時候,在斷言裡就要分別驗證到這3種狀態的商品資料。
通常,在執行case之前,會去資料庫分別插入對應狀態的商品資料,來滿足測試需求。
而在pytest框架中,我喜歡用fixture()
去實現測試資料的準備和清理工作。
於是,2種實現方式瞬間出現:
- 寫3個case,只有傳參不一樣。對應寫3個fixture來分別初始化3種狀態的商品資料。
- 寫1個case,在case裡用
@pytest.mark.parametrize()
進行引數化。只寫一個fixture一次性的插入3種狀態的商品資料。
在這2個方法裡,顯然第二種更優雅,避免了case的冗餘程式碼。
但是一把梭的插入所有的測試資料還是差點意思,如果我只想執行其中的某一個數據的case,那麼其他2個不必要的資料也生成了。
所以,我想要的樣子是,可以自由的根據執行的case的引數,去對應的初始化測試資料。
具體點的描述就是:引數化裡3個引數,我只執行2-銷售中
的時候,只去插入2-銷售中
這一種狀態的資料。
網上搜的都是簡單的fixture引數化的東西,達不到我想要的需求。於是乎我自己去翻閱官方文件,發現可以用fixture中的params和ids
這2個引數去實現我的需求。
一、fixture中的params
params
是一個列表,用來存放我們要引數化的值。
舉例:這裡的程式碼放了引數1
和引數2
這2個引數,2個測試case,都會用params
裡的引數去分別執行2次。
import pytest @pytest.fixture(params=['引數1', '引數2']) def my_fixture(request): return request.param def test_fixtures_01(my_fixture): print('\n 執行test_fixtures_01') print(my_fixture) def test_fixtures_02(my_fixture): print('\n 執行test_fixtures_02') print(my_fixture)
執行一下:
collecting ... collected 4 items
test_ids.py::test_fixtures_01[\u53c2\u65701] PASSED [ 25%]
執行test_fixtures_01
引數1
test_ids.py::test_fixtures_01[\u53c2\u65702] PASSED [ 50%]
執行test_fixtures_01
引數2
test_ids.py::test_fixtures_02[\u53c2\u65701] PASSED [ 75%]
執行test_fixtures_02
引數1
test_ids.py::test_fixtures_02[\u53c2\u65702] PASSED [100%]
執行test_fixtures_02
引數2
============================== 4 passed in 0.03s ==============================
Process finished with exit code 0
二、fixture中的ids
ids
也是要結合著params
一起使用的。當有多個 params
時,針對每一個 param
,可以指定一個id
,
然後,這個 id
會變成測試用例名字的一部分。如果沒有提供 id
,則 id
將自動生成。
import pytest
@pytest.fixture(params=['引數1', '引數2'], ids=["id-01", "id-02"])
def my_fixture(request):
return request.param
def test_fixtures_01(my_fixture):
print('\n 執行test_fixtures_01')
print(my_fixture)
執行下,結果裡case名稱後分別帶了 id:[id-01]
和[id-02]
collecting ... collected 2 items
test_ids.py::test_fixtures_01[id-01] PASSED [ 50%]
執行test_fixtures_01
引數1
test_ids.py::test_fixtures_01[id-02] PASSED [100%]
執行test_fixtures_01
引數2
============================== 2 passed in 0.02s ==============================
Process finished with exit code 0
三、利用ids實現需求
ids的賦值除了上述的方式之外,還有一種,也就是幫我實現需求的一種,直接看程式碼。
import pytest
def init_data(fixture_value):
if fixture_value == 1:
return "未銷售"
elif fixture_value == 2:
return "銷售中"
elif fixture_value == 3:
return "已售罄"
@pytest.fixture(params=[1, 2, 3], ids=init_data)
def my_method(request):
req_param = request.param
print("\n引數為:【{}】,執行sql--插入【{}】狀態的資料".format(req_param, req_param))
yield req_param
print("\n執行sql--清理引數為【{}】的測試資料".format(req_param, req_param))
print("\n----------------------------------------")
def test_01(my_method):
print("\n正在執行【{}】的case--".format(my_method))
if __name__ == '__main__':
pytest.main(['-s', '-v', 'test_my_fixture.py::test_01'])
示例程式碼就沒有去真正的寫一個介面的case了,因為太(lan)忙了,所以直接用print()打印出我要做的事兒。
上述程式碼中,重點就是3個部分:
test_01()
,這是測試casemy_method()
這是我定義的fixture函式init_data()
這個是用來初始化測試資料的函式
1、test_01()
測試case沒什麼說的,一般來說介面的case裡的組成就是:傳參
、呼叫測試介面
、斷言
。
case裡傳入了my_method
函式,這是呼叫fixture
的一種方式。
def test_01(my_method):
print("\n正在執行[{}]的case--".format(my_method))
2、my_method()
我定義了my_method這個fixture去進行case執行之前的測試資料處理。
@pytest.fixture(params=[1, 2, 3], ids=init_data)
def my_method(request):
req_param = request.param
print("\n引數為:{}".format(req_param))
yield req_param
print("\n執行sql--清理引數為【{}】的測試資料".format(req_param))
print("\n----------------------------------------")
params=[1,2,3]
就是相當於我介面請求體裡查詢不同狀態商品的資料對應的引數,1-未銷售
、2-銷售中
、3-已售罄
。
ids
在上面單獨介紹的例子中是直接賦值的,但是在這裡,我是把一個函式賦給了它,這個函式就是init_data()
。
當然了,為了滿足後面我的指定引數執行case的需求,init_data
要返回具體的id
才行。
3、init_data()
init_data(fixture_value)
函式裡傳入的fixture_value
,其實就是fixture
函式裡的params=[1, 2, 3]
。
return
出來的則是這個引數對應的id,分別是"未銷售"
、"銷售中"
、"已售罄"
,那麼我就可以通過pytest命令 加上 -k
來指定要執行的case。
def init_data(fixture_value):
if fixture_value == 1:
return "unsold"
elif fixture_value == 2:
return "onSale"
elif fixture_value == 3:
return "sellOut"
先不用-k
,看下整個的執行結果。
if __name__ == '__main__':
pytest.main(['-s', '-v', 'test_my_fixture.py::test_01'])
執行結果:
collected 3 items
test_my_fixture.py::test_01[\u672a\u9500\u552e]
引數為:【1】,執行sql--插入【1】狀態的資料
正在執行【1】的case--
PASSED
執行sql--清理引數為【1】的測試資料
----------------------------------------
test_my_fixture.py::test_01[\u9500\u552e\u4e2d]
引數為:【2】,執行sql--插入【2】狀態的資料
正在執行【2】的case--
PASSED
執行sql--清理引數為【2】的測試資料
----------------------------------------
test_my_fixture.py::test_01[\u5df2\u552e\u7f44]
引數為:【3】,執行sql--插入【3】狀態的資料
正在執行【3】的case--
PASSED
執行sql--清理引數為【3】的測試資料
----------------------------------------
============================================================================= 3 passed in 0.03s =============================================================================
可以看到,在case執行之前,就插入了3種狀態的測試資料,並且運行了3個case。
接下來,我們用-k
來執行 id是"onSale"
的case:
if __name__ == '__main__':
pytest.main(['-s', '-v', '-k', "onSale", 'test_my_fixture.py::test_01'])
執行結果:
collected 3 items / 2 deselected / 1 selected
test_my_fixture.py::test_01[onSale]
引數為:【2】,執行sql--插入【2】狀態的資料
正在執行【2】的case--
PASSED
執行sql--清理引數為【2】的測試資料
----------------------------------------
====================================================================== 1 passed, 2 deselected in 0.03s ======================================================================
可以看到,找到了3個case,但是隻執行了我們制定要執行的case。
在我以前寫的case中,其實並沒有這樣寫。之前我們寫了一個mock服務,於是乎我就把一些會變化的請求引數也配置進去了,然後根據我
傳參的不同,去拿到我想要的請求body,最後再去請求我要測試的介面。
現在新換了個地方,每天忙於dian業dian務dian,工作中突然有了這個想法,於是乎抽空找尋了下方法。這個方法我在後面的自動化服務搭建
中會去運用,屆時有新想法再分享。