Github地址 https://github.com/lixiaofeng1993/pytestProject
灵感来源
GitHub上的 pytestDemo 和 HttpRunner
目录结构
base ==>> requests请求,返回结果类,测试数据对象化封装
config ==>> 域名,固定变量,数据库链接
public ==>> 测试数据处理,全局变量替换,log,自定义异常类等公共方法
testcase ==>> 测试用例
data.yml ==>> 测试数据
parametrize_query.csv ==>> 参数化数据
用例设计
1.**局限于pytest参数化形式 `@pytest.mark.parametrize` ,每个测试用例只能对应一个参数化文件**
2.保证测试用例py文件的简洁,每个用例格式基本固定,代码量少
3.统一的YAML文件格式
4.参数化引用csv文件
用例基本格式
import pytest from public.send_request import SendRequest # 处理发送请求 from public.log import logger from public.sql_to_data import SqlToData # 处理测试数据 from public.help import get_data_path, os, fun_name, report_setting, report_step_setting, allure data_path = get_data_path(os.path.dirname(__file__)) # 返回当前 py 文件的绝对路径 test_params = SqlToData().yaml_db_query(data_path) # 返回对象化的测试数据 @allure.severity(allure.severity_level.TRIVIAL) # 测试类等级 @allure.epic(test_params.get("epic")) # allure报告一级目录 @allure.feature(test_params.get("feature")) # allure报告二级目录 class TestUsersCase: def setup_class(self): self.extract = {} # 全局变量
# 参数化用例格式 @pytest.mark.parametrize("data", test_params["test_register_user_case"].parametrize) # pytest参数化装饰器 def test_register_user_case(self, data): logger.info("*************** 开始执行用例 ***************") # 获取执行用例函数名 name = fun_name() # 报告展示的测试步骤 report_step_setting(test_params[name]) test_params[name].parametrize = data result, self.extract = SendRequest(test_params[name], self.extract).send_request() # 报告上展示的测试标题等 report_setting(test_params[name]) logger.info("*************** 结束执行用例 ***************\n")
# 有依赖的参数化用例格式
@pytest.mark.parametrize("data", test_params["test_one_user_case"].parametrize) def test_one_user_case(self, data): logger.info("*************** 开始执行用例 ***************") # 获取执行用例函数名 name = fun_name() # 报告展示的测试步骤 report_step_setting(test_params[name].case_step_1) # 登录接口 result, self.extract = SendRequest(test_params[name].case_step_1, self.extract).send_request() report_step_setting(test_params[name]) test_params[name].parametrize = data result, self.extract = SendRequest(test_params[name], self.extract).send_request() # 报告上展示的测试标题等 report_setting(test_params[name]) logger.info("*************** 结束执行用例 ***************\n")
# 非参数化用例格式 def test_all_user_case(self, test_data): logger.info("*************** 开始执行用例 ***************") # 报告展示的测试步骤 report_step_setting(test_data.case_step_1) # 登录接口 result, self.extract = SendRequest(test_data.case_step_1, self.extract).send_request() report_step_setting(test_data.case_step_2) result, self.extract = SendRequest(test_data.case_step_2, self.extract).send_request() # 报告上展示的测试标题等 report_setting(test_data.case_step_2) logger.info("*************** 结束执行用例 ***************\n")
单接口 YAML 文件参数化
test_register_user_case: path: /register method: post headers: validate: &validate - [ comparator: equal, check: msg, expect: "恭喜,注册成功!", jsonpath: "$.msg" ] validate_username: &validate_username - [ comparator: equal, check: msg, expect: "用户名/密码/手机号不能为空,请检查!!!", jsonpath: "$.msg" ] validate_username_exit: &validate_username_exit - [ comparator: contains, check: msg, expect: "用户名已存在", jsonpath: "$.msg" ] validate_phone: &validate_phone - [ comparator: contains, check: msg, expect: "手机号格式不正确", jsonpath: "$.msg" ] validate_sex: &validate_sex - [ comparator: contains, check: msg, expect: "输入的性别只能是 0(男) 或 1(女)", jsonpath: "$.msg" ] validate_phone_exit: &validate_phone_exit - [ comparator: contains, check: msg, expect: "手机号已被注册", jsonpath: "$.msg" ] parametrize: - [ username: __name, password: "123456", sex: "__random_int(0, 1)", telephone: __phone, address: __address, validate: *validate ] - [ username: "", password: "123456", sex: "__random_int(0, 1)", telephone: __phone, address: __address, validate: *validate_username ] - [ username: sql_one_user, password: "123456", sex: "__random_int(0, 1)", telephone: __phone, address: __address, validate: *validate_username_exit ] - [ username: __name, password: "123456", sex: "__random_int(0, 1)", telephone: __random_int, address: __address, validate: *validate_phone ] - [ username: __name, password: "123456", sex: "__random_int(2, 9)", telephone: __phone, address: __address, validate: *validate_sex ] - [ username: __name, password: "123456", sex: "__random_int(0, 1)", telephone: sql_one_phone, address: __address, validate: *validate_phone_exit ] params: upload: extract: story: 用例-注册接口 title: 注册接口 step: 注册接口测试 description: 该用例是针对 注册接口 的测试 sql: sql_one_user: SELECT u.username from `user` u LIMIT 1 sql_one_phone: SELECT u.telephone from `user` u LIMIT 1 epic: 用户数据测试 feature: 测试Demo
单接口 CSV 文件参数化
test_register_user_case:
path: /register
method: post
headers:
parametrize: ${parametrize_register.csv}
params:
upload:
extract:
story: 用例-注册接口
title: 注册接口
step: 注册接口测试
description: 该用例是针对 注册接口 的测试
sql:
sql_one_user: SELECT u.username from `user` u LIMIT 1
sql_one_phone: SELECT u.telephone from `user` u LIMIT 1
epic: 用户数据测试
feature: 测试Demo
parametrize_register.csv文件数据
case_name,username,password,sex,telephone,address,,msg,code
注册成功,__name,123456,"__random_int(0, 1)",__phone, __address,,"{""comparator"": ""equal"",""expect"": ""恭喜,注册成功!"",""jsonpath"":""""}","{""comparator"": ""equal"",""expect"": ""0"",""jsonpath"":""""}"
用户名/密码/手机号不能为空,,123456,"__random_int(0, 1)",__phone, __address,,"{""comparator"": ""equal"",""expect"": ""用户名/密码/手机号不能为空,请检查!!!"",""jsonpath"":""""}","{""comparator"": ""equal"",""expect"": ""2001"",""jsonpath"":""""}"
用户名已存在,sql_one_user,123456,"__random_int(0, 1)",__phone, __address,,"{""comparator"": ""contains"",""expect"": ""用户名已存在"",""jsonpath"":""""}","{""comparator"": ""equal"",""expect"": ""2002"",""jsonpath"":""""}"
手机号格式不正确,__name,123456,"__random_int(0, 1)",__random_int, __address,,"{""comparator"": ""contains"",""expect"": ""手机号格式不正确"",""jsonpath"":""""}","{""comparator"": ""equal"",""expect"": ""2004"",""jsonpath"":""""}"
输入的性别格式错误,__name,123456,"__random_int(2, 9)",__phone, __address,,"{""comparator"": ""contains"",""expect"": ""输入的性别只能是 0(男) 或 1(女)"",""jsonpath"":""""}","{""comparator"": ""equal"",""expect"": ""2003"",""jsonpath"":""""}"
手机号已被注册,__name,123456,"__random_int(0, 1)",sql_one_phone, __address,,"{""comparator"": ""contains"",""expect"": ""手机号已被注册"",""jsonpath"":""""}","{""comparator"": ""equal"",""expect"": ""2005"",""jsonpath"":""""}"
有依赖的接口参数化
test_one_user_case: case_step_1: path: /login method: post headers: parametrize: params: json: username: sql_one_user password: "123456" upload: extract: token: $.login_info.token username: $.login_info.username validate: - [ comparator: equal, check: msg, expect: "恭喜,登录成功!", jsonpath: "$.msg" ] - [ comparator: equal, check: code, expect: 0, jsonpath: "$.code" ] story: 用例-登录接口 title: 登录接口 step: 登录接口测试 description: 该用例是针对 登录接口 的测试 path: /get/user method: get headers: token: $token username: $username validate: &validate - [ comparator: equal, check: msg, expect: "查询成功", jsonpath: "$.msg" ] - [ comparator: equal, check: code, expect: 0, jsonpath: "$.code" ] validate_username: &validate_username - [ comparator: equal, check: msg, expect: "查不到相关用户的信息", jsonpath: "$.msg" ] - [ comparator: equal, check: code, expect: 1004, jsonpath: "$.code" ] parametrize: - [ username: sql_one_user, validate: *validate ] - [ username: __name, validate: *validate_username ] params: upload: extract: story: 用例-查询指定用户信息接口 title: 查询指定用户信息接口 step: 查询指定用户信息接口测试 description: 该用例是针对 查询指定用户信息接口 的测试 sql: sql_one_user: SELECT u.username from `user` u LIMIT 1 epic: 用户数据测试 feature: 测试Demo
非参数化接口,存在依赖
test_all_user_case:
case_step_1:
path: /login
method: post
headers:
parametrize:
params:
json:
username: sql_one_user
password: "123456"
upload:
extract:
token: $.login_info.token
username: $.login_info.username
validate:
- [ comparator: equal, check: msg, expect: "恭喜,登录成功!", jsonpath: "$.msg" ]
- [ comparator: equal, check: code, expect: 0, jsonpath: "$.code" ]
story: 用例-登录接口
title: 登录接口
step: 登录接口测试
description: 该用例是针对 登录接口 的测试
case_step_2:
path: /users
method: get
headers:
token: $token
username: $username
parametrize:
params:
json:
upload:
validate:
- [ comparator: equal, check: msg, expect: "查询成功", jsonpath: "$.msg" ]
- [ comparator: equal, check: code, expect: 0, jsonpath: "$.code" ]
story: 用例-查询所有用户信息接口
title: 查询所有用户信息接口
step: 查询所有用户信息接口
description: 该用例是针对 查询所有用户信息接口 的测试
sql:
sql_one_user: SELECT u.username from `user` u LIMIT 1
epic: 用户数据测试
feature: 测试Demo
测试数据对象化封装
def object_data(test_data: dict, file_path: str, case_step_num=10): """ 封装测试数据为对象 :param test_data: 测试数据 :param file_path: 测试数据文件路径 :param case_step_num: 测试用例依赖接口数量 :return: 字典包含的数据对象 """ obj = dict() case_step_list = list() case_step_num = int(case_step_num) if str(case_step_num).isdigit() else 10 case_step_num = 10 if case_step_num < 10 else case_step_num for i in range(1, case_step_num + 1): case_step_list.append(f"case_step_{i}") for keys, values in test_data.items(): obj[keys] = ObjectData() if isinstance(values, dict): for key, value in values.items(): setattr(obj[keys], key, value) setattr(obj[keys], "file_path", file_path) if isinstance(value, dict): step = CaseStep() for k, v in value.items(): setattr(step, k, v) setattr(step, "file_path", file_path) if key in case_step_list: setattr(obj[keys], key, step) obj.update({ "epic": test_data.get("epic"), "feature": test_data.get("feature") }) return obj
allure报告