天天看點

pytest架構進階自學系列 | 使用params傳遞不同資料

作者:熱愛程式設計的通信人

書籍來源:房荔枝 梁麗麗《pytest架構與自動化測試應用》

一邊學習一邊整理老師的課程内容及實驗筆記,并與大家分享,侵權即删,謝謝支援!

附上彙總貼:pytest架構進階自學系列 | 彙總_熱愛程式設計的通信人的部落格-CSDN部落格

fixture(scope="function",params=None,autouse=False,ids=None,name=None),params是參數,預設可以不選。

如果需要在一系列測試用例的執行中,每輪執行都使用同一個fixture,但是有不同的依賴場景,則可以考慮對fixture進行參數化,這種方式适用于對多場景的功能子產品進行詳盡測試。

可以通過指定params關鍵字參數建立兩個fixture執行個體,每個執行個體供一輪測試使用,所有的測試用例執行兩遍,在fixture的聲明函數中,可以使用request.param擷取目前使用的入參。

測試方法使用兩個簡單測試資料

每次執行應使用不同資料,一次應使用一個。可以通過fixture自帶的params準備不同的資料。下面是一個測試方法使用2個簡單測試資料。

代碼如下:

import pytest

@pytest.fixture(params=['apple', 'banana'])
def fruit(request):
    return request.param

def test_fruit(fruit):
    print("\n筆者今天吃{}".format(fruit))
    assert True
    
def test_cook_fruit(fruit):
    print("筆者今天做{}派。".format(fruit))
    assert True           

執行結果如下所示:

D:\SynologyDrive\CodeLearning\WIN\pytest-book\venv\Scripts\python.exe "C:/Program Files/JetBrains/PyCharm Community Edition 2022.3.2/plugins/python-ce/helpers/pycharm/_jb_pytest_runner.py" --path D:\SynologyDrive\CodeLearning\WIN\pytest-book\src\chapter-3\test_fixture_params.py 
Testing started at 14:11 ...
Launching pytest with arguments D:\SynologyDrive\CodeLearning\WIN\pytest-book\src\chapter-3\test_fixture_params.py in D:\SynologyDrive\CodeLearning\WIN\pytest-book\src\chapter-3

============================= test session starts =============================
platform win32 -- Python 3.7.7, pytest-5.4.1, py-1.11.0, pluggy-0.13.1 -- D:\SynologyDrive\CodeLearning\WIN\pytest-book\venv\Scripts\python.exe
cachedir: .pytest_cache
rootdir: D:\SynologyDrive\CodeLearning\WIN\pytest-book
collecting ... collected 4 items

test_fixture_params.py::test_fruit[apple] PASSED                         [ 25%]
筆者今天吃apple

test_fixture_params.py::test_fruit[banana] PASSED                        [ 50%]
筆者今天吃banana

test_fixture_params.py::test_cook_fruit[apple] PASSED                    [ 75%]筆者今天做apple派。

test_fixture_params.py::test_cook_fruit[banana] PASSED                   [100%]筆者今天做banana派。


============================== 4 passed in 0.04s ==============================

Process finished with exit code 0
           

二(多)個測試方法共用兩個簡單測試資料

在代碼中增加下述代碼,實作多個測試使用一組不同資料:

D:\SynologyDrive\CodeLearning\WIN\pytest-book\venv\Scripts\python.exe "C:/Program Files/JetBrains/PyCharm Community Edition 2022.3.2/plugins/python-ce/helpers/pycharm/_jb_pytest_runner.py" --path D:\SynologyDrive\CodeLearning\WIN\pytest-book\src\chapter-3\test_fixture_params.py 
Testing started at 14:11 ...
Launching pytest with arguments D:\SynologyDrive\CodeLearning\WIN\pytest-book\src\chapter-3\test_fixture_params.py in D:\SynologyDrive\CodeLearning\WIN\pytest-book\src\chapter-3

============================= test session starts =============================
platform win32 -- Python 3.7.7, pytest-5.4.1, py-1.11.0, pluggy-0.13.1 -- D:\SynologyDrive\CodeLearning\WIN\pytest-book\venv\Scripts\python.exe
cachedir: .pytest_cache
rootdir: D:\SynologyDrive\CodeLearning\WIN\pytest-book
collecting ... collected 4 items

test_fixture_params.py::test_fruit[apple] PASSED                         [ 25%]
筆者今天吃apple

test_fixture_params.py::test_fruit[banana] PASSED                        [ 50%]
筆者今天吃banana

test_fixture_params.py::test_cook_fruit[apple] PASSED                    [ 75%]筆者今天做apple派。

test_fixture_params.py::test_cook_fruit[banana] PASSED                   [100%]筆者今天做banana派。


