本框架使用python+unittest+requests+httmrunner+excel+log+jenkins(ci部分待接入)实现的接口自动化框架.
- Git地址:http://git.edianzuno.cn/qa1/interface-testing
- 项目维护人:吴越
- python版本:3.12.3
interfaceTest // 测试框架名称
|-- api/ // 根据模块抽象出来的类和方法 方便在其他脚本中进行调用
| |-- shipment.py
|-- cases/ // 用例管理文件夹
| |-- console/ // 系统名称 中文英文均可
| | |-- 发货管理/ // 系统下的一个大功能模块 如华清的发货管理
| | | |-- 包裹查询/ // 大功能模块下的子功能模块 如 发货管理-包裹查询 模块需要与 excel 同名
| | | | |-- __init__.py
| | | | |-- test_select.py // 具体模块下的测试脚本 脚本的test_xxx.py xxx 部分与 excel 内sheet同名
| | | | |-- 包裹查询.xlsx // 此模块下测试用例数据 脚本和用例存放到相同的目录下进行管理 方便维护
| | | |-- 发货任务/
| | | |-- 快递查询/
| | | |-- 订单管理/
| | |-- __init__.py
| | |-- console.xlsx // 进入系统时候的一些接口用例 可以放到最外层管理
| | |-- test_getUserMenusByParentId.py
|-- common/ // 一些业务上的公共方法 如登录获取cookie等
| |-- __init__.py
| |-- common.py
|-- configs/ // 配置文件
| |-- email_style.txt // 邮件格式配置
| |-- pre_config.ini // PRE 环境配置
| |-- test_config.ini // TEST 环境配置
|-- files/ // 文件管理文件夹
| |-- download/ // 文件下载目录
| |-- pictures/ // readme.md 文件的图片
| |-- upload/ // 用例使用到的 file 文件存放路径
|-- reports/ // 测试报告目录
| |-- 2024-10-17 16-01-47/ // 每次执行的测试报告目录 存放 .html 报告和 .log 文件
|-- templates/ // 用例模板和脚本模板
| |-- template.xlsx // excel 维护的用例模板
| |-- test_template.py // 脚本模板
|-- utils/ // 公共方法库
| |-- asserts.py // 断言方法
| |-- config_db.py // 数据库链接以及配置
| |-- config_dingding.py // 钉钉通知
| |-- config_email.py // 邮件发送
| |-- data_decryptor.py // 加密数据解密
| |-- excel_parser.py // excel 数据处理
| |-- log.py // 日志处理
| |-- report.py // 测试报告处理
| |-- request_control.py // http 请求控制 根据 excel 用例数据封装请求
| |-- wrappers.py // 各种装饰器方法 减少代码量
|-- read_config.py // 配置文件操作
|-- readme.md // readme 文件
|-- requirements.txt // requirements.txt 安装的包列表
|-- run.ini // 运行配置 是否开启 log
|-- run.py // 测试用例执行入口
|-- run_caselist.ini // 运行的用例列表beautifulsoup4==4.12.3
certifi==2024.8.30
charset-normalizer==3.3.2
et-xmlfile==1.1.0
Faker==30.3.0
HTMLTestRunner-rv==1.1.2
idna==3.10
Jinja2==3.1.4
jsonpath==0.82.2
jsonpath-ng==1.6.1
logzero==1.7.0
MarkupSafe==2.1.5
openpyxl==3.1.5
parameterized==0.9.0
ply==3.11
PyMySQL==1.1.1
pyparsing==3.1.4
python-dateutil==2.9.0.post0
requests==2.32.3
six==1.16.0
soupsieve==2.6
typing_extensions==4.12.2
urllib3==2.2.3- 安装python 3.12 版本.
- 安装pycharm 开发工具.
- 从 gitlab 上通过
git pull git@git.edianzuno.cn:qa1/interface-testing.git获取代码 - 安装依赖包
pip3 install requirements.txt
为了避免大家使用同一个主干master分支编写脚本,导致彼此之间相互影响,也方便公共库在维护后大家能及时更新和拉取,各个团队的自动化脚本采用分支开发的模式.一个系统有多个人共同维护的时候,代码都对相同一个自建分支去push和pull代码即可.
master
|-- dev_console_branch // 华清分支
|-- dev_hr_branch // 人力分支
|-- dev_crm_branch // CRM 分支
|-- dev_system_branch // XX 系统分支创建自己系统分支的步骤
- 创建自己分支
git checkout -b dev_console_branch - 关联创建远端分支
git push -u origin dev_console_branch这时在 Gitlab 上可以看到自己创建的分支
- 使用
git branch查看自己目前所在分支,如果dev_console_branch是绿色,就代表我们处于当前分支了
- 在自己的分支进行脚本开发,开发过程中尽量不要动utils/templates/run.py/read_config.py等公共使用的内容,有需要新增的功能或者发现框架BUG反馈给 @吴越 进行修改
从master合并代码的步骤
当一些公共方法或者框架上有一些 BUG 修复后需要更新的时候,可以从master上来拉取最新的代码
- 从远端的master拉取代码
git merge origin/master
| case_name | priority | method | uri | content_type | request_type | request_content | assertions | cleanup_needed |
|---|---|---|---|---|---|---|---|---|
| 用例名称 | 用例优先级 | 请求方法类型 | 请求接口的uri | 请求的content_type | 请求的类型 | 请求的具体数据 | 断言 | 是否需要进行teardown处理 |
- case_name:测试用例的名称,例如:TC-01-正常登录/TC-02-用户名错误
- priority:用例的优先级,选择项:P0~P4 用例执行会根据配置的优先级判断这条 case 是否执行
- method:请求方法,选择项:GET/POST 等
- uri:请求的uri,接口路径
- content_type:接口实际的请求content_type,当请求是上传文件的时候不需要指定content_type
- request_type:请求的参数类型.
- request_content:请求的实际数据,如果在F12中我们直接将[载荷—查看源代码]的数据拷贝过来就可以
针对 request_content 内的内容需要进行变化的,比如一个创建 SKU 的接口要求 SKU 的名称是变化,所以我们就需要确保 skuname 随机性和唯一性,这种情况如何来做?
正常请求:
request_content = {
"sku_name": "联想蓝色笔记本",
"sku_remark":"这个笔记本比较贵",
}
调整后:将sku_name
request_content = {
"sku_name": "{sku_name}",
"sku_remark":"这个笔记本比较贵",
}在测试脚本中对{}中的内容进行参数化替换:
- assertions:
断言请求采取了使用与assert类相同的方式,虽然有些难以记忆,但框架针对断言部分会相对好实现一些.框架支持了几乎所有的断言方式,包括:assertEqual assertNotEqual assertIn 等常用断言.
断言在 Excel 内编写的时候需要特别注意:断言的书写不区分大小写,但字母顺序不能错,否则无法匹配.我用华清接口验证了一下,大部分接口都是可以使用assertEqual和assertIn完成断言的.
下面给出几个示例:
断言示例 1:返回JSON数据
response_data = {
"status": 200,
"message": "operation successful",
"data": {
"user": {
"id": 12345,
"name": "Alice",
"roles": ["admin", "user"],
"profile": {
"age": 30,
"hobbies": ["reading", "cycling"],
"location": {"country": "USA", "state": "California"}
},
"tags": {"tag1": "A", "tag2": "B", "tag3": "C"},
"balance": 2000.00000001,
"threshold": 1000.0,
"list_value": [5, 10, 15],
"tuple_value": (1, 2, 3),
"set_value": {1, 2, 3},
"history": None,
},
"success": True,
}
}
asserts = """
assertEqual:$.status=200
assertEqual:$.data.user.roles[0]=admin
assertNotEqual:$.message=failed
assertSequenceEqual:$.data.user.roles=['admin', 'user']
assertListEqual:$.data.user.roles=['admin', 'user']
assertTupleEqual:$.data.user.tuple_value=(1, 2, 3)
assertSetEqual:$.data.user.set_value={1, 2, 3}
assertIn:operation successful
assertNotIn:error
assertDictEqual:$.data.user.tags={'tag1': 'A', 'tag2': 'B', 'tag3': 'C'}
assertCountEqual:$.data.user.tags={'tag1': 'A', 'tag2': 'B', 'tag3': 'C'}
assertMultiLineEqual:$.data.user.name=Alice
assertLess:$.data.user.profile.age=40
assertLessEqual:$.data.user.profile.age=30
assertGreater:$.data.user.profile.age=20
assertGreaterEqual:$.data.user.profile.age=30
assertIsNone:$.data.user.history
assertIsNotNone:$.data.user.id
assertIsInstance:$.data.user.id=int
assertNotIsInstance:$.data.user.id=str
assertIs:$.data.success=True
assertIsNot:$.data.success=False
assertAlmostEqual:$.data.user.balance=2000.0
assertNotAlmostEqual:$.data.user.balance=2000.0
"""断言示例 2:返回文本数据
response_data = """
<link rel="canonical" href="https://blog.csdn.net/care5752/article/details/104476355">
"""
asserts = """\
assertIn:104476355
"""断言示例 3:从返回list中断言多条数据
# 应用场景:如果一个查询接口是通过状态或者类型来查询结果的,需要断言所有查询结果只能包括这个状态或者类型,即可通过这种方式进行断言.
response_data = {
"code":0
"data":
"items":[
{"status":"已完成"},
{"status":"已完成"},
]
}
asserts = """
assertEqual:$.data.items[*]=已完成 #上述案例会断言成功
"""
response_data = {
"code":0
"data":
"items":[
{"status":"已完成"},
{"status":"未完成"},
]
}
asserts = """
assertEqual:$.data.items[*]=已完成 #上述断言会失败
"""- 在 PyCharm 中,点击
File>Settings(或Preferenceson macOS) - 在左侧导航栏中,选择
Editor>File and Code Templates - 在
File and Code Templates页面,点击+按钮以创建一个新的模板 - 为你的模板取一个名字,比如
TestTemplate - 在右侧的模板编辑框中,将 /templates/test_templates.txt 文件内容拷贝到模板内:
- 将class Template 替换成 class ${ClassName}
- 将test_template 替换成 class test_${MethodName}
- 应用并保存,后续在创建文件的时候可以使用这个模板,会询问替换${}内的值
import unittest
import os
import inspect
from parameterized import parameterized
import read_config as readConfig
from utils import excel_parser, request_control as rc
from common import common as bizCom
from utils.log import MyLog
from utils.wrappers import *
from utils.asserts import AssertionHandler
from faker import Faker
# 获取当前脚本的文件名(不带扩展名),脚本名称和Excel sheet页名称保持一致,否则找不到.
sheet_name = os.path.splitext(os.path.basename(inspect.getfile(inspect.currentframe())))[0].split('_', 1)[1]
# 获取当前脚本所在的目录名
script_dir = os.path.basename(os.path.dirname(inspect.getfile(inspect.currentframe())))
# 获取当前脚本所在文件夹下的测试用例数据文件,文件要与所在模块同名
file_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), f"{script_dir}.xlsx")
# 获取测试用例数据
case_xls = excel_parser.get_xls(file_path, sheet_name)
config = readConfig.ReadConfig()
# 获取优先级过滤器并转换为大写
priority_filter = config.get_priority_filter()
class ${ClassName}(unittest.TestCase):
def setUp(self):
"""
测试前的准备工作
"""
self.log = MyLog.get_log()
self.logger = self.log.get_logger()
self.cookies = bizCom.get_cookie()
self.info = None # 初始化信息
self.response = None # 初始化响应
self.fake = Faker(locale='zh_CN')
self.case_name = self.__str__().split('(')[1].split(')')[0]
self.params = {}
self.handler = AssertionHandler(self)
@parameterized.expand(case_xls)
@filter(priority_filter)
@retry()
@delay()
def test_${MethodName}(self, case_name, priority, method, uri,
content_type, request_type, request_content, assertions, cleanup_needed):
"""
case描述
"""
self.start_time = time.time()
self.assertions = assertions
self.cleanup_needed = cleanup_needed
# 第一步:设置请求头.因为请求头各个系统存在特殊的情况,比如token、cookie,所以需要单独在case内设置.
headers = {"Content-Type": content_type, "Cookie": self.cookies}
# 第二步:对请求内容进行替换,比如:时间、姓名、手机号等,确保请求是不重复的
self.params = {"sku_name": self.fake.word()}
request_content = rc.replace_placeholder(request_content, **self.params)
# 第三步:发送请求
self.response = rc.RequestControl(method, uri, headers, request_type, request_content).send_request()
# 第四步:检查结果
self.checkResult()
@skip_teardown_if_skipped
def tearDown(self):
"""
测试后的清理工作
"""
if self.cleanup_needed:
pass
# 第五步:记录执行结果
self.end_time = time.time()
self.log.build_case_line(self.case_name, self.handler.assert_status, self.start_time - self.end_time)
@decrypt_response
def checkResult(self):
"""
检查测试结果
self.info 在装饰器decrypt_response内已经进行了赋值并且进行了解密动作,后面self.info可以直接进行使用.
"""
print(self.info)
self.handler.fun_assert(self.info, self.assertions)
if __name__ == "__main__":
unittest.main()
- 创建文件的时候选择
new-TestTemplates - 选择模板后会弹出如下页面
- 点击 OK,生成脚本文件
测试框架不支持直接使用 Excel 组织单接口进行接口串联组成自动化测试场景,主要考虑到使用 Excel 管理场景用例复杂度高并且灵活度比较差,不利于脚本维护.场景用例的组织和实现如下案例.
- 在 api 文件夹内按照功能模块对接口进行管理,如华清的[发货管理]模块
deliver_management # 发货管理
deliver_express.py # 快递查询
saas_order.py # 订单管理
saas_work_order_job.py # 发货任务- 将页面接口抽象成类 集成base_class并将接口封装成方法,按需进行数据处理和返回
- 然后在cases目录下按照unittest的形式去组织场景用例 实现自己的场景串联
在执行自动化测试中有很多场景需要进行前置的数据准备,可以将数据构造脚本封装到数据构造平台,来简化自动化场景.数据构造平台的调用方式如下:
- 在数据构造平台实现构造场景
- 对数据构造场景进行RPC调用或者参数组合调用
- 外部平台的脚本调用和数据处理存放在 /common/datafactory 文件夹下
测试用例的执行管理是在 run_caselist.ini 文件
- [admin] 为模块名
- case 名称 为 test_xxx 或者 test_xxx.py 均可
- 当 case 前被标记成 ; 的时候代表不执行这条 case
- 如果文件为空,默认执行所有用例
[admin]
;test_password.py
[procurement]
test_queryProcureApplyList_list
[recycle]
test_doExpressNoticeList
test_express_list
test_modifyNoticeExpressRemark
test_toIndex
[shipment]
test_edit
test_express_query
test_getSystemRemarkByOrderId
test_getUserMenusByParentId
test_import
test_modifyDetailReceiver
test_modifySystemRemark
test_orderDetailIndex
test_query
test_queryAll
test_queryProvinceAll
test_saasFailureDevice_adjust
test_saasFailureDevice_getList
test_saasFailureDevice_recover
test_select
test_view
[system]
test_importFromFile
test_select用例的执行入口在run.py,在执行 case 之前需要配置好环境配置和 run.ini 配置
x[EMAIL]
mail_host = smtp.exmail.qq.com
mail_user = wuyue1@edianyun.com
mail_pass = xxxxxxxx # 替换成自己的密码
mail_port = 465
sender = wuyue1@edianyun.com
receiver = wuyue1@edianyun.com # 邮件接收方,多人用 email1,email2 方式隔开
subject = 测试邮件
content = 测试邮件内容
on_off = off # 是否发送邮件开关 on 或者 off
[DINGDING]
on_off = off # 是否发送钉钉消息开关 on 或者 off
callback_url = https://oapi.dingtalk.com/robot/send?access_token=9635733cefcc9133a22c59f1d9ca4f33dd1ca52af13ca261d44c89bcd96126dd # 钉钉机器人回调地址 安全认证写【测试】
[HTTP]
scheme = https
;baseurl = test-hr.edianzu.cn
baseurl = test-console.edianzu.cn # 系统域名
port = 443
timeout = 10.0
[DATABASE]
host = test-database.edianzu.cn
username = db_public_w
password = Mysql_123
port = 3306
[ADMIN]
scheme = https
baseurl = test-admin.edianzu.cn # admin 系统域名
username = wuyue # admin 系统用户名
password = 11223344aA # admin 系统密码
[DECORATOR]
decrypt_url = https://test-data-security.edianzu.cn/data/decry # 加解密请求域名[ENV]
env = test # 读取哪个环境配置文件
# env = pre
# env = prod
[PRIORITY]
priority = # 要执行用例的优先级 不写默认全部执行.P0,P1,P2,P3,P4 逗号分割
[LOG]
on_off = on # 是否打印 log 文件测试报告会在 reports 目路下根据日期生成一个 测试报告目录.如果打开邮件发送测试报告,聚合测试报告会发送到 receiver











