1、前言
本框架支持多业务接口依赖,多进程执行,mysql 数据库断言和 接口响应断言,并且用例直接在yaml文件中维护,无需编写业务代码, 接口pytest框架生成allure报告,并且发送 企业微信通知/ 钉钉通知/ 邮箱通知/ 飞书通知,灵活配置。
2、实现功能
- 测试数据隔离, 实现数据驱动
- 支持多接口数据依赖: 如A接口需要同时依赖B、C接口的响应数据作为参数
- 数据库断言: 直接在测试用例中写入查询的sql即可断言,无需编写代码
- 动态多断言: 如接口需要同时校验响应数据和sql校验,支持多场景断言
- 自动生成用例代码: 测试人员在yaml文件中填写好测试用例, 程序可以直接生成用例代码,纯小白也能使用
- 代理录制: 支持代理录制,生成yaml格式的测试用例
- 统计接口的运行时长: 拓展功能,订制开关,可以决定是否需要使用
- 日志模块: 打印每个接口的日志信息,同样订制了开关,可以决定是否需要打印日志
- 钉钉、企业微信通知: 支持多种通知场景,执行成功之后,可选择发送钉钉、或者企业微信、邮箱通知
- 自定义拓展字段: 如用例中需要生成的随机数据,可直接调用
- 多线程执行
- 支持swagger接口文档转成yaml用例,节省用例编写时间
4、目录结构
├── common // 配置
│ ├── conf.yaml // 公共配置
│ ├── setting.py // 环境路径存放区域
├── data // 测试用例数据
├── File // 上传文件接口所需的文件存放区域
├── logs // 日志层
├── report // 测试报告层
├── test_case // 测试用例代码
├── utils // 工具类
│ └── assertion
│ └── assert_control.py // 断言
│ └── assert_type.py // 断言类型
│ └── cache_process // 缓存处理模块
│ └── cacheControl.py
│ └── redisControl.py
│ └── logUtils // 日志处理模块
│ └── logControl.py
│ └── logDecoratrol.py // 日志装饰器
│ └── runTimeDecoratrol.py // 统计用例执行时长装饰器
│ └── mysqlUtils // 数据库模块
│ └── get_sql_data.py
│ └── mysqlControl.py
│ └── noticUtils // 通知模块
│ └── dingtalkControl.py // 钉钉通知
│ └── feishuControl.py // 飞书通知
│ └── sendmailControl.py // 邮箱通知
│ └── weChatSendControl.py // 企业微信通知
│ └── otherUtils // 其他工具类
│ └── allureDate // allure封装
│ └── allure_report_data.py // allure报告数据清洗
│ └── allure_tools.py // allure 方法封装
│ └── error_case_excel.py // 收集allure异常用例,生成excel测试报告
│ └── localIpControl.py // 获取本地IP
│ └── threadControl.py // 定时器类
│ └── readFilesUtils // 文件操作
│ └── caseAutomaticControl.py // 自动生成测试代码
│ └── clean_files.py // 清理文件
│ └── excelControl.py // 读写excel
│ └── get_all_files_path.py // 获取所有文件路径
│ └── get_yaml_data_analysis.py // yaml用例数据清洗
│ └── regularControl.py // 正则
│ └── yamlControl.py // yaml文件读写
│ └── recordingUtils // 代理录制
│ └── mitmproxyContorl.py
│ └── requestsUtils
│ └── dependentCase.py // 数据依赖处理
│ └── requestControl.py // 请求封装
│ └── timeUtils
├── Readme.md // help
├── pytest.ini
├── run.py // 运行入口
5、依赖库
allure-pytest2.9.45
allure-python-commons2.9.45
atomicwrites1.4.0
attrs21.2.0
certifi2021.10.8
cffi1.15.0
charset-normalizer2.0.7
colorama0.4.4
colorlog6.6.0
cryptography36.0.0
DingtalkChatbot1.5.3
execnet1.9.0
Faker9.8.3
idna3.3
iniconfig1.1.1
jsonpath0.82
packaging21.3
pluggy1.0.0
py1.11.0
pycparser2.21
PyMySQL1.0.2
pyOpenSSL21.0.0
pyparsing3.0.6
pytest6.2.5
pytest-forked1.3.0
pytest-xdist2.4.0
python-dateutil2.8.2
PyYAML6.0
requests2.26.0
six1.16.0
text-unidecode1.3
toml0.10.2
urllib31.26.7
xlrd2.0.1
xlutils2.0.0
xlwt1.3.0
6、基础环境安装教程
首先,执行本框架之后,需要搭建好 python、jdk、 allure环境
搭建python教程:http://c.biancheng.net/view/4161.html
搭建jdk环境:
安装allure:
如上环境如都搭建好,则安装本框架的所有第三方库依赖,执行如下命令:
pip3 install -r requirements.txt
如果在安装过程中出现如下 Could not find a version
类似的异常, 不用担心,可能是因为你安装的python环境 版本和我不一致导致的,直接 pip install 库名称,不指定版本安装就可以了。
如上方截图说没有找到 asgiref==3.5.1
,报错的意思是,没有找到3.5.1这个版本,那么直接控制台输入 pip3 install asgiref
进行安装即可
7、创建用例
7.1、创建用例步骤
1、在data文件夹下方创建相关的yaml用例
2、写完之后,需要执行 utils\readFilesUtils\caseAutomaticControl.py 这个文件,生成自动化代码
3、执行caseAutomaticControl.py文件之后,会发现,在test_case层新增该条用例的对应代码,可直接执行该用例调试
4、注意,如果生成对应的测试代码之后,期间有更改过yaml用例中的内容,需要重新生成代码,必现因为更改yaml用例之后导致运行失败
5、当所有接口都编写好之后,可以直接运行run.py主程序,执行所有自动化接口
下面我们来看一下,如何创建用例:
7.1.1用例中相关字段介绍:
上方截图,就是一个用例中需要维护的相关字段,下面我会对每个字段的作用,做出解释。
7.2、如何发送请求(get为例):
上方了解了用例的数据结构之后,下面我们开始编写第一个get请求方式的接口。 首先,开始编写项目之后,我们在 conf.yaml 中配置项目的域名
域名配置好之后,我们来编写测试用例,在 data 文件下面,创建一个名称为 collect_tool_list.yaml 的用例文件。
get请求我们 requestType 写的是 params ,这样发送请求时,我们会将请求参数拼接中url中,最终像服务端发送请求的地址格式会为:
如: ${{host()}}/lg/collect/usertools/json?pageNum=1&pageSize=10
7.3、如何测试上传接口:
首先,我们将所有需要测试的文件,全部都放在 files 文件夹中:
requestType: file
#是否执行,空或者 true 都会执行
is_run:
data:
file:
xxx: 排入水体名.png
在yaml文件中,我们需要注意两个地方,主要是用例中的requestType、和 filename 字段:
- requestType: 上传文件,我们需要更改成 file
- file: 上传文件中,新增一个file关键字,在下方传我们需要的数据
- file_name: 首先,这个xxx是我们公司接口定义的上传文件的参数,排入水体名.png
这个是我们放在Files这个文件夹下方的文件名称 程序在执行的时候,会判断如果你的requestType为file的时候,则会去执行file下方的参数,然后取到文件名称直接去执行用例
上传文件接口,即需要上传文件,又需要上传其他参数时:
requestType: file
#是否执行,空或者 true 都会执行
is_run:
data:
file:
file_name: 排入水体名.png
data:
is_upload: 0
params:
collect: false
上方的这个案例,请求参数即上传了文件,又上传了其他参数
1、file: 这里下方上传的是文件参数
2、data: 这个data下方是该接口,除了文件参数,还需要上传其他的参数,这个参数会以json的方式传给服务端(如果没有其他参数,可以不用写这个)
3、params: 这个是除了文件参数以外的,上传的其他参数,这个参数是拼接在url后方的
为了方便大家理解,上方将该参数,以postman的形式上传。
7.4将当前用例的请求值或者响应值存入缓存中
有些小伙伴之前有反馈过,比如想要做数据库的断言,但是这个字段接口没有返回,我应该怎么去做校验呢? 程序中提供了current_request_set_cache这个关键字,可以将当前这条用例的请求数据 或者响应数据 给直接存入缓存中 如下案例所示:
current_request_set_cache: #1、response 从响应中提取内容 2、request从请求中提取内容
- type: response
jsonpath: $.data.data.[0].id
#自定义的缓存名称
name: test_query_shop_brand_list_02_id
7.5请求用例时参数需要从数据库中提取
如上图所示,用例中的 dependent_type 需要填写成 sqlData。 当你的依赖类型为 sqlData 数据库的数据时,那么下方就需要再加一个 setup_sql 的参数,下方填写需要用到的sql语句
注意case_id: 因为程序设计原因,通常情况下,我们关联的业务,会发送接口请求,但是如果我们依赖的是sql的话, 是不需要发送请求的,因此我们如果是从数据库中提取数据作为参数的话,我们case_id 需要写self ,方便程序中去做区分:
ApplyVerifyCode_01:
host: ${{host}}
url: /api/v1/merchant/apply/verifyCode
method: GET
detail: 校验已经审核通过的供应商手机号码
headers:
Content-Type: application/json;charset=UTF-8
# 请求的数据,是 params 还是 json、或者file、data
requestType: params
# 是否执行,空或者 true 都会执行
is_run:
data:
mobile: 18811111111
authCode: 123456
# 是否有依赖业务,为空或者false则表示没有
dependence_case: True
# 依赖的数据
dependence_case_data:
- case_id: self
dependent_data:
- dependent_type: sqlData
jsonpath: $.username
replace_key: $.data.mobile
assert:
code:
jsonpath: $.code
type: ==
value: 200
AssertType:
applyId:
jsonpath: $.data[0].applyId
type: ==
value: $.applyId
AssertType: SQL
applyStatus:
jsonpath: $.data[0].applyStatus
type: ==
value: $.applyStatus
AssertType: SQL
sql:
- select a.apply_id as applyId, a.to_status as applyStatus, a.sub_biz_type as subBizType, a.operator_name as operatorName, a.operator_user_id as operatorUserId, b.apply_type as applyType from test_obp_midware.apply_operate_log as a inner join test_obp_midware.apply as b on a.apply_id = b.id where b.id = $json($.data[0].applyId)$ order by a.id desc limit 1;
setup_sql:
- SELECT * FROM test_obp_user.user_biz_info where user_id = '300000405'
7.6用例中需要依赖登录的token,如何设计
首先,为了防止重复请求调用登录接口,pytest中的 conftest.py 提供了热加载机制,看上方截图中的代码,我们需要在 conftest.py 提前编写好登录的代码。
如上方代码所示,我们会先去读取login.yaml文件中的用例,然后执行获取到响应中的token,然后 编写 Cache(‘work_login_init’).set_caches(token),将token写入缓存中,其中 work_login_init 是缓存名称。
编写好之后,我们会在 requestControl.py 文件中,读取缓存中的token,如果该条用例需要依赖token,则直接进行内容替换。
@pytest.fixture(scope="session", autouse=True)
def work_login_init():
"""
获取登录的cookie
:return:
"""
url = "https://www.wanandroid.com/user/login"
data = {
"username": 18800000001,
"password": 123456
}
headers = {'Content-Type': 'application/x-www-form-urlencoded'}
# 请求登录接口
res = requests.post(url=url, data=data, verify=True, headers=headers).json()
token = res['response']['token']
CacheHandler.update_cache(cache_name='work_login_init', value=token)
这里在编写用例的时候,token 填写我们所编写的缓存名称即可:
7.7用例中依赖cookie如何设计
首先我们在conftest.py中编写获取cookie的方法
@pytest.fixture(scope="session", autouse=True)
def work_login_init():
"""
获取登录的cookie
:return:
"""
url = "https://www.wanandroid.com/user/login"
data = {
"username": 18800000001,
"password": 123456
}
headers = {'Content-Type': 'application/x-www-form-urlencoded'}
# 请求登录接口
res = requests.post(url=url, data=data, verify=True, headers=headers)
response_cookie = res.cookies
cookies = ''
for k, v in response_cookie.items():
_cookie = k + "=" + v + ";"
# 拿到登录的cookie内容,cookie拿到的是字典类型,转换成对应的格式
cookies += _cookie
# 将登录接口中的cookie写入缓存中,其中login_cookie是缓存名称
CacheHandler.update_cache(cache_name='login_cookie', value=cookies)
和token一样,我们如果用例的请求头中依赖cookie, cookie中的值,直接写我们存入缓存中的名称即可
headers:
Content-Type: multipart/form-data;
# 这里cookie的值,写的是存入缓存的名称
cookie: $cache{login_cookie}
7.8断言http响应状态码
相信有些小伙伴在做接口测试的过程中,有部分接口是没有任何响应的,那么在没有响应数据的情况下 我们就只能通过 http的状态码去判断这条用例是否通过,我们可以这样写
assert:
status_code: 200
我们直接在assert下方添加一个 status_code 参数,状态码我们判断其为 200
7.9用例中添加等待时间
程序中可以设定接口请求之后,等待时长,假设A接口依赖B接口的业务,A接口请求完时,我们需要让他等待几秒钟 再次请求B接口,这样的话,我们可以使用sleep关键字
sleep: 3
7.10断言类型
下截图中,是所有断言支持的类型
7.11用例中如何进行接口断言和数据库断言
假设现在我需要测试一个报表统计的数据,该接口返回了任务的处理时长 和 处理数量。功能如下截图所示:
假设下方是我们拿到接口响应的数据内容:
{“code”: 200, “times”: 155.91, “counts”: 9}
这个时候,我们需要判断该接口返回的数据是否正确,就需要编写sql,对响应内容进行校验。
因此我们编写了如上sql,查出对应的数据,那么用例中编写规则如下,下方我们分别断言了两个内容,一个是对接口的响应code码进行断言,一个是断言数据库中的数据。
assert:
code:
jsonpath: $.code
type: ==
value: 200
# 断言接口响应时,可以为空
AssertType:
do_time:
# jsonpath 拿到接口响应的数据
jsonpath: $.times
type: ==
# sql 查出来的数据,是字典类型的,因此这里是从字段中提取查看出来的字段
value: $.do_time
# 断言sql的时候,AssertType 的值需要填写成 SQL
AssertType: SQL
question_counts:
jsonpath: $.counts
type: ==
#
value: $.question_counts
# 断言sql的时候,AssertType 的值需要填写成 SQL
AssertType: SQL
sql:
- select * from test_goods where shop_id = 515
我们分别对用例的数据进行讲解,首先是响应断言, 编写规则如下
code:
# 通过jsonpath获取接口响应中的code {"code": 200, "times": 155.91, "counts": 9}
jsonpath: $.code
type: ==
value: 200
# 断言接口响应时,可以为空
AssertType:
下面是对sql进行断言
question_counts:
# 断言接口响应的问题上报数量counts {"code": 200, "times": 155.91, "counts": 9}
jsonpath: $.counts
type: ==
# 查询sql,我们数据库查到的数据是一个字段,数据是这样的:{question_counts: 13, do_time: 1482.70}, 这里我们通过 jsonpath获取question_counts
value: $.question_counts
# 断言sql的时候,AssertType 的值需要填写成 SQL
AssertType: SQL
sql:
- SELECT round( sum(( UNIX_TIMESTAMP( filing_time )- UNIX_TIMESTAMP( report_time )) / 60 ) / 60, 2 ) AS do_time, count( id ) AS question_counts FROM fl_report_info WHERE state IN ( 1, 3 )
有些细心的小伙伴会发现,我们的sql,是列表类型的。这样就意味这,我们的sql可以同时编写多条,这样会对不会编写多表联查的小伙伴比较友好,可以进行单表查询,获取我们需要的数据。
sql:
- select * from users;
- select * from goods;
7.12使用teardown功能,做数据清洗
通常情况下,我们做自动化所有新增的数据,我们测试完成之后,都需要讲这些数据删除,程序中支持两种写法 一种是直接调用接口进行数据删除。另外一种是直接删除数据库中的数据,建议使用第一种,直接调用业务接口删除对应的数据
1、下面我们先来看看第一种删除方式,teardown的功能,因为需要兼容较多的场景,因此使用功能上相对也会比较复杂 需要小伙伴们一个一个去慢慢的理解。
下面为了方便大家对于teardown功能的理解,我会针对不同的场景进行举例:
- 假设现在我们有一个新增接口,写完之后,我们需要先调用查询接口获取到新增接口的ID,然后再进行删除
那么此时会设计到两个场景,首先执行新增接口ID,然后再拿到响应(这里有个逻辑上的先后关系,查询接口,是先发送请求,在提取数据)获取到查询的ID之后,我们在执行删除,删除的话,我们是直接发送请求
那么针对这个场景,我们就需要有个关键字去做区分,什么场景下先发送请求,什么场景下后发送请求,下面我们来看一下案例,方便大家理解:
teardown:
# 查看品牌审核列表,获取品牌的apply_id
- case_id: query_apply_list_01
# 注意这里我们是先发送请求,在拿到自己响应的内容,因此我们这个字段需要写param_prepare
param_prepare:
# 因为是获取自己的响应内容,我们dependent_type需要写成 self_response
- dependent_type: self_response
# 通过jsonpath的方法,获取query_apply_list_01这个接口的响应内容
jsonpath: $.data.data.[0].applyId
# 将内容存入缓存,这个是自定义的缓存名称
set_cache: test_brand_apply_initiate_apply_01_applyId
# 支持同时存多个数据,只会发送一次请求
- dependent_type: self_response
jsonpath: $.data.data.[0].brandName
set_cache: test_brand_apply_initiate_apply_01_brandName
# 删除
- case_id: delete_01
# 删除的话,我们是直接发送请求的,因此我们这里写 send_request
send_request:
# 我们上方已经拿到了ID,并且将ID存入缓存中,因此这里依赖数据的类型为cache,直接从缓存中提取
- dependent_type: cache
# 这个是缓存名称
cache_data: test_brand_apply_initiate_apply_01_applyId
# 通过relace_key 去替换 delete_01 中的 applyID参数
replace_key: $.data.applyId
那么有些小伙伴会在想,同样我们以上方的接口场景为例,有些小伙伴会说,我公司的新增的接口,有直接返回ID,不需要调用查询接口 程序中当然也支持这种场景,我们只需要这么编写
- case_id: process_apply_01
# 同样这么写 send_request
send_request:
# 这里我们从响应中获取
- dependent_type: response
# 通过jsonpath的方式,获取响应的内容
jsonpath: $.data.id
# 使用repalce_key进行替换
replace_key: $.data.applyId
程序中也支持从请求里面获取内容,编写规则如下:
- case_id: process_apply_01
# 同样这么写 send_request
send_request:
# 这里我们从响应中获取
- dependent_type: request
# 通过jsonpath的方式,获取请求的内容
jsonpath: $.data.id
# 使用repalce_key进行替换
replace_key: $.data.applyId
7.13使用 teardown_sql 后置sql删除数据
如一些特殊场景,业务上并没有提供删除接口,我们也可以直接通过 sql去讲对应的sql删除 teardown_sql: - delete * from xxx - delete * from xxx
7.14自动生成test_case层代码
小伙伴们在编写好 yaml 用例之后,可以直接执行 caseAutomaticControl.py ,会跟你设计的测试用例,生成对应的代码。
7.15日志打印装饰器
在requestControl.py中,我单独封装了一个日志装饰器,需要的小伙伴可以不用改动代码,直接使用,如果不需要,直接注释,或者改成False。控制台将不会有日志输出。
7.16统计用例运行时长
同样,这里封装了一个统计用例运行时长的装饰器,使用改装饰器前,需要先进行导包
from utils.logUtils.runTimeDecoratorl import execution_duration
导入之后,调用改装饰器,装饰器中填写的用例执行时长,以毫秒为单位,如这里设置的2000ms,那么如果该用例执行大于2000ms,则会输出一条告警日志。
@execution_duration(2000)
7.17生成allure报告
我们直接运行主程序 run.py ,运行完成之后,就可以生成漂亮的allure报告啦~
8、项目源码
源码在我的Github上,想要源码的话,记得添加我微信,会拉你进微信群方便后续沟通交流。
9、总而言之
自动化测试是一个不断学习不断更新的东西,我们都需要时刻准备接受新的知识!