============================== 4 passed in 0.04s ==============================

Process finished with exit code 0
           

有效測試資料與預期失敗xfail的測試資料

在測試某些功能時,如果步驟一緻,則可以使用資料驅動方式執行測試,也就是說多組資料使用一個測試方法。通常正确有效的資料分得一個測試方法,因為錯誤的步驟通常簡單,而正确的步驟會多些,必須分開驗證,但當步驟相同而資料不同時,預期失敗和預期正确的資料可以在一個測試方法中實作。這樣就隻有一組或幾組資料會引起預期的失敗,但不能在測試方法中标記預期失敗,而應該在通過參數傳遞的測試資料中使用pytest.param标記預期失敗。

代碼如下:

import pytest

@pytest.fixture(params=[('3+5', 8), pytest.param(('6*9', 42), marks=pytest.mark.xfail, ids='failed')])
def data_set(request):
    return request.param

def test_data(data_set):
    assert eval(data_set[0]) == data_set[1]           

在data_set方法中使用request傳遞fixture中的參數,使用request.param解析每個參數。在方法上面加上pytest.fixture裝飾器,通過參數傳遞不同的測試資料。一組是正确的,3+5和8比對,另一組将6*9和42比對,并且在test_data中通過data_set的依賴注入的方式傳入資料,通過eval方法将表達式,例如3+5從字元串轉換成可執行的算式,計算的結果與傳遞參數的第二列data_set[1]資料比對是否相等。

最重要的是第二組資料預期結果不正确,通過marks标記成失敗pytest.mark.xfail,并用ids顯示出failed。

D:\SynologyDrive\CodeLearning\WIN\pytest-book\venv\Scripts\python.exe "C:/Program Files/JetBrains/PyCharm Community Edition 2022.3.2/plugins/python-ce/helpers/pycharm/_jb_pytest_runner.py" --path D:\SynologyDrive\CodeLearning\WIN\pytest-book\src\chapter-3\test_fixture_param_marks.py 
Testing started at 14:19 ...
Launching pytest with arguments D:\SynologyDrive\CodeLearning\WIN\pytest-book\src\chapter-3\test_fixture_param_marks.py in D:\SynologyDrive\CodeLearning\WIN\pytest-book\src\chapter-3

============================= test session starts =============================
platform win32 -- Python 3.7.7, pytest-5.4.1, py-1.11.0, pluggy-0.13.1 -- D:\SynologyDrive\CodeLearning\WIN\pytest-book\venv\Scripts\python.exe
cachedir: .pytest_cache
rootdir: D:\SynologyDrive\CodeLearning\WIN\pytest-book
collecting ... collected 2 items

test_fixture_param_marks.py::test_data[data_set0] 
test_fixture_param_marks.py::test_data[failed] 

======================== 1 passed, 1 xfailed in 0.08s =========================

Process finished with exit code 0
PASSED                 [ 50%]XFAIL                     [100%]
data_set = ('6*9', 42)

    def test_data(data_set):
>       assert eval(data_set[0]) == data_set[1]
E       assert 54 == 42
E         +54
E         -42

test_fixture_param_marks.py:8: AssertionError
           

params與ids的應用

對于複雜類型的測試資料通常加上id或name來表明資料的含義,并标記測試要點。測試資料除了字元串以外,還可以是表達式,以及元組、字典、類等類型。使用ids關鍵字參數,自定義測試ID。

代碼如下:

import pytest

@pytest.fixture(params=[0, 'a'], ids=['number', 'charactor'])
def a(request):
    return request.param 

def test_a(a):
    print(a)
    pass

def idfn(fixture_value):
    if fixture_value == 0:
        return "eggs"
    elif fixture_value == 1:
        return False
    elif fixture_value == 2:
        return None
    else:
        return fixture_value
    
@pytest.fixture(params=[0,1,2,3], ids=idfn)
def b(request):
    return request.param

def test_b(b):
    print(b)
    pass

class C:
    pass

@pytest.fixture(params=[(1,2), {'d':1}, C()])
def c(request):
    return request.param

def test_c(c):
    print(c)
    pass           

ids=idfn,idfn是前面使用的方法,在fixture中将params的每個資料傳入idfn,ids的值就是其傳回值。當params=0時,傳回值eggs;當params=1時,傳回值False;當params=2時,傳回值None,ids顯示params的值;當params=3時,傳回值直接顯示為3。

D:\SynologyDrive\CodeLearning\WIN\pytest-book\venv\Scripts\python.exe "C:/Program Files/JetBrains/PyCharm Community Edition 2022.3.2/plugins/python-ce/helpers/pycharm/_jb_pytest_runner.py" --path D:\SynologyDrive\CodeLearning\WIN\pytest-book\src\chapter-3\test_fixture_params_ids.py 
Testing started at 14:24 ...
Launching pytest with arguments D:\SynologyDrive\CodeLearning\WIN\pytest-book\src\chapter-3\test_fixture_params_ids.py in D:\SynologyDrive\CodeLearning\WIN\pytest-book\src\chapter-3

