基于Pytest的接口测试框架

基于Pytest的接口测试框架

这是一款基于pytest框架的开源测试框架,功能涵盖请求体保存, allure生成, log生成, 全局变量存储, 全局变量使用, 断言返回体 等多种功能组合

github地址是:https://github.com/dreamshao/pytest-autotest-interface

详细介绍

框架图:
  —接口自动化测试
     --allure_report
     --allure-results
     --BaseRequests
       ---__init__.py
       ---BaseRequests.py
     --Data
       ---data.db
       ---data.xlsx
       ---sqlite 可视化安装包
       ---read_data.py
     --logs
     --RequestObject
       ---request_runner.py
     --TestCases
       -- 测试用例
     --Utils
       --json_assert.py
       --logger.py
       --replace_requests.py
       --variable_handler.py
      --man.py
      --requirements.txt

开始前必须了解的东西

数据存储

数据存储用到了sqlite,这个轻量级的数据库,无需额外安装第三方的包,也没有mysql那么大对资源要求低,当然也支持excel,里面也封装好了数据。

支持运行生成sql 的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
def get_data_from_sqlite(self, sql_command, name, sql_db_path=None, query_sql_type=False):
try:
cur = self.connect_sqlite(name)
if query_sql_type:
if cur:
logger.info(f"执行查询操作 执行语句是 {sql_command}")
cur[0].execute(sql_command)
re = cur[0].fetchall()
print(re)
# 关闭Cursor
cur[0].close()
# 关闭conn
cur[1].close()
list_of_re = [[item for item in tup] for tup in re]
logger.info(f"执行查询操作 成功返回的数据是 {list_of_re}")
return list_of_re
else:
if cur:
logger.info(f"执行非查询操作 执行语句是 {sql_command}")
cur[0].execute(sql_command)
cur[1].commit()
# 关闭Cursor
cur[0].close()
# 关闭conn
cur[1].close()
except Exception as e:
logger.info(f"创建数据库失败,具体原因是 {e}")
return False

其中要传入sql_command 是sql语句,name 是 数据库名字,query_sql_type 是要确定是你要查询还是 增删改 数据 查询传递True,其余就是False

这里也准备了sqlite的可是化安装包,不过是windows,这个网络很多也可以自己下载

数据库的表目前有两个,分别是: requests, variable

requests: 存储请求信息断言信息等。

variable 存储变量信息。

数据断言及变量获取

这里都用到了一个东西叫jsonpath,这里不知道的可以自己查询一下,稍后我也会更新关于jsonpath的文章,将连接填写到这里方便查看

文章已经更新: 关于jsonpath的文章地址:https://8888666.top/2024/07/24/jsonpath/

可以看一下已经存储的jsonpath:

报告及log

报告生成采用的allure,allure report 目前没有保存旧的报告,可以去掉相关代码,实现旧的报告存储,log日志生成规则按天生成文件夹,按启动时间生成日志

allure 报告:

log 日志:

具体封装逻辑及操作详解

基本请求封装

我们的接口请求一般都是GET, POST, PUT, DELETE这四种较为常见, 目前就是封装了这四种 具体的文件是 BaseRequests 下的 BaseRequests.py文件

举例 按照 GET 请求讲解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
def get_request(self, url, params=None, headers=None):
"""
get 请求
:param url: 请求url
:param params: 请求入参
:param headers: 请求头
:return: Boolean
"""
try:
logger.info(f"开始进入get请求, 当前请求url是{url}, params是{params}, headers是{headers}")
result = requests.get(url=url, params=params, headers=headers)
if result.status_code == 200:
logger.info(f'当前请求成功,返回的http_code是 200, 返回结果是 {result.text}')
return result.text
else:
logger.info(f'当前返回出现了问题,返回的http_code是{result.status_code},返回的信息是{result.text}')
return False
except Exception as e:
logger.info(f"当前请求出现了问题,请求方式是 GET, 地址是 {url}, params是{params}, headers是{headers}, 出现的问题是 {e}")
return False

这里首先封装了一个基础请求也就是把我们的requests下的get请求进行了简单封装,判断了返回的httpcode是否是200,返回布尔值。

更进一步的封装一个get请求

我的思路是在封装的时候最底层的最少接触业务层代码,所以在 RequestObject 下重新二次封装业务信息的 request_runner.py

举例 按照 POST 请求讲解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
def post_request_runner(self, id, replace_type=None):
"""
post请求
:param id: id: 数据库用例Id
:param replace_type: replace_type: 替换类型 1:url 替换 2:data 替换 默认None 不替换
:return: 失败 False 成功 dict
"""
logger.info("当前进入post测试用例中")
if replace_type == 1:
self.url = Replace_Request(url=self.url).repalce_request_url()
else:
self.data = Replace_Request(data=self.data).replace_request_data()
result = BaseRequests().post_request(url=self.url, headers=json.loads(self.headers), data=self.data)
if result:
result_json = json.loads(result)
DataManger().get_data_from_sqlite(name="data",
sql_command="UPDATE requests SET response_body = '{response_body}' where id = {id}".format(
response_body=json.dumps(result), id=id),
query_sql_type=False)
if self.assert_info is not None:
result = Assert_Json().assert_info(result_json, self.assert_info)
if result:
logger.info("测试全部通过")
return result_json
else:
logger.info("测试存在失败")
return False
else:
logger.info("测试全部通过")
return result_json

else:
logger.info("当前请求失败")
print("失败了!")

其中需要传递当前数据库存储的用例id, 以及是否要进行替换,这里说明一下两个参数的作用。

