我在该项目采用的是关键字驱动测试的框架类型。首先创建如下几个目录common(公共模块)、config(公共配置)、logs(运行日志)、reports(测试报告)、resources(测试资源)、testcases(测试用例)、utils(工具脚本)。在接下来的环节我将详细介绍这几个模块的实现。
config文件夹下放有config.ini和conf.py。config.ini用来管理多套测试环境(dev、sit、uat),config.py用来管理项目目录的存取。
# config.ini
[WEB_SIT]
sit = icare
type = uat
sms_url = http://xx.sangfor.com
url = http://xx.sangfor.com
username = user
password = pass
db = sf-icare
db_host = 1.1.1.1
db_port = 3306
db_user = xx
db_pass = xx
[ICARE_DEV]
sit = icare
type = dev
sms_url = http://xx.sangfor.com
url = http://xx.sangfor.com
username = xx
password = xx
db = sf-icare
db_host = 2.2.2.2
db_port = 3306
db_user = xx
db_pass = xx
[ICARE_SIT]
sit = icare
type = sit
sms_url = http://xx.sangfor.com
url = http://xx.sangfor.com
username = xx
password = xx
db = sf-icare
db_host = 3.3.3.3
db_port = 3306
db_user = xx
db_pass = xx
[ICARE_UAT]
sit = icare
type = uat
sms_url = http://xx.sangfor.com
url = http://xx.sangfor.com
username = xx
password = xx
db = sf-icare
db_host = 4.4.4.4
db_port = 3306
db_user = xx
db_pass = xx
[PRM_SIT]
sit = prm
type = sit
url = https://xx.sangfor.com
username = xx
password = xx
[TOKEN]
token = /api/api-auth/oauth/user/token
[HEADERS]
user_agent = Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36
authorization = Bearer 4521eb70-8fb2-4747-a512-1d2aed596849
[ICARE_USER]
super_user = 13300,99896,94406,98923,43913,21401
#conf.py
import os
import uuid
from utils.times import dt_strftime
class ConfigManager(object):
# 项目目录
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
@property
def log_file(self):
"""日志目录"""
log_dir = os.path.join(self.BASE_DIR, 'logs', dt_strftime())
if not os.path.exists(log_dir):
os.makedirs(log_dir)
return os.path.join(log_dir, '{}.log'.format(dt_strftime('%Y%m%d')))
@property
def ini_file(self):
"""配置文件"""
ini_file = os.path.join(self.BASE_DIR, 'config', 'config.ini')
if not os.path.exists(ini_file):
raise FileNotFoundError("配置文件%s不存在!" % ini_file)
return ini_file
@property
def report_dir(self):
"""测试报告目录"""
report_dir = os.path.join(self.BASE_DIR, 'reports', dt_strftime('%Y%m%d'), str(uuid.uuid1()))
if not os.path.exists(report_dir):
os.makedirs(report_dir)
report_json_dir = os.path.join(report_dir, 'temp_jsonreport')
report_html_dir = os.path.join(report_dir, 'html')
if not os.path.exists(report_json_dir):
os.makedirs(report_json_dir)
if not os.path.exists(report_html_dir):
os.makedirs(report_html_dir)
dir_dict = {'report_json_dir': report_json_dir, 'report_html_dir':report_html_dir}
return dir_dict
def json_file(self, json_name):
"""json文件"""
json_file = os.path.join(self.BASE_DIR, 'resources', json_name)
if not os.path.exists(json_file):
raise FileNotFoundError("json文件%s不存在!" % json_file)
return json_file
def test_file(self, test):
"""上传下载测试文件"""
test = os.path.join(self.BASE_DIR, 'resources', test)
if not os.path.exists(test):
raise FileNotFoundError("测试文件%s不存在!" % test)
return test
cm = ConfigManager()
if __name__ == '__main__':
cm.report_html_dir
# pass
common目录下主要存放着各个业务模块的关键字封装(cass_step)和一些公用的场景,如连接数据库、读取配置文件内容、获取登录用户token、request请求封装。如下摘取片段:
# question_step.py
@allure.step("setup:检查问题单基本信息")
def check_q_info(self, re_info, q_data):
"""
1、取问题单的基本信息
2、取提交问题单时填写的基本信息
3、对比
:param re_info:get_question_info
:param q_data: {
"客户名称": "测试",
"客户联系人": "小李",
"客户联系电话": "13343427921",
"产品线": "aCMP",
"业务影响": "P21",
"问题分类": "产品技术问题",
"问题描述": "描述测试测试测试",
}
:return:
"""
try:
cust_name = re_info["customerName"]
contact_name = re_info["contactName"]
contact_phone = re_info["contactPhoneNumber"]
product = re_info["mainProductLine"]
descrip = re_info["questionDescription"]
cs.assert_result("包含", q_data["客户名称"], cust_name)
cs.assert_result("等于", contact_name, q_data["客户联系人"])
cs.assert_result("等于", contact_phone, q_data["客户联系电话"])
cs.assert_result("等于", product, q_data["产品线"])
cs.assert_result("等于", descrip, q_data["问题描述"])
except TypeError:
pytest.fail("该工单不存在!")
except AssertionError:
pytest.fail("检查工单信息和填写不一致!")
# request_url.py
class RequestMain:
def __init__(self):
self.session = requests.Session()
def request_main(self, method, url, params=None, data=None, payloads=None, headers=None, redirects=True, **kwargs):
"""
:param method: 请求方式
:param url: 请求地址
:param params: 字典或bytes,作为参数增加到url中
:param data: data类型传参,字典、字节序列或文件对象,作为Request的内容
:param payloads: json传参,作为Request的内容
:param headers: 请求头,字典
:param kwargs: 若还有其他的参数,使用可变参数字典形式进行传递
:return:
"""
ini = ReadConfig()
ini_headers = {
'User-Agent': ini.user_agent,
'Authorization': ini.auth,
}
# log.info(ini)
if headers is None:
self.session.headers.update(ini_headers)
else:
self.session.headers.update(headers)
# log.info('正在请求{}接口:{}'.format(method, url))
try:
"""
封装request请求,将请求方法、请求地址,请求参数、请求头等信息入参。
注 :verify: True/False,默认为True,认证SSL证书开关;cert: 本地SSL证书。如果不需要ssl认证,可将这两个入参去掉
allow_redirects:True/False,默认为True,是启动重定向
"""
re_data = self.session.request(method, url, params=params, data=data, json=payloads, headers=headers, allow_redirects=redirects, timeout=10, verify=False, **kwargs)
try:
log.info(self.session.headers["Content-Type"])
del self.session.headers['Content-Type']
except KeyError:
pass
if re_data.status_code != 200:
# 如果重定向,新连接在response headers里
if re_data.status_code == 302:
return re_data.headers
raise Exception(re_data.text)
result = re_data.text
# log.info("请求成功,返回结果{0}".format(result))
return json.loads(result)
except asyncio.exceptions.TimeoutError:
log.error("连接超时!")
pytest.fail("连接超时")
except Exception as e:
log.error("请求失败:{0}".format(e))
pytest.fail("请求失败:{0}".format(e))
记录运行过程的日志,用来调试用例。
INFO 2023-09-11 10:32:02,611 [question_step.py:135] 问题单创建成功:Q2023091100004
INFO 2023-09-11 10:32:02,723 [question_step.py:248] 问题单当前状态为{'main_status': 20, 'status': 2010}
INFO 2023-09-11 10:32:02,729 [question_step.py:285] 获取问题创建人为:w13300
INFO 2023-09-11 10:32:02,785 [common_step.py:62] 当前登陆人:w13300
INFO 2023-09-11 10:32:03,035 [conftest.py:71] 恢复配置中...
用来存放测试使用的接口数据json
存放测试用例(按业务场景串联起来的关键字)
# test_question.py
@pytest.mark.smoke
@allure.feature('问题管理')
@allure.title('新建问题单自己处理流程')
def test_question_my(self):
# 1、手动新建问题单,填写基本信息,选自己处理 - 请求受理,提交
q_no = qs.create_question(q_info, deal_type={"type": "自己处理", "info": "请求受理"})
q_data = qs.get_question_info(q_no)
# 2、检查问题单状态、责任人、问题来源
status = qs.get_q_status(q_data)
cs.assert_result("等于", status["status"], ts.trans_question_status("请求受理")[1])
cs.assert_result("等于", status["main_status"], ts.trans_question_status("请求受理")[0])
create_user = qs.get_question_handler(q_data, "问题创建人")
user1 = cs.login_user()["username"]
cs.assert_result("等于", create_user, user1)
# 3、检查问题单基本信息,客户名称、产品线、业务影响、版本号、问题描述、问题分类
qs.check_q_info(q_data, q_info)
# 4、写进展 - 问题确认解决,检查处理进展,检查短信
qs.write_next_plan(q_no, "问题确认解决")
# 5、点击短信里的超链接,检查H5页面
sms_dict = qs.get_sms_record(q_no)
cs.assert_result("大于", sms_dict["count"], 0)
sms_link = sms_dict["content"]
short_key = qs.request_sms_link(sms_link)
h5_progress_count = qs.h5_show_progress(short_key)
cs.assert_result("等于", h5_progress_count, 3)
# 6、H5页面评价,勾选四星提交,检查后台评价结果
score = 8
qs.sms_evaluation(q_no, score)
web_score = qs.get_eval_result(q_no)
cs.assert_result("等于", score, web_score)
使用了allure报告,看起来更美观。
为了测试用例的代码更简洁美观,将处理数据、字段取值转换等单独封装起来。
创建解析器,自定义命令行参数,让执行更简单
# main.py
def get_command():
"""
创建一个解析器,解析命令行参数
:return: args
"""
parser = argparse.ArgumentParser(description="解析命令行参数")
parser.add_argument("--env", "-e", help="环境配置-dev/sit/uat")
parser.add_argument("--module", "-m", help="用例模块-问题模块/项目模块")
parser.add_argument("--smoke", "-s", help="冒烟执行-1")
parser.add_argument("--test", "-t", help="测试调试-1")
args = parser.parse_args()
return args
# 命令行执行python .\main.py -e sit -m 问题模块 -s 1
# 参数-e对应测试环境:可输入dev/sit/uat,不填默认sit
# 参数-m对应测试模块:可输入问题模块/项目模块,不填默认执行全部
# 参数-s对应冒烟测试:可输入1,不填默认执行全部