============================= test session starts =============================
platform win32 -- Python 3.7.7, pytest-5.4.1, py-1.11.0, pluggy-0.13.1 -- D:\SynologyDrive\CodeLearning\WIN\pytest-book\venv\Scripts\python.exe
cachedir: .pytest_cache
rootdir: D:\SynologyDrive\CodeLearning\WIN\pytest-book
collecting ... collected 9 items

test_fixture_params_ids.py::test_a[number] PASSED                        [ 11%]0

test_fixture_params_ids.py::test_a[charactor] PASSED                     [ 22%]a

test_fixture_params_ids.py::test_b[eggs] PASSED                          [ 33%]0

test_fixture_params_ids.py::test_b[False] PASSED                         [ 44%]1

test_fixture_params_ids.py::test_b[2] PASSED                             [ 55%]2

test_fixture_params_ids.py::test_b[3] PASSED                             [ 66%]3

test_fixture_params_ids.py::test_c[c0] PASSED                            [ 77%](1, 2)

test_fixture_params_ids.py::test_c[c1] PASSED                            [ 88%]{'d': 1}

test_fixture_params_ids.py::test_c[c2] PASSED                            [100%]<test_fixture_params_ids.C object at 0x0000023D0F1A5388>


============================== 9 passed in 0.09s ==============================

Process finished with exit code 0
           

從執行結果可以看出:ids可以接收一個函數,用于生成測試ID。當測試ID指定為None時,使用的是params原先對應的值。

注意:當測試params中包含元組、字典或者對象時,測試ID使用的是fixture函數名+param的下标。

如果使用CLI終端執行,則結果如下:

PS D:\SynologyDrive\CodeLearning\WIN\pytest-book\src\chapter-3> pytest -s -v .\test_fixture_params_ids.py 
========================================================================================================= test session starts =========================================================================================================
platform win32 -- Python 3.7.7, pytest-5.4.1, py-1.11.0, pluggy-0.13.1 -- c:\users\guoliang\appdata\local\programs\python\python37\python.exe
cachedir: .pytest_cache
rootdir: D:\SynologyDrive\CodeLearning\WIN\pytest-book
plugins: allure-pytest-2.13.2
collected 9 items                                                                                                                                                                                                                       

test_fixture_params_ids.py::test_a[number] 0
PASSED
test_fixture_params_ids.py::test_a[charactor] a
PASSED
test_fixture_params_ids.py::test_b[eggs] 0
PASSED
test_fixture_params_ids.py::test_b[False] 1
PASSED
test_fixture_params_ids.py::test_b[2] 2
PASSED
test_fixture_params_ids.py::test_b[3] 3
PASSED
test_fixture_params_ids.py::test_c[c0] (1, 2)
PASSED
test_fixture_params_ids.py::test_c[c1] {'d': 1}
PASSED
test_fixture_params_ids.py::test_c[c2] <test_fixture_params_ids.C object at 0x0000022B6155BF48>
PASSED

========================================================================================================== 9 passed in 0.03s ========================================================================================================== 
PS D:\SynologyDrive\CodeLearning\WIN\pytest-book\src\chapter-3> 
           

params綜合執行個體

這個測試用例使用不同的SMTP伺服器,共執行了3次。在參數化的fixture中,pytest為每個fixture執行個體自動指定一個測試ID。

import pytest
import smtplib

@pytest.fixture(scope='module', params=['smtp.163.com', 'smtp.126.com', 'mail.python.org'])
def smtp_connection_params(request):
    server = request.param
    with smtplib.SMTP(server, 587, timeout=5) as smtp_connection:
        yield smtp_connection
        
def test_parames(smtp_connection_params):
    response, _ = smtp_connection_params.ehlo()
    assert response == 250           

可以看到執行顯示的ID是test_parames[smtp.163.com]、test_parames[smtp.126.com]、test_parames[mail.python.org]。

圖3-10 param的綜合執行個體

PS D:\SynologyDrive\CodeLearning\WIN\pytest-book\src\chapter-3> pytest -q -s --collect-only .\test_request.py
src/chapter-3/test_request.py::test_parames[smtp.163.com]
src/chapter-3/test_request.py::test_parames[smtp.126.com]
src/chapter-3/test_request.py::test_parames[mail.python.org]

no tests ran in 0.01s
PS D:\SynologyDrive\CodeLearning\WIN\pytest-book\src\chapter-3> 
           

繼續閱讀