第一: id 是为了存储当前用例请求返回后存储的值

第二: replace_type 是为了是否进行替换,在我们流程性请求中我们一般都需要进行整体性的测试,那么我们就需要做到可以拿到上个的某个返回值作为下一个接口的输入值,这
个作用就是这个, 1 是url 进行替换 2 是 data进行替换

如果需要断言 则是 Assert_Json 这个类 需要传入一个接口返回值, 以及断言的jsponpath

返回: 成功则是返回接口的返回体,不成功则返回False

测试用例

我们的测试用例也是只需写部分代码即可完成断言,变量的存储,变量的替换。

举例:Today 比赛列表

1
2
3
4
5
6
7
8
9
10
11
@allure.step("Today比赛列表")
@pytest.mark.parametrize('id, url, method, headers, request_data, params, json, assert_jsonpath, response_body, remarks',
DataManger().get_data_from_sqlite(name="data",sql_command='''select * from requests where id= 2''',
query_sql_type=True))
def test_match_list(self, id, url, method, headers, request_data, params, json, assert_jsonpath, response_body, remarks):
logger.info(f"开始测试比赛列表")
result = Request_Runner(url=url, headers=headers, assert_info=assert_jsonpath).get_request_runner(id=id)
logger.info(f"测试结果是 {result}")
if result:
Variable_Handler(variable_name=["eventid","competitorid"], response_body=result).variable_handler()
assert result is not False

这里用了allure报告的setp方法进行标识用例信息,采用了pytest.mark.parametrize的参数化方法,这里是从数据库里面提取数据,当前也支持从excel中提取数据,里面调用了封装的请求方法传递了id,这里还调用了Variable_Handler 这个类, 这里类就是实现存储变量的方法,variable_name 需要传递一个列表,里面是你的变量名称,response_body 是接口返回的值。这样我们就可以将我们需要的变量存储下来。

工具

json_assert.py 是实现 断言的 采用的是 jsonpath 的方法获取对应信息进行对比

logger.py 是日志的生成

replace_request.py 是变量的替换,支持url 替换, data 替换

variable_handler.py 是变量的存储功能

main 文件

也就是主文件,用来实现调用pytest以及生成allure报告

代码:

1
2
3
4
5
6
if __name__ == "__main__":
pytest.main(['-v', '-s', '--alluredir=allure-results', '--clean-alluredir'])
cmd = "allure generate ./allure-results report --clean"
subprocess.run(cmd, shell=True)
cmd = "allure open -h 127.0.0.1 -p 8883 allure-report"
subprocess.run(cmd, shell=True)

demo 解析

当案例中有四个url

其中包含变量存储 以及 替换的是:remarks 里面的 比赛列表页, 这里存储了两个变量分别是eventid, competitorid。将对应的值保存到variable表中。

代码:

1
2
3
4
5
6
7
8
9
10
11
@allure.step("Today比赛列表")
@pytest.mark.parametrize('id, url, method, headers, request_data, params, json, assert_jsonpath, response_body, remarks',
DataManger().get_data_from_sqlite(name="data",sql_command='''select * from requests where id= 2''',
query_sql_type=True))
def test_match_list(self, id, url, method, headers, request_data, params, json, assert_jsonpath, response_body, remarks):
logger.info(f"开始测试比赛列表")
result = Request_Runner(url=url, headers=headers, assert_info=assert_jsonpath).get_request_runner(id=id)
logger.info(f"测试结果是 {result}")
if result:
Variable_Handler(variable_name=["eventid","competitorid"], response_body=result).variable_handler()
assert result is not False

使用变量的是 remarks 是订阅比赛,深度数据 这里分别用到了 url 替换 以及 data 替换。

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
 @allure.step("订阅比赛,采用全局变量")
@pytest.mark.parametrize(
'id, url, method, headers, request_data, params, json, assert_jsonpath, response_body, remarks',
DataManger().get_data_from_sqlite(name="data", sql_command='''select * from requests where id= 4''',
query_sql_type=True))
def test_match_subscribe_list(self, id, url, method, headers, request_data, params, json, assert_jsonpath,
response_body,
remarks):
logger.info(f"订阅比赛信息")
result = Request_Runner(url=url, headers=headers, data=request_data, assert_info=assert_jsonpath).post_request_runner(id=id, replace_type=1)
logger.info(f"测试结果是 {result}")
# if result[0] is True:
# Variable_Handler(id=2, variable_name="eventid").variable_handler()
assert result is not False

@allure.step("深度数据,采用全局变量")
@pytest.mark.parametrize(
'id, url, method, headers, request_data, params, json, assert_jsonpath, response_body, remarks',
DataManger().get_data_from_sqlite(name="data", sql_command='''select * from requests where id= 5''',
query_sql_type=True))
def test_match_subscribe_list(self, id, url, method, headers, request_data, params, json, assert_jsonpath,
response_body,
remarks):
logger.info(f"订阅比赛信息")
result = Request_Runner(url=url, headers=headers, data=request_data,
assert_info=assert_jsonpath).post_request_runner(id=id, replace_type=2)
logger.info(f"测试结果是 {result}")
assert result is not False

结束语

之前我做的是基于Django+vue 的测试开发平台,已经稳定运行了3年+, 最近想要把部分功能用Pytest实现,也就做出了一版,这版肯定有很多不足,欢迎各位大神批评和建议!让我们一起进步努力!


基于Pytest的接口测试框架
https://dreamshao.github.io/2024/07/12/pytest接口测试框架/
作者
Yun Shao
发布于
2024年7月12日
许可协议