柚子快報(bào)邀請(qǐng)碼778899分享:pytest
柚子快報(bào)邀請(qǐng)碼778899分享:pytest
pytest命名規(guī)則
文件名類方法函數(shù)谷歌風(fēng)格命名規(guī)范Google Python命名規(guī)范-易微幫
pytest測(cè)試用例編寫規(guī)則
類型規(guī)則文件test_開(kāi)頭或者_(dá)test結(jié)尾類Test開(kāi)頭方法/函數(shù)test開(kāi)頭測(cè)試類中不可添加__init__構(gòu)造函數(shù)
pycharm配置與界面化運(yùn)行
首先進(jìn)行添加pytest環(huán)境?其次在settings中的搜索框搜索pytest在default test runner中選擇pytest
?pytest用例結(jié)構(gòu)
用例結(jié)構(gòu)(缺一不可)
用例名稱用例步驟用例斷言
def test_XXX(self):
# 測(cè)試步驟1
# 測(cè)試步驟2
# 斷言 實(shí)際結(jié)果 對(duì)比 預(yù)期結(jié)果
assert ActualResult == ExpectedResult
?類級(jí)別的用例示例
class TestXXX:
def setup(self):
# 資源準(zhǔn)備
pass
def teardown(self):
# 資源銷毀
pass
def test_XXX(self):
# 測(cè)試步驟1
# 測(cè)試步驟2
# 斷言 實(shí)際結(jié)果 對(duì)比 預(yù)期結(jié)果
assert ActualResult == ExpectedResult
用例斷言
概念:斷言(assertion)是一種在程序中的一階邏輯(如:一個(gè)結(jié)果為真或假的邏輯判斷式),目的為了表示與驗(yàn)證軟件開(kāi)發(fā)者預(yù)期的結(jié)果——當(dāng)程序執(zhí)行到斷言的位置時(shí),對(duì)應(yīng)的斷言應(yīng)該為真。若斷言不為真時(shí),程序會(huì)中止執(zhí)行,并給出錯(cuò)誤信息
斷言寫法
assert <表達(dá)式>assert <表達(dá)式>,<描述>
示例1
def test_sum():
a = 1
b = 2
expect = 3
assert a + b == expect,f'{a} + = {expect} ,結(jié)果為真'
示例2//查看當(dāng)前環(huán)境下是否為linux環(huán)境
import sys
def test_plat():
assert ('linux' in sys.platform), "該代碼只能在 Linux 下執(zhí)行"
?pytest測(cè)試結(jié)構(gòu)框架(set up/tear down)
set up為一些準(zhǔn)備的前置工作tear down為完成一些后置工作
類型規(guī)則setup_module/teardown_module全局模塊級(jí)setup_class/teardown_class類級(jí),只在類中前后運(yùn)行一次(一般常用???????)setup_function/teardown_function函數(shù)級(jí),在類外setup_method/teardown_method方法級(jí),類中的每個(gè)方法執(zhí)行前后setup/teardown在類中,運(yùn)行在調(diào)用方法的前后(一般常用???????)
def test_case1():
print('case1')
def setup_function():
print("資源準(zhǔn)備,setup function")
def teardown_function():
print('資源已損毀 teardown_function')
運(yùn)行代碼后,在終端開(kāi)啟兩個(gè)button,一個(gè)show passed和一個(gè)show ignored,如下圖
def setup_module():
print('資源準(zhǔn)備,setup module')
def teardown_module():
print('資源銷毀,teardown_module')
def test_case1():
print('case1')
def test_case2():
print('case2')
def setup_function():
print("資源準(zhǔn)備,setup function")
def teardown_function():
print('資源已損毀 teardown_function')
運(yùn)行結(jié)果是:
pytest_study.py::test_case1 資源準(zhǔn)備,setup module //module級(jí)別的只執(zhí)行一次
資源準(zhǔn)備,setup function //一般每執(zhí)行一次測(cè)試用例就會(huì)執(zhí)行一次
PASSED [ 50%]case1
資源已損毀 teardown_function
pytest_study.py::test_case2 資源準(zhǔn)備,setup function
PASSED [100%]case2
資源已損毀 teardown_function
資源銷毀,teardown_module
============================== 2 passed in 0.01s ===============================
?pytest 參數(shù)化
通過(guò)參數(shù)的方式傳遞數(shù)據(jù),從而實(shí)現(xiàn)數(shù)據(jù)和腳本分離并且可以實(shí)現(xiàn)用力的重復(fù)生成與執(zhí)行
參數(shù)化實(shí)現(xiàn)方案
裝飾器:@pytest.mark.parametrize
測(cè)試登陸場(chǎng)景
測(cè)試登陸成功,登陸失敗(賬號(hào)錯(cuò)誤,密碼錯(cuò)誤)創(chuàng)建多種賬號(hào),中文文本賬號(hào),英文文本賬號(hào)
不使用pytest
def test_param_login_ok():
# 登錄成功
username = "right"
password = "right"
login(username,password)
def test_param_login_fail():
# 登錄失敗
username = "wrong"
password = "wrong"
login(username,password)
使用pytest的場(chǎng)景
@pytest.mark.parametrize("username,password",[["right","right"],["wrong","wrong"]])
def test_param(username,password):
login(username,password)
?參數(shù)化測(cè)試函數(shù)使用
單參數(shù)
每一條測(cè)試數(shù)據(jù)就會(huì)生成一個(gè)測(cè)試用例 search_list = ['appium', 'selenium', 'pytest']
@pytest.mark.parametrize('search_key', ['appium', 'pytest', 'allure', 'abc'])
def test_search_param(search_key):
assert search_key in search_list
多參數(shù)
# 第二種:多參數(shù)情況,用列表嵌套列表的方式
@pytest.mark.parametrize("username,password", [["rightusername", "right username"],
["wrong username", "wrong password"],
[" ", "password"]
])
def test_login(username, password):
print(f"登陸的用戶名:{username},登陸的密碼:{password}")
默認(rèn)的命名方式測(cè)試數(shù)據(jù)用“-”相連接 如“pytest.pytest_study.test_login”
???????
用例重命名
# 第二種:多參數(shù)情況,用列表嵌套列表的方式
@pytest.mark.parametrize("username,password", [["rightusername", "right username"],
["wrong username", "wrong password"],
[" ", "password"]
],
ids = ["正常的用戶和正確的密碼","錯(cuò)誤的用戶名和錯(cuò)誤的密碼","用戶名為空"]
)
def test_login(username, password):
print(f"登陸的用戶名:{username},登陸的密碼:{password}")
#第三種給我們的用例進(jìn)行重命名,默認(rèn)的命名方式測(cè)試數(shù)據(jù)用“-”相連接 如“pytest.pytest_study.test_login”
如上這樣我們的運(yùn)行結(jié)果是不支持中文的,如下為運(yùn)行后的結(jié)果
?如想要其支持中文的話,需要在文件夾內(nèi)添加conftest.py
# 創(chuàng)建conftest.py 文件 ,將下面內(nèi)容添加進(jìn)去,運(yùn)行腳本
def pytest_collection_modifyitems(items):
"""
測(cè)試用例收集完成時(shí),將收集到的用例名name和用例標(biāo)識(shí)nodeid的中文信息顯示在控制臺(tái)上
"""
for i in items:
i.name=i.name.encode("utf-8").decode("unicode_escape")
i._nodeid=i.nodeid.encode("utf-8").decode("unicode_escape")
如上運(yùn)行結(jié)果,測(cè)試用例將被重命名為中文的格式
笛卡爾積(在接口測(cè)試中應(yīng)用比較廣)
兩組數(shù)據(jù)a = [1,2,3]b = [a,b,c]對(duì)應(yīng)有幾種組形式(1,a)(1,b)(1,c)(2,a)(2,b)(2,c)(3,a)(3,b)(3,c) @pytest.mark.parametrize("b", ["a", "b", "c"])
@pytest.mark.parametrize("a", [1, 2, 3])
def test_param1(a,b):
print(f'笛卡爾積形式的參數(shù)中a = {a},b = ')
? ? ? ? ? ? ? ?測(cè)試結(jié)果如下:
????????????????
pytest標(biāo)記測(cè)試用例
pytest設(shè)置跳過(guò)及預(yù)期失敗測(cè)試用例
調(diào)試時(shí)不想運(yùn)行這個(gè)用例標(biāo)記無(wú)法在某些平臺(tái)上運(yùn)行的測(cè)試功能在某些版本中執(zhí)行,其他版本中跳過(guò)比如:當(dāng)前的外部資源不可用時(shí)跳過(guò)
如果測(cè)試數(shù)據(jù)是從數(shù)據(jù)庫(kù)中取到的,連接數(shù)據(jù)庫(kù)的功能如果返回結(jié)果未成功就跳過(guò),因?yàn)閳?zhí)行也都報(bào)錯(cuò)解決 1:添加裝飾器
無(wú)條件跳過(guò)@pytest.mark.skip
??????????????使用方法:@pytest.mark.skip標(biāo)記在需要跳過(guò)的測(cè)試用例上。 @pytest.mark.skip
def test_login():
print('該條測(cè)試用例未開(kāi)發(fā)完成')
assert True
有條件跳過(guò)@pytest.mark.skipif(reason = 'xxx')//需要添加具體的原因
??????? @pytest.mark.skipif(reson = '代碼未實(shí)現(xiàn)')
def test_bbb():
print('該條測(cè)試用例,代碼未實(shí)現(xiàn)')
assert False 執(zhí)行完成后:
解決 2:代碼中添加跳過(guò)代碼
pytest.skip(reason) def check_login():
return True
def test_function():
print('start')
# 如果未登陸則跳過(guò)后續(xù)步驟
if not check_login():
pytest.skip('unsupported configuration')
print('end')
?在mac上跳過(guò),在windows上運(yùn)行 import sys
import pytest
print(sys.platform)
@pytest.mark.skipif(sys.platform == 'darwin',reason='does not run on mac')
def test_aaa():
assert True
@pytest.mark.skipif(sys.platform == 'win',reason='does not run on windows')
def test_bbb():
assert True xfail使用場(chǎng)景:
?與skip類似,預(yù)期結(jié)果為fail,標(biāo)記用例為fail用法:添加裝飾器@pytest.mark.xfail
?pytest運(yùn)行用例
運(yùn)行 某個(gè)/多個(gè) 用例包運(yùn)行 某個(gè)/多個(gè) 用例模塊運(yùn)行 某個(gè)/多個(gè) 用例類運(yùn)行 某個(gè)/多個(gè) 用例方法
pytest只執(zhí)行上次失敗的測(cè)試用例
--lf(--last-failed)?只重新運(yùn)行故障。--ff(--failed-first)?先運(yùn)行故障然后再運(yùn)行其余的測(cè)試pytest文檔26-運(yùn)行上次失敗用例(--lf 和 --ff)_weixin_34292959的博客-CSDN博客
?常用命令行
?python執(zhí)行pytest代碼
使用main函數(shù)使用python -m pytest 調(diào)用pytest(jeckins持續(xù)集成)
if __name__ == '__main__': ? ? # 1、運(yùn)行當(dāng)前目錄下所有符合規(guī)則的用例,包括子目錄(test_*.py 和 *_test.py) ? ?
pytest.main() ? ? # 2、運(yùn)行test_mark1.py::test_dkej模塊中的某一條用例 ? ? pytest.main(['test_mark1.py::test_dkej','-vs']) ? ? # 3、運(yùn)行某個(gè) 標(biāo)簽 ? ? pytest.main(['test_mark1.py','-vs','-m','dkej']) 運(yùn)行方式 `python test_*.py `
常用的異常處理方法
Pytest如何對(duì)捕獲的異常的類型和內(nèi)容進(jìn)行斷言_pytest捕獲異常_redrose2100的博客-CSDN博客
?pytest結(jié)合數(shù)據(jù)驅(qū)動(dòng)-yaml
什么是數(shù)據(jù)驅(qū)動(dòng)
數(shù)據(jù)驅(qū)動(dòng)就是數(shù)據(jù)的改變從而驅(qū)動(dòng)自動(dòng)化測(cè)試的執(zhí)行,最終引起測(cè)試結(jié)果的改變,簡(jiǎn)單來(lái)說(shuō),就是參數(shù)化的應(yīng)用,
數(shù)據(jù)量小的測(cè)試用例可以使用代碼的參數(shù)化來(lái)實(shí)現(xiàn)數(shù)據(jù)驅(qū)動(dòng),數(shù)據(jù)量大的情況下使用結(jié)構(gòu)化的文件(例如yaml,json)來(lái)對(duì)數(shù)據(jù)進(jìn)行存儲(chǔ),然后用測(cè)試用例讀取這些數(shù)據(jù)應(yīng)用:
app、web、接口自動(dòng)化測(cè)試測(cè)試步驟的數(shù)據(jù)驅(qū)動(dòng)測(cè)試數(shù)據(jù)的數(shù)據(jù)驅(qū)動(dòng)配置的數(shù)據(jù)驅(qū)動(dòng)
yaml文件介紹
對(duì)象:鍵值對(duì)的集合,用冒號(hào)“:”表示數(shù)組:一組按次序排列的值,前加 “-”純量:?jiǎn)蝹€(gè)的,不可再分的值
字符串布爾值整數(shù)浮點(diǎn)數(shù)NULL時(shí)間日期
# 編程語(yǔ)言
languages: # 相當(dāng)于是一對(duì)鍵值 languages:["php","Java","Python"]
- PHP
- Java
- Python
book:
Python入門: # 書籍名稱
price: 55.5
author: Lily
available: True
repertory: 20
date: 2018-02-17
Java入門:
price: 60
author: Lily
available: False
repertory: Null
date: 2018-05-11
yaml文件使用
查看yaml文件
pycharmtxt記事本讀取yaml文件
安裝:pip install pyyaml方法:yaml.safe_load(f) #將yaml轉(zhuǎn)化為python對(duì)象方法:yaml.safe_dump(f)#將python對(duì)象轉(zhuǎn)化為yaml文件 import yaml
file_path = './my.yaml'
with open(file_path, 'r', encoding='utf-8') as f:
data = yaml.safe_load(f)
工程目錄文件?
data 目錄:存放 yaml 數(shù)據(jù)文件func 目錄:存放被測(cè)函數(shù)文件testcase 目錄:存放測(cè)試用例文件
ps:創(chuàng)建data時(shí),可以創(chuàng)建為文件夾,也可以創(chuàng)建為packages
?operation.py#被測(cè)函數(shù)
def my_add(x, y):
result = x + y
return result
test_add.py#測(cè)試case
import pytest
from data_driver_yaml.func.operation import my_add#如果不寫這個(gè)會(huì)造成 下面的my_add函數(shù)找不到,會(huì)報(bào)錯(cuò)
class TestWithYAML:
@pytest.mark.parametrize('x,y,expected', [[1, 1, 2],[1,2,3],[5,6,7]])
def test_add(self, x, y, expected):
assert my_add(int(x), int(y)) == int(expected)
當(dāng)測(cè)試數(shù)據(jù)量很大時(shí),我們可以從yaml中再讀取出來(lái)數(shù)據(jù)
import pytest
import yaml
from data_driver_yaml.func.operation import my_add
#windows電腦需要將with open中再添加一個(gè)參數(shù) encoding = “utf-8”
def get_data():
with open("../data/data.yaml") as f:
data = yaml.safe_load(f)
return data
def test_get_data():
print(get_data())
class TestWithYAML:
@pytest.mark.parametrize('x,y,expected', get_data())
def test_add(self, x, y, expected):
assert my_add(int(x), int(y)) == int(expected)
data_yaml#測(cè)試數(shù)據(jù)
#嵌套三個(gè)列表[[1,1,2],[3,6,9],[100,200,300]]
-
- 1
- 1
- 2
-
- 3
- 6
- 9
-
- 100
- 200
- 300
pytest結(jié)合數(shù)據(jù)驅(qū)動(dòng)Excel
第三方庫(kù)
xlrdxlwingspandas openpyxl
官方文檔: https://openpyxl.readthedocs.io/en/stable/openpyxl庫(kù)的安裝
安裝:pip install openpyxl導(dǎo)入:import openpyxl
import openpyxl
# 獲取工作簿
book = openpyxl.load_workbook('../data/params.xlsx')
# 讀取工作表
sheet = book.active
# 讀取單個(gè)單元格
cell_a1 = sheet['A1']
cell_a3 = sheet.cell(column=1, row=3) # A3
# 讀取多個(gè)連續(xù)單元格
cells = sheet["A1":"C3"]
# 獲取單元格的值
cell_a1.value
注意xlsx文件必須用wps進(jìn)行創(chuàng)建,在文件下創(chuàng)建file重命名為xlsx文件會(huì)出現(xiàn)報(bào)錯(cuò)的問(wèn)題
testcases.py文件
from openpyxl_study.read_excel.func.operation import my_add
import pytest
import openpyxl
def get_excel():
book = openpyxl.load_workbook("../data/params.xlsx")
sheet = book.active
cells = sheet["A1":"C3"]
print(cells)
values = []
for row in cells:
data = []
for cell in row:
data.append(cell.value)
values.append(data)
return values
class TestWithEXCEL:
@pytest.mark.parametrize('x,y,expected', get_excel())
def test_add(self, x, y, expected):
assert my_add(int(x), int(y)) == int(expected)
pytest結(jié)合csv實(shí)現(xiàn)數(shù)據(jù)驅(qū)動(dòng)
csv文件介紹
csv:逗號(hào)分隔值是comma-separated values的縮寫是純文本形式存儲(chǔ)數(shù)字和文本文件由任意數(shù)目的記錄組成每行記錄由多個(gè)字段組成
以下例子:
Linux從入門到高級(jí),linux,¥5000
web自動(dòng)化測(cè)試進(jìn)階,python,¥3000
app自動(dòng)化測(cè)試進(jìn)階,python,¥6000
Docker容器化技術(shù),linux,¥5000
測(cè)試平臺(tái)開(kāi)發(fā)與實(shí)戰(zhàn),python,¥8000
讀取數(shù)據(jù)
內(nèi)置函數(shù):open()內(nèi)置模塊:csv //有相關(guān)讀取csv文件的方法
??????????????方法:csv.reader(iterable)
參數(shù):iterable ,文件或列表對(duì)象返回:迭代器,每次迭代會(huì)返回一行數(shù)據(jù)。
工程目錄結(jié)構(gòu)
# 工程目錄結(jié)構(gòu)
.
├── data
│ └── params.csv
├── func
│ ├── __init__.py
│ └── operation.py
└── testcase
├── __init__.py
└── test_add.py
data 目錄:存放 csv 數(shù)據(jù)文件func 目錄:存放被測(cè)函數(shù)文件testcase 目錄:存放測(cè)試用例文件
import pytest
import csv
from openpyxl_study.read_csv.func.operation import my_add
def get_csv():
with open("../data/demo.csv") as f:
#讀取這個(gè)文件
raw = csv.reader(f)
data = []
for line in raw:
data.append(line)
print(data)
return data
class TestWithCsv:
@pytest.mark.parametrize('x,y,expected',get_csv())
def test_add(self,x,y,expected):
assert my_add(int(x),int(y)) == int(expected)
pytest結(jié)合數(shù)據(jù)驅(qū)動(dòng)json
json是js對(duì)象全稱是javascript object notation是一種輕量級(jí)的數(shù)據(jù)交換格式j(luò)son結(jié)構(gòu)
對(duì)象{'key':value}數(shù)組[value1,value2....]例子 {
"name:": "hogwarts ",
"detail": {
"course": "python",
"city": "北京"
},
"remark": [1000, 666, 888]
} 查看 json 文件
pycharmtxt 記事本讀取 json 文件
內(nèi)置函數(shù) open()內(nèi)置庫(kù) json方法:json.loads() //返回值是dict 字典格式的方法:json.dumps() //返回值是字符串類型的格式
import json
def get_json():
with open("demo.json") as file:
data = json.load(file)
print(data,type(data))
s = json.dumps(data,ensure_ascii=False)
print(s,type(s))
if __name__ == "__main__":
get_json()
運(yùn)行結(jié)果是:
{'name:': 'hogwarts ', 'detail': {'course': 'python', 'city': '北京'}, 'remark': [1000, 666, 888]}
{"name:": "hogwarts ", "detail": {"course": "python", "city": "北京"}, "remark": [1000, 666, 888]}
{
"case1": [1, 1, 2],
"case2": [3, 6, 9],
"case3": [100, 200, 300]
}
import json
from json_study.func.operation import my_add
import pytest
def get_json():
with open("../data/params.json",'r') as file:
data = json.loads(file.read())
print(data)
return list(data.values())
class TestWithJSON:
@pytest.mark.parametrize('x,y,expected', get_json())
def test_add(self, x, y, expected):
assert my_add(int(x), int(y)) == int(expected)
4. 使用values()方法取值
Python字典提供了values()方法,該方法可以將字典的所有值以列表(list)的形式返回。我們可以通過(guò)遍歷列表來(lái)取出對(duì)應(yīng)的值。具體可以通過(guò)以下方式實(shí)現(xiàn):
```
person = {"name": "張三", "age": 18, "gender": "男"}
for value in person.values():
print(value)
```
在上述代碼中,我們使用for循環(huán)遍歷了字典person中的所有值,然后使用value變量取出對(duì)應(yīng)的值。輸出結(jié)果為:
```
張三
18
男
```
需要注意的是,values()方法返回的值是無(wú)序的,因此輸出結(jié)果的順序與字典中各個(gè)鍵值對(duì)的順序無(wú)關(guān)。
?Fixture用法
Python測(cè)試框架pytest(04)fixture - 測(cè)試用例調(diào)用fixture、fixture傳遞測(cè)試數(shù)據(jù)_pytest用例之間值傳遞fixture_wangmcn的博客-CSDN博客
Fixture特點(diǎn)及優(yōu)勢(shì)
命令靈活:對(duì)于setup,teardown,可以不起這兩個(gè)名字?jǐn)?shù)據(jù)共享:在conftest.py配置里寫方法可以實(shí)現(xiàn)數(shù)據(jù)共享,不需要import導(dǎo)入,可以跨文件共享scope的層次及神奇的yield組合相當(dāng)于各種setup和teardown實(shí)現(xiàn)參數(shù)化
fixture在自動(dòng)化中的應(yīng)用
場(chǎng)景測(cè)試用例執(zhí)行時(shí),有時(shí)用例需要登陸才能執(zhí)行,有些不需要登陸。setup和teardown無(wú)法滿足,fixture可以。默認(rèn)scope(范圍)function步驟
導(dǎo)入pytest在登陸的函數(shù)上面加@pytest.fixture()在要使用的測(cè)試方法中傳入(登陸函數(shù)名稱)不穿入的就不登陸直接執(zhí)行測(cè)試方法 import pytest
def login():
print("登陸操作")
def test_search():
print("搜索")
def test_cart():
login()
print("購(gòu)物車")
def test_order():
login()
print("購(gòu)物車") import pytest
@pytest.fixture()
def login():
print("登陸操作")
def test_search():
print("搜索")
def test_cart(login):
print("購(gòu)物車")
def test_order(login):
print("購(gòu)物車")
?yield的用法
場(chǎng)景:
你已經(jīng)可以將測(cè)試方法【當(dāng)前要執(zhí)行的或依賴的】解決了,測(cè)試方法后銷毀清楚數(shù)據(jù)的要如何進(jìn)行呢?解決:
通過(guò)在fixture函數(shù)中加入yield關(guān)鍵字,yield時(shí)調(diào)用第一次返回結(jié)果,第二次執(zhí)行他下面的語(yǔ)句返回步驟:
在@pytest.fixture(scope=module)在登陸的方法中加yield,之后加銷毀清除的步驟
import pytest
@pytest.fixture()
def login():
# setup操作
print("登陸操作")
token = '123456'
username = 'hogwarts'
yield token, username#相當(dāng)于return
#teardown操作
print('完成登出操作')
def test_search(login):
token,username = login
print(f'token:{token},username:{username}')
print("搜索")
?fixture在自動(dòng)化中的應(yīng)用-數(shù)據(jù)共享
場(chǎng)景:
你與其他測(cè)試工程師合作一起開(kāi)發(fā)時(shí),公共的模塊要在不同文件中,要在大家都訪問(wèn)到的地方解決:
使用conftest.py這個(gè)文件進(jìn)行數(shù)據(jù)共享,并且它可以放在不同位置起著不同范圍的共享作用前提:
conftest文件名是不能換的放在項(xiàng)目下是全局的數(shù)據(jù)共享的地方執(zhí)行:
系統(tǒng)執(zhí)行到參數(shù)login時(shí)先從本模塊中查找是否有這個(gè)名字的變量之后在conftest.py中找是否有步驟:
將登陸模塊帶@pytest.fixture寫在conftest.py
?conftest.py
import pytest
@pytest.fixture()
def login():
# setup操作
print("登陸操作")
token = '123456'
username = 'hogwarts'
yield token, username#相當(dāng)于return
#teardown操作
print('完成登出操作')
test_fixturedemo1.py
def test_search(login):
token,username = login
print(f'token:{token},username:{username}')
print("搜索")
def test_cart(login):
print("購(gòu)物車")
def test_order(login):
print("購(gòu)物車")
?fixture在自動(dòng)化中的應(yīng)用-自動(dòng)應(yīng)用
場(chǎng)景:
不想原測(cè)試方法有任何改動(dòng),或全部都自動(dòng)實(shí)現(xiàn)自動(dòng)應(yīng)用,沒(méi)特例,也都不需要返回值時(shí)可以選擇自動(dòng)應(yīng)用解決:
使用fixture中參數(shù)autouse=True實(shí)現(xiàn)步驟:
在放上面加@pytest.fixture(autouse = True)
conftest.py
import pytest
@pytest.fixture(scope="function",autouse=True)
def login():
# setup操作
print("登陸操作")
token = '123456'
username = 'hogwarts'
yield token, username#相當(dāng)于return
#teardown操作
print('完成登出操作')
?test_fixturedemo1.py
def test_search(login):
token,username = login
print(f'token:{token},username:{username}')
print("搜索")
def test_cart():
print("購(gòu)物車")
def test_order():
print("購(gòu)物車")
在test方法中沒(méi)有調(diào)用login參數(shù)的測(cè)試用例,也會(huì)默認(rèn)加了login,在執(zhí)行每個(gè)測(cè)試用例之前,都會(huì)執(zhí)行一遍登入登出操作
fixture在自動(dòng)化中的應(yīng)用-參數(shù)化
場(chǎng)景:
測(cè)試離不開(kāi)數(shù)據(jù),為了數(shù)據(jù)靈活,一般數(shù)據(jù)都是通過(guò)參數(shù)傳的解法
fixture通過(guò)固定參數(shù)request傳遞步驟
在fixture中增加@pytest.fixture(params=[1,2,3,'linda'])在方法參數(shù)寫request,方法體里面使用request.param接收參數(shù)
import pytest
@pytest.fixture(params=["selenium","appium"])
def login(request):
print(f"用戶名:{request.param}")
return request.param
def test_demo1(login):
print(f"demo1 case:數(shù)據(jù)為:{login}")
運(yùn)行結(jié)果是:
/Users/cangqiongqiyuyunxi/PycharmProjects/pythonProject/venv/bin/python /Applications/PyCharm CE.app/Contents/plugins/python-ce/helpers/pycharm/_jb_pytest_runner.py --target test_fixture_param.py::test_demo1
Testing started at 07:48 ...
Launching pytest with arguments test_fixture_param.py::test_demo1 --no-header --no-summary -q in /Users/cangqiongqiyuyunxi/PycharmProjects/pythonProject/test_fixture
============================= test session starts ==============================
collecting ... collected 2 items
test_fixture_param.py::test_demo1[selenium] 用戶名:selenium
PASSED [ 50%]demo1 case:數(shù)據(jù)為:selenium
test_fixture_param.py::test_demo1[appium] 用戶名:appium
PASSED [100%]demo1 case:數(shù)據(jù)為:appium
============================== 2 passed in 0.01s ===============================
Process finished with exit code 0
使用?yield可以讓每條測(cè)試用例執(zhí)行yield后的語(yǔ)句,return的話將不會(huì)執(zhí)行return后的語(yǔ)句
import pytest
@pytest.fixture(params=["selenium","appium"])
def login(request):
print(f"用戶名:{request.param}")
yield request.param
print("完成登出操作")
def test_demo1(login):
print(f"demo1 case:數(shù)據(jù)為:{login}")
運(yùn)行結(jié)果是:
/Users/cangqiongqiyuyunxi/PycharmProjects/pythonProject/venv/bin/python /Applications/PyCharm CE.app/Contents/plugins/python-ce/helpers/pycharm/_jb_pytest_runner.py --path /Users/cangqiongqiyuyunxi/PycharmProjects/pythonProject/test_fixture/test_fixture_param.py
Testing started at 07:49 ...
Launching pytest with arguments /Users/cangqiongqiyuyunxi/PycharmProjects/pythonProject/test_fixture/test_fixture_param.py --no-header --no-summary -q in /Users/cangqiongqiyuyunxi/PycharmProjects/pythonProject/test_fixture
============================= test session starts ==============================
collecting ... collected 2 items
test_fixture_param.py::test_demo1[selenium] 用戶名:selenium
PASSED [ 50%]demo1 case:數(shù)據(jù)為:selenium
完成登出操作
test_fixture_param.py::test_demo1[appium] 用戶名:appium
PASSED [100%]demo1 case:數(shù)據(jù)為:appium
完成登出操作
============================== 2 passed in 0.01s ===============================
Process finished with exit code 0
pytest.ini是什么
pytest.ini是pytest的配置文件可以修改pytest的默認(rèn)行為不能使用任何中文符號(hào),包括漢字、空格、引號(hào)、冒號(hào)等等
pytest.ini
修改用例的命名規(guī)則配置日志格式,比代碼配置更方便添加標(biāo)簽,防止運(yùn)行過(guò)程報(bào)警告錯(cuò)誤制定執(zhí)行目錄排除搜索目錄
pytest配置-改變運(yùn)行規(guī)則
pytest.ini ? ? ? ? ? ? //是用來(lái)配置哪些命名方式的函數(shù)、類或者python文件可以運(yùn)行check_demo.py //以check_開(kāi)頭命名的測(cè)試文件test_first.py ? ? ? //以test_開(kāi)頭命名的測(cè)試文件
pytest.ini
[pytest]
;執(zhí)行check_開(kāi)頭和 test_開(kāi)頭的所有的文件,后面一定要加*
python_files = check_* test_*
;執(zhí)行所有的以Test和Check開(kāi)頭的類
python_classes = Test* Check*
;執(zhí)行所有以test_和check_開(kāi)頭的方法
python_functions= test_* check_*
check_demo.py //check開(kāi)頭的類及函數(shù)也是需要執(zhí)行
class CheckDemo:
def check_demo1(self):
pass
def check_demo2(self):
pass
?test_first.py //test開(kāi)頭的文件名/函數(shù)名/類名也是需要執(zhí)行
import logging
def inc(x):
return x + 1
def test_answer():
logging.info("這是 answer 測(cè)試用例")
logging.info("斷言 assert inc(3) == 5 ")
assert inc(3) == 5
class TestDemo:
def test_demo1(self):
logging.info("這是 demo1 測(cè)試用例")
pass
def test_demo2(self):
logging.info("這是 demo2 測(cè)試用例")
pass
在pytest.ini中添加
addopts = -v -s --alluredir=./results
在終端中鍵入 pytest -vs可以將所有的測(cè)試結(jié)果打印出來(lái)
?
(venv) cangqiongqiyuyunxi@cangqiongqiyuyunxideMacBook-Air testini % pytest -vs
==================================================== test session starts =====================================================
platform darwin -- Python 3.9.6, pytest-7.4.0, pluggy-1.3.0 -- /Users/cangqiongqiyuyunxi/PycharmProjects/pythonProject/venv/bin/python
cachedir: .pytest_cache
rootdir: /Users/cangqiongqiyuyunxi/PycharmProjects/pythonProject/testini
configfile: pytest.ini
collected 3 items
test_first.py::test_answer FAILED
test_first.py::TestDemo::test_demo1 PASSED
test_first.py::TestDemo::test_demo2 PASSED
========================================================== FAILURES ==========================================================
________________________________________________________ test_answer _________________________________________________________
def test_answer():
logging.info("這是 answer 測(cè)試用例")
logging.info("斷言 assert inc(3) == 5 ")
> assert inc(3) == 5
E assert 4 == 5
E + where 4 = inc(3)
test_first.py:12: AssertionError
================================================== short test summary info ===================================================
FAILED test_first.py::test_answer - assert 4 == 5
================================================ 1 failed, 2 passed in 0.04s =================================================
pytest配置-指定/忽略執(zhí)行目錄
;設(shè)置執(zhí)行的路徑
;testpaths = bilibili baidu
;忽略某些文件夾/目錄
norecursedirs = result logs datas test_demo*
pytest logging收集日志
[pytest]
;日志開(kāi)關(guān) true false
log_cli = true
;日志級(jí)別
log_cli_level = info
;打印詳細(xì)日志,相當(dāng)于命令行加 -vs
addopts = --capture=no
;日志格式
log_cli_format = %(asctime)s [%(levelname)s] %(message)s (%(filename)s:%(lineno)s)
;日志時(shí)間格式
log_cli_date_format = %Y-%m-%d %H:%M:%S
;日志文件位置
log_file = ./log/test.log
;日志文件等級(jí)
log_file_level = info
;日志文件格式
log_file_format = %(asctime)s [%(levelname)s] %(message)s (%(filename)s:%(lineno)s)
;日志文件日期格式
log_file_date_format = %Y-%m-%d %H:%M:%S
log_file = ./log/test.log會(huì)將logging的輸出存放在test.log中
pytest插件開(kāi)發(fā)[進(jìn)階]
pytest插件分類
外部插件:pip install 安裝的插件本地插件:pytest自動(dòng)模塊發(fā)現(xiàn)機(jī)制(conftest.py存放的)內(nèi)置插件:代碼內(nèi)部的_pytest目錄加載pytest hook介紹pytest hook執(zhí)行順序pytest常用插件
pip install pytest-ordering 控制用例的執(zhí)行順序(重點(diǎn))
pip install pytest-xdist 分布式并發(fā)執(zhí)行測(cè)試用例(重點(diǎn))
pip install pytest-dependency 控制用例的依賴關(guān)系 (了解)
pip install pytest-rerunfailures 失敗重跑(了解)
pip install pytest-assume 多重較驗(yàn)(了解)
pip install pytest-random-order 用例隨機(jī)執(zhí)行(了解)
pip install pytest-html 測(cè)試報(bào)告(了解) 可以通過(guò)???????PyPI · The Python Package Index 搜索pytest看相關(guān)的插件 pytest執(zhí)行順序控制
場(chǎng)景:
對(duì)于集成測(cè)試,經(jīng)常會(huì)有上下文依賴關(guān)系的測(cè)試用例對(duì)于10個(gè)步驟,拆成10條case,這時(shí)候能知道到底執(zhí)行到哪步報(bào)錯(cuò)用例默認(rèn)執(zhí)行順序:自上而下執(zhí)行解決:
可以通過(guò)setup,teardown和fixture來(lái)解決,也可以使用對(duì)應(yīng)的插件安裝:pip install pytest-ordering用法:@pytest.mark.run(order = 2)注意:多個(gè)插件裝飾器(>2)的時(shí)候,有可能會(huì)發(fā)生沖突?
pytest并行與分布式執(zhí)行
場(chǎng)景 1:
測(cè)試用例 1000 條,一個(gè)用例執(zhí)行 1 分鐘,一個(gè)測(cè)試人員執(zhí)行需要 1000 分鐘。
通常我們會(huì)用人力成本換取時(shí)間成本,加幾個(gè)人一起執(zhí)行,時(shí)間就會(huì) 縮短。
如果 10 人一起執(zhí)行只需要 100 分鐘,這就是一種分布式場(chǎng)景。
場(chǎng)景 2:
假設(shè)有個(gè)報(bào)名系統(tǒng),對(duì)報(bào)名總數(shù)統(tǒng)計(jì),數(shù)據(jù)同時(shí)進(jìn)行修改操作的時(shí)候有可能出現(xiàn)問(wèn)題,
需要模擬這個(gè)場(chǎng)景,需要多用戶并發(fā)請(qǐng)求數(shù)據(jù)。
解決:
使用分布式并發(fā)執(zhí)行測(cè)試用例。分布式插件:pytest-xdist
安裝及運(yùn)行:?pip install pytest-xdist
注意:?用例多的時(shí)候效果明顯,多進(jìn)程并發(fā)執(zhí)行,同時(shí)支持 allure
from time import sleep
import pytest
def test_foo():
sleep(1)
assert True
def test_bar():
sleep(1)
assert True
def test_bar1():
sleep(1)
assert True
def test_foo1():
sleep(1)
assert True
def test_bar2():
sleep(1)
assert True
def test_bar3():
sleep(1)
assert True
在終端中執(zhí)行pytest -n auto -vs將會(huì)分布式執(zhí)行我們的測(cè)試用例
如果串型執(zhí)行我們的測(cè)試用例 總共會(huì)用6s的執(zhí)行時(shí)間
?
?如果分布式執(zhí)行我們的測(cè)試用例 我們將大大縮短執(zhí)行我們測(cè)試用例的時(shí)間
?pytest hook介紹
hook是一個(gè)勾子函數(shù)
?hook是個(gè)函數(shù),在系統(tǒng)消息觸發(fā)時(shí)被系統(tǒng)調(diào)用自動(dòng)觸發(fā)機(jī)制hook函數(shù)的名稱是確定的pytest有非常多的勾子函數(shù)使用時(shí)直接編寫函數(shù)體
root
└── pytest_cmdline_main
├── pytest_plugin_registered
├── pytest_configure
│ └── pytest_plugin_registered
├── pytest_sessionstart
│ ├── pytest_plugin_registered
│ └── pytest_report_header
├── pytest_collection #在收集測(cè)試用例時(shí)就需要hook
│ ├── pytest_collectstart
│ ├── pytest_make_collect_report
│ │ ├── pytest_collect_file
│ │ │ └── pytest_pycollect_makemodule
│ │ └── pytest_pycollect_makeitem
│ │ └── pytest_generate_tests
│ │ └── pytest_make_parametrize_id
│ ├── pytest_collectreport
│ ├── pytest_itemcollected
│ ├── pytest_collection_modifyitems
│ └── pytest_collection_finish
│ └── pytest_report_collectionfinish
├── pytest_runtestloop
│ └── pytest_runtest_protocol
│ ├── pytest_runtest_logstart
│ ├── pytest_runtest_setup
│ │ └── pytest_fixture_setup
│ ├── pytest_runtest_makereport
│ ├── pytest_runtest_logreport
│ │ └── pytest_report_teststatus
│ ├── pytest_runtest_call #將會(huì)在調(diào)用測(cè)試用例時(shí)實(shí)現(xiàn)
│ │ └── pytest_pyfunc_call
│ ├── pytest_runtest_teardown
│ │ └── pytest_fixture_post_finalizer
│ └── pytest_runtest_logfinish
├── pytest_sessionfinish
│ └── pytest_terminal_summary
└── pytest_unconfigure
原始鏈接
pytest官方關(guān)于hook的說(shuō)明
https://docs.pytest.org/en/stable/reference.html?#hooks 134
https://docs.pytest.org/en/stable/_modules/_pytest/hookspec.html#pytest_cmdline_parse 76
流程說(shuō)明原始鏈接
https://github.com/pytest-dev/pytest/issues/3261 81
找到hook的源碼:python3.9/site-packages/_pytest/hookspec.py
conftest.py //將setup和teardown引入在測(cè)試用例前后進(jìn)行調(diào)用
from typing import Optional
def pytest_runtest_setup(item: "Item") -> None:
print("setup")
def pytest_runtest_teardown(item: "Item", nextitem: Optional["Item"]) -> None:
print("teardown")
test_hook.py // 實(shí)際跑的測(cè)試用例
def test_demo1():
print("test hook")
?hook函數(shù)總結(jié)
hook函數(shù)名字固定hook函數(shù)會(huì)被自動(dòng)執(zhí)行執(zhí)行時(shí)有先后順序的pytest定義了很多hook函數(shù),可以在不同階段實(shí)現(xiàn)不同的功能
柚子快報(bào)邀請(qǐng)碼778899分享:pytest
推薦閱讀
本文內(nèi)容根據(jù)網(wǎng)絡(luò)資料整理,出于傳遞更多信息之目的,不代表金鑰匙跨境贊同其觀點(diǎn)和立場(chǎng)。
轉(zhuǎn)載請(qǐng)注明,如有侵權(quán),聯(lián)系刪除。