1. 程式人生 > 實用技巧 >【pytest】(十)fixture引數化-巧用params和ids優雅的建立測試資料

【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(),這是測試case
  • my_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,工作中突然有了這個想法,於是乎抽空找尋了下方法。這個方法我在後面的自動化服務搭建
中會去運用,屆時有新想法再分享。