前言
什麼是引數化,通俗點理解就是,定義一個測試類或測試函式,可以傳入不同測試用例對應的引數,從而執行多個測試用例。
例如對登入介面進行測試,假設有3條用例:正確賬號正確密碼登入、正確賬號錯誤密碼登入、錯誤賬號正確密碼登入,那麼我們只需要定義一個登陸測試函式test_login()
,然後使用這3條用例對應的引數去呼叫test_login()
即可。
在unittest中可以使用ddt
進行引數化,而pytest中也提供非常方便的引數化方式,即使用裝飾器@pytest.mark.parametrize()
。
一般寫為pytest.mark.parametrize("argnames", argvalues)
,其中:
argnames
為引數名稱,可以是單個或多個,多個寫法為"argname1, argname2, ......"argvalues
為引數值,型別必須為list
(單個引數時可以為元組,多個引數時必須為list,所以最好統一)
當然pytest.mark.parametrize()
不止這兩個引數,感興趣的可以去檢視原始碼,後面我們還會講到其中的引數ids
。
示例中所請求的登陸介面資訊為:
- 介面url:http://127.0.0.1:5000/login
- 請求方式:post
- 請求引數:{"username": "xxx", "password": "xxxxxx"}
- 響應資訊:{"code": 1000, "msg": "登入成功", "token": "xxxxxx"}
單個引數
只需要傳入一個引數時,示例如下:
# 待測試函式
def sum(a):
return a+1
# 單個引數
data = [1, 2, 3, 4]
@pytest.mark.parametrize("item", data)
def test_add(item):
actual = sum(item)
print("\n{}".format(actual))
# assert actual == 3
if __name__ == '__main__':
pytest.main()
注意,@pytest.mark.parametrize()
中的第一個引數,必須以字串的形式來標識測試函式的入參,如上述示例中,定義的測試函式test_login()
中傳入的引數名為item
,那麼@pytest.mark.parametrize()
的第一個引數則為"item"
。
執行結果如下:
rootdir: E:\blog\python介面自動化\apiAutoTest, configfile: pytest.ini
plugins: html-2.1.1, metadata-1.10.0, ordering-0.6, rerunfailures-9.1.1
collecting ... collected 4 items
test_case_2.py::test_add[1] PASSED [ 25%]
2
test_case_2.py::test_add[2] PASSED [ 50%]
3
test_case_2.py::test_add[3] PASSED [ 75%]
4
test_case_2.py::test_add[4] PASSED [100%]
5
============================== 4 passed in 0.02s ==============================
從結果我們可以看到,測試函式分別傳入了data中的引數,總共執行了5次。
多個引數
測試用例需傳入多個引數時,@pytest.mark.parametrize()
的第一個引數同樣是字串, 對應用例的多個引數用逗號分隔。
示例如下:
import pytest
import requests
import json
# 列表巢狀元組
data = [("lilei", "123456"), ("hanmeimei", "888888")]
# 列表巢狀列表
# data = [["lilei", "123456"], ["hanmeimei", "888888"]]
@pytest.mark.parametrize("username, password", data)
def test_login(username, password):
headers = {"Content-Type": "application/json;charset=utf8"}
url = "http://127.0.0.1:5000/login"
_data = {
"username": username,
"password": password
}
res = requests.post(url=url, headers=headers, json=_data).text
res = json.loads(res)
assert res['code'] == 1000
if __name__ == '__main__':
pytest.main()
這裡需要注意:
- 程式碼中
data
的格式,可以是列表巢狀列表,也可以是列表巢狀元組,列表中的每個列表或元組代表一組獨立的請求引數。 "username, password"
不能寫成 "username", "password"。
執行結果如下:
從結果中我們還可以看到每次執行傳入的引數,如下劃線所示部分。
這裡所舉示例是2個引數,傳入3個或更多引數時,寫法也同樣如此,一定要注意它們之間一一對應的關係,如下圖:
對測試類引數化
上面所舉示例都是對測試函式進行引數化,那麼對測試類怎麼進行引數化呢?
其實,對測試類的引數化,就是對測試類中的測試方法進行引數化。@pytest.mark.parametrize()
中標識的引數個數,必須與類中的測試方法的引數一致。示例如下:
# 將登陸介面請求單獨進行了封裝,僅僅只是為了方便下面的示例
def login(username, password):
headers = {"Content-Type": "application/json;charset=utf8"}
url = "http://127.0.0.1:5000/login"
_data = {
"username": username,
"password": password
}
res = requests.post(url=url, headers=headers, json=_data).text
res = json.loads(res)
return res
# 測試類引數化
data = [
("lilei", "123456"), ("hanmeimei", "888888")
]
@pytest.mark.parametrize("username, password", data)
class TestLogin:
def test_login_01(self, username, password):
res = login(username, password)
assert res['code'] == 1000
def test_login_02(self, username, password):
res = login(username, password)
assert res['msg'] == "登入成功!"
if __name__ == '__main__':
pytest.main(["-s"])
執行結果如下:
從結果中可以看出來,總共執行了4
次,測試類中的每個測試方法都執行了2
次,即每個測試方法都將data
中的每一組引數都執行了一次。
注意,這裡還是要強調引數對應的關係,即@pytest.mark.parametrize()
中的第一個引數,需要與測試類下面的測試方法的引數一一對應。
引數組合
在編寫測試用例的過程中,有時候需要將引數組合進行介面請求,如示例的登入介面中username
有 lilei、hanmeimei,password
有 123456、888888,進行組合的話有下列四種情況:
{"username": "lilei", "password": "123456"}
{"username": "lilei", "password": "888888"}
{"username": "hanmeimei", "password": "123456"}
{"username": "hanmeimei", "password": "888888"}
在@pytest.mark.parametrize()
也提供了這樣的引數組合功能,編寫格式示例如下:
import pytest
import requests
import json
username = ["lilei", "hanmeimei"]
password = ["123456", "888888"]
@pytest.mark.parametrize("password", password)
@pytest.mark.parametrize("username", username)
def test_login(username, password):
headers = {"Content-Type": "application/json;charset=utf8"}
url = "http://127.0.0.1:5000/login"
_data = {
"username": username,
"password": password
}
res = requests.post(url=url, headers=headers, json=_data).text
res = json.loads(res)
assert res['code'] == 1000
if __name__ == '__main__':
pytest.main()
執行結果如下:
rootdir: E:\blog\python介面自動化\apiAutoTest, configfile: pytest.ini
plugins: html-2.1.1, metadata-1.10.0, ordering-0.6, rerunfailures-9.1.1
collecting ... collected 4 items
test_case_5.py::test_login[lilei-123456] PASSED [ 25%]
test_case_5.py::test_login[lilei-888888] FAILED [ 50%]
test_case_5.py::test_login[hanmeimei-123456] FAILED [ 75%]
test_case_5.py::test_login[hanmeimei-888888] PASSED [100%]
=========================== short test summary info ===========================
FAILED test_case_5.py::test_login[lilei-888888] - assert 1001 == 1000
FAILED test_case_5.py::test_login[hanmeimei-123456] - assert 1001 == 1000
========================= 2 failed, 2 passed in 0.18s =========================
從結果可以看出來,2個username、2個password 有4中組合方式,總執行了4次。如果是3個username、2個password,那麼就有6中引數組合方式,依此類推。
注意,以上這些示例中的測試用例僅僅只是用於舉例,實際專案中的登入介面測試指令碼與測試資料會不一樣。
增加測試結果可讀性
從示例的執行結果中我們可以看到,為了區分引數化的執行結果,在結果中都會顯示由引數組合而成的執行用例名稱,很方便就能看出來執行了哪些引數組合的用例,如下示例:
但這只是簡單的展示,如果引數多且複雜的話,僅僅這樣展示是不夠清晰的,需要新增一些說明才能一目瞭然。
因此,在@pytest.mark.parametrize()
中有兩種方式來自定義上圖中劃線部分的顯示結果,即使用@pytest.mark.parametrize()
提供的引數 ids
自定義,或者使用pytest.param()
中的引數id
自定義。
ids(推薦)
ids
使用方法示例如下:
import pytest
import requests
import json
data = [("lilei", "123456"), ("hanmeimei", "888888")]
ids = ["username:{}-password:{}".format(username, password) for username, password in data]
@pytest.mark.parametrize("username, password", data, ids=ids)
def test_login(username, password):
headers = {"Content-Type": "application/json;charset=utf8"}
url = "http://127.0.0.1:5000/login"
_data = {
"username": username,
"password": password
}
res = requests.post(url=url, headers=headers, json=_data).text
res = json.loads(res)
assert res['code'] == 1000
if __name__ == '__main__':
pytest.main()
從編寫方式可以看出來,ids
就是一個list
,且它的長度與引數組合的分組數量一致。
執行結果如下:
比較上面個執行結果,我們能看出ids
自定義執行結果與預設執行結果展示的區別。使用過程中,需要根據實際情況來自定義。
id
使用方式示例如下:
import pytest
import requests
import json
data = [
pytest.param("lilei", "123456", id="correct username and correct password"),
pytest.param("lilei", "111111", id="correct user name and wrong password")
]
@pytest.mark.parametrize("username, password", data)
def test_login(username, password):
headers = {"Content-Type": "application/json;charset=utf8"}
url = "http://127.0.0.1:5000/login"
_data = {
"username": username,
"password": password
}
res = requests.post(url=url, headers=headers, json=_data).text
res = json.loads(res)
assert res['code'] == 1000
if __name__ == '__main__':
pytest.main()
執行結果如下:
從個人實際使用體驗來說,感覺id
相對於ids
,自定義的擴充套件性相當較小,而且我嘗試著在id
中使用中文進行定義,控制檯顯示的不是中文,而是編碼後的結果。所以推薦使用ids
。當然也可能是它的作用我沒理解透徹。
總結
以上功能基本能覆蓋我們平常自動化測試專案中的絕大部分場景。
當然,pytest.mark.parametrize()
進行引數化的過程中,還有一些別的功能,如結合pytest.param()
標記用例失敗或跳過,感興趣的可以自行查詢使用方式,這裡不做過多說明。