前言:
接口自動化是指模擬程序接口層面的自動化,由於接口不易變更,維護成本更小,所以深受各大公司的喜愛。
第一版入口:接口自動化框架(Pytest+request+Allure)
本次版本做了一些升級,增加了自動生成testcase等,一起來看看吧!~~
一、簡單介紹
環境:Mac+Python 3+Pytest+Allure+Request
pytest==3.6.0
pytest-allure-adaptor==1.7.10
pytest-rerunfailures==5.0
allure-python-commons==2.7.0
configparser==3.5.0
PyYAML==3.12
requests==2.18.4
simplejson==3.16.0
流程:Charles導出接口數據-自動生成測試用例-修改測試用例-執行測試用例-生成Allure報告
開源: 點擊這裏,跳轉到github
備註⚠️:Charles導出接口應選擇文件類型爲JSON Session File(.chlsj)
重要模塊介紹:
1、writeCase.py :自動讀取新的Charles文件,並自動生成測試用例
2、apiMethod.py:封裝request方法,可以支持多協議擴展(get\post\put)
3、checkResult.py:封裝驗證response方法
4、setupMain.py: 核心代碼,定義並執行用例集,生成報告
⚠️2020-5-29更新:
pytest-allure-adaptor已棄用,更換爲allure-pytest
pip uninstall pytest-allure-adaptor
pip install allure-pytest
代碼已更新。 點擊這裏,跳轉到github
__
二、目錄介紹
三、代碼分析
1、測試數據yml(自動生成的yml文件)
# 用例基本信息
test_info:
# 用例標題,在報告中作爲一級目錄顯示
title: blogpost
# 用例ID
id: test_reco_01
# 請求的域名,可寫死,也可寫成模板關聯host配置文件
host: ${host}$
# 請求地址 選填(此處不填,每條用例必填)
address: /api/v2/recomm/blogpost/reco
# 前置條件,case之前需關聯的接口
premise:
# 測試用例
test_case:
- test_name: reco_1
# 第一條case,info可不填
info: reco
# 請求協議
http_type: https
# 請求類型
request_type: POST
# 參數類型
parameter_type: application/json
# 請求地址
address: /api/v2/recomm/blogpost/reco
# 請求頭
headers:
# parameter爲文件路徑時
parameter: reco.json
# 是否需要獲取cookie
cookies: False
# 是否爲上傳文件的接口
file: false
# 超時時間
timeout: 20
# 校驗列表 list or dict
# 不校驗時 expected_code, expected_request 均可不填
check:
expected_request: result_reco.json
check_type: only_check_status
expected_code: 503
# 關聯鍵
relevance:
2、測試case(自動生成的case.py)
@allure.feature(case_dict["test_info"]["title"])
class TestReco:
@pytest.mark.parametrize("case_data", case_dict["test_case"], ids=[])
@allure.story("reco")
@pytest.mark.flaky(reruns=3, reruns_delay=3)
def test_reco(self, case_data):
"""
:param case_data: 測試用例
:return:
"""
self.init_relevance = ini_request(case_dict, PATH)
# 發送測試請求
api_send_check(case_data, case_dict, self.init_relevance, PATH)
3、writeCase.py (封裝方法:自動生成測試case)
def write_case(_path):
yml_list = write_case_yml(_path)
project_path = str(os.path.abspath('.').split('/bin')[0])
test_path = project_path+'/aff/testcase/'
src = test_path+'Template.py'
for case in yml_list:
yml_path = case.split('/')[0]
yml_name = case.split('/')[1]
case_name = 'test_' + yml_name + '.py'
new_case = test_path + yml_path + '/' + case_name
mk_dir(test_path + yml_path)
if case_name in os.listdir(test_path + yml_path):
pass
else:
shutil.copyfile(src, new_case)
with open(new_case, 'r') as fw:
source = fw.readlines()
n = 0
with open(new_case, 'w') as f:
for line in source:
if 'PATH = setupMain.PATH' in line:
line = line.replace("/aff/page/offer", "/aff/page/%s" % yml_path)
f.write(line)
n = n+1
elif 'case_dict = ini_case' in line:
line = line.replace("Template", yml_name)
f.write(line)
n = n + 1
elif 'class TestTemplate' in line:
line = line.replace("TestTemplate", "Test%s" % yml_name.title().replace("_", ""))
f.write(line)
n = n + 1
elif '@allure.story' in line:
line = line.replace("Template", yml_name)
f.write(line)
n = n + 1
elif 'def test_template' in line:
line = line.replace("template", yml_name.lower())
f.write(line)
n = n + 1
else:
f.write(line)
n += 1
for i in range(n, len(source)):
f.write(source[i])
4、apiMethod.py(封裝方法:http多協議)
def post(header, address, request_parameter_type, timeout=8, data=None, files=None):
"""
post請求
:param header: 請求頭
:param address: 請求地址
:param request_parameter_type: 請求參數格式(form_data,raw)
:param timeout: 超時時間
:param data: 請求參數
:param files: 文件路徑
:return:
"""
if 'form_data' in request_parameter_type:
for i in files:
value = files[i]
if '/' in value:
file_parm = i
files[file_parm] = (os.path.basename(value), open(value, 'rb'))
enc = MultipartEncoder(
fields=files,
boundary='--------------' + str(random.randint(1e28, 1e29 - 1))
)
header['Content-Type'] = enc.content_type
response = requests.post(url=address, data=enc, headers=header, timeout=timeout)
else:
response = requests.post(url=address, data=data, headers=header, timeout=timeout, files=files)
try:
if response.status_code != 200:
return response.status_code, response.text
else:
return response.status_code, response.json()
except json.decoder.JSONDecodeError:
return response.status_code, ''
except simplejson.errors.JSONDecodeError:
return response.status_code, ''
except Exception as e:
logging.exception('ERROR')
logging.error(e)
raise
5、checkResult.py(封裝方法:校驗response結果)
def check_result(test_name, case, code, data, _path, relevance=None):
"""
校驗測試結果
:param test_name: 測試名稱
:param case: 測試用例
:param code: HTTP狀態
:param data: 返回的接口json數據
:param relevance: 關聯值對象
:param _path: case路徑
:return:
"""
# 不校驗結果
if case["check_type"] == 'no_check':
with allure.step("不校驗結果"):
pass
# json格式校驗
elif case["check_type"] == 'json':
expected_request = case["expected_request"]
if isinstance(case["expected_request"], str):
expected_request = readExpectedResult.read_json(test_name, expected_request, _path, relevance)
with allure.step("JSON格式校驗"):
allure.attach("期望code", str(case["expected_code"]))
allure.attach('期望data', str(expected_request))
allure.attach("實際code", str(code))
allure.attach('實際data', str(data))
if int(code) == case["expected_code"]:
if not data:
data = "{}"
check_json(expected_request, data)
else:
raise Exception("http狀態碼錯誤!\n %s != %s" % (code, case["expected_code"]))
# 只校驗狀態碼
elif case["check_type"] == 'only_check_status':
with allure.step("校驗HTTP狀態"):
allure.attach("期望code", str(case["expected_code"]))
allure.attach("實際code", str(code))
allure.attach('實際data', str(data))
if int(code) == case["expected_code"]:
pass
else:
raise Exception("http狀態碼錯誤!\n %s != %s" % (code, case["expected_code"]))
# 完全校驗
elif case["check_type"] == 'entirely_check':
expected_request = case["expected_request"]
if isinstance(case["expected_request"], str):
expected_request = readExpectedResult.read_json(test_name, expected_request, _path, relevance)
with allure.step("完全校驗"):
allure.attach("期望code", str(case["expected_code"]))
allure.attach('期望data', str(expected_request))
allure.attach("實際code", str(code))
allure.attach('實際data', str(data))
if int(code) == case["expected_code"]:
result = operator.eq(expected_request, data)
if result:
pass
else:
raise Exception("完全校驗失敗! %s ! = %s" % (expected_request, data))
else:
raise Exception("http狀態碼錯誤!\n %s != %s" % (code, case["expected_code"]))
# 正則校驗
elif case["check_type"] == 'Regular_check':
if int(code) == case["expected_code"]:
try:
result = ""
if isinstance(case["expected_request"], list):
for i in case[""]:
result = re.findall(i.replace("\"","\""), str(data))
allure.attach('校驗完成結果\n',str(result))
else:
result = re.findall(case["expected_request"].replace("\"", "\'"), str(data))
with allure.step("正則校驗"):
allure.attach("期望code", str(case["expected_code"]))
allure.attach('正則表達式', str(case["expected_request"]).replace("\'", "\""))
allure.attach("實際code", str(code))
allure.attach('實際data', str(data))
allure.attach(case["expected_request"].replace("\"", "\'") + '校驗完成結果',
str(result).replace("\'", "\""))
if not result:
raise Exception("正則未校驗到內容! %s" % case["expected_request"])
except KeyError:
raise Exception("正則校驗執行失敗! %s\n正則表達式爲空時" % case["expected_request"])
else:
raise Exception("http狀態碼錯誤!\n %s != %s" % (code, case["expected_code"]))
else:
raise Exception("無該校驗方式%s" % case["check_type"])
6、setupMain.py(執行用例集,生成測試報告)
def invoke(md):
output, errors = subprocess.Popen(md, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()
o = output.decode("utf-8")
return o
if __name__ == '__main__':
LogConfig(PATH)
write_case(har_path)
args = ['-s', '-q', '--alluredir', xml_report_path]
pytest.main(args)
cmd = 'allure generate %s -o %s' % (xml_report_path, html_report_path)
invoke(cmd)
7、測試報告
以上,喜歡的話請點贊❤️吧~
歡迎關注我的簡書,博客,TesterHome,Github~~~