yamlapi接口测试框架

1、思路:

yamlapi支持unittest与pytest两种运行模式,

yamlapi即为yaml文件+api测试的缩写,

可以看作是一个脚手架工具,

可以快速生成项目的各个目录与文件,

测试人员只需维护一份或者多份yaml文件即可,

不需要大量写代码。

2、安装:

https://pypi.org/

可在首页搜索“yamlapi”,

或者直接访问项目主页:

https://pypi.org/project/yamlapi/

pip install yamlapi

# 安装

yamlapi -h(或yamlapi --help)

# 查看参数信息

yamlapi -v(或yamlapi --v)

# 查看版本号

pip install -U yamlapi

# 安装最新版

yamlapi --p=项目名称

# 创建项目

# 例如在某个路径下执行命令:yamlapi --p=demo_project

pip uninstall yamlapi

# 卸载

3、工程示例:

README.md文件:

# honeybee
(蜜蜂)接口测试框架  

# 一、思路
1、采用requests+unittest+ddt+PyMySQL+BeautifulReport+demjson+loguru+PyYAML+pytest+pytest-html+allure-pytest+pytest-rerunfailures+pytest-sugar+pytest-timeout
2、requests是发起HTTP请求的第三方库
3、unittest是Python自带的单元测试工具
4、ddt是数据驱动的第三方库
5、PyMySQL是连接MySQL的第三方库
6、BeautifulReport是生成html测试报告的第三方库
7、demjson是解析json的第三方库
8、loguru是记录日志的第三方库
9、PyYAML是读写yaml文件的第三方库
10、pytest是单元测试的第三方库
11、pytest-html是生成html测试报告的插件
12、allure-pytest是生成allure测试报告的插件
13、pytest-rerunfailures是失败重跑的插件
14、pytest-sugar是显示进度的插件
15、pytest-timeout是设置超时时间的插件  

# 二、目录结构
1、case是测试用例包
2、log是日志目录
3、report是测试报告的目录
4、resource是yaml文件的目录
5、setting是工程的配置文件包
6、tool是常用方法的封装包         

# 三、yaml文件说明
1、字段(命名和格式不可修改,顺序可以修改)
case_name: 用例名称
mysql: MySQL查询语句
request_mode: 请求方式
api: 接口
data: 请求体,缩进字典格式或者json格式
headers: 请求头,缩进字典格式或者json格式
query_string: 请求参数,缩进字典格式或者json格式
expected_code: 预期的响应代码
expected_result: 预期的响应结果,-列表格式
regular: 正则,缩进字典格式
>>variable:变量名,-列表格式
>>expression:表达式,-列表格式  

2、参数化
正则表达式提取的结果用${变量名}表示,一条用例里面可以有多个
MySQL返回的结果用{__SQL}表示,一条用例里面可以有多个
随机数字用{__RN位数},一条用例里面可以有多个
随机英文字母用{__RL位数},一条用例里面可以有多个
以上4种类型在一条用例里面可以混合使用
${变量名}的作用域是全局的,其它3种的作用域仅限该条用例  

demo_test.py文件:

  1 import json
  2 import re
  3 import os
  4 import sys
  5 import unittest
  6 from itertools import chain
  7 from time import sleep
  8
  9 import ddt
 10 import demjson
 11 import requests
 12
 13 BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
 14 sys.path.append(BASE_DIR)
 15
 16 from setting.project_config import *
 17 from tool.connect_mysql import query_mysql
 18 from tool.create_random import create_random_number, create_random_letters
 19 from tool.read_write_yaml import read_yaml, write_yaml
 20 from tool.beautiful_report_run import beautiful_report_run
 21
 22
 23 @ddt.ddt
 24 # 声明使用ddt
 25 class DemoTest(unittest.TestCase):
 26     temporary_yaml = yaml_path + "/temporary.yaml"
 27     if os.path.isfile(temporary_yaml):
 28         # 如果临时yaml文件存在
 29         os.remove(temporary_yaml)
 30         # 删除之
 31     demo_one_list = read_yaml("/demo_one.yaml")
 32     demo_two_list = read_yaml("/demo_two.yaml")
 33     demo_three_list = read_yaml("/demo_three.yaml")
 34     temporary_list = demo_one_list + demo_two_list + demo_three_list
 35     temporary_yaml = yaml_path + write_yaml("/temporary.yaml", temporary_list)
 36
 37     # 把几个yaml文件合并为一个临时yaml文件
 38
 39     @classmethod
 40     def setUpClass(cls) -> None:
 41         cls.variable_result_dict = {}
 42         # 定义一个变量名与提取的结果字典
 43         # cls.variable_result_dict与self.variable_result_dict都是本类的公共属性
 44
 45     @ddt.file_data(yaml_path + "/temporary.yaml")
 46     # 传入临时yaml文件
 47     def test_logistics(self, **kwargs):
 48         """
 49         测试用例
 50         :param kwargs:
 51         :return:
 52         """
 53
 54         kwargs = str(kwargs)
 55         if "None" in kwargs:
 56             kwargs = kwargs.replace("None", "‘‘")
 57         kwargs = demjson.decode(kwargs)
 58         # 把值为None的替换成‘‘空字符串,因为None无法拼接
 59         # demjson.decode()等价于json.loads()反序列化
 60
 61         case_name = kwargs.get("case_name")
 62         # 用例名称
 63         self._testMethodDoc = case_name
 64         # 测试报告里面的用例描述
 65         mysql = kwargs.get("mysql")
 66         # mysql查询语句
 67         request_mode = kwargs.get("request_mode")
 68         # 请求方式
 69         api = kwargs.get("api")
 70         # 接口
 71         if type(api) != str:
 72             api = str(api)
 73         payload = kwargs.get("data")
 74         # 请求体
 75         if type(payload) != str:
 76             payload = str(payload)
 77         headers = kwargs.get("headers")
 78         # 请求头
 79         if type(headers) != str:
 80             headers = str(headers)
 81         query_string = kwargs.get("query_string")
 82         # 请求参数
 83         if type(query_string) != str:
 84             query_string = str(query_string)
 85         expected_code = kwargs.get("expected_code")
 86         # 预期的响应代码
 87         expected_result = kwargs.get("expected_result")
 88         # 预期的响应结果
 89         regular = kwargs.get("regular")
 90         # 正则
 91
 92         logger.info("{}>>>开始执行", case_name)
 93
 94         if environment == "prd" and mysql != "":
 95             self.skipTest("生产环境跳过此用例,请忽略")
 96         # 生产环境不能连接MySQL数据库,因此跳过,此行后面的都不会执行
 97
 98         requests_list = [api, payload, headers, query_string]
 99         # 请求数据列表
100
101         for index, value in enumerate(requests_list):
102             # for循环修改requests_list的值
103
104             if self.variable_result_dict:
105                 # 如果变量名与提取的结果字典不为空
106                 if "$" in value:
107                     for key, value_2 in self.variable_result_dict.items():
108                         value = value.replace("{" + key + "}", value_2)
109                         # replace(old, new)把字符串中的旧字符串替换成正则表达式提取的值
110                     value = re.sub("\\$", "", value)
111                     # re.sub(old, new, 源字符串)默认全部替换
112                     # 如果遇到带有转义的字符被当作特殊字符时,使用双反斜杠\\来转义,或者在引号前面加r
113             else:
114                 pass
115
116             if mysql:
117                 # 如果mysql查询语句不为空
118                 if "$" in mysql:
119                     # 有些场景下MySQL查询语句也需要参数化
120                     for key, value_2 in self.variable_result_dict.items():
121                         mysql = mysql.replace("{" + key + "}", value_2)
122                     mysql = re.sub("\\$", "", mysql)
123                 mysql_result_tuple = query_mysql(mysql)
124                 # mysql查询结果元祖
125                 mysql_result_list = list(chain.from_iterable(mysql_result_tuple))
126                 # 把二维元祖转换为一维列表
127                 if "__SQL" in value:
128                     for i in mysql_result_list:
129                         if type(i) != str:
130                             i = str(i)
131                         value = value.replace("{__SQL}", i, 1)
132                         # replace(old, new, 替换次数)把字符串中的{__SQL}替换成mysql查询返回的值
133             else:
134                 pass
135
136             if "__RN" in value:
137                 digit_list = re.findall("{__RN(.+?)}", value)
138                 # 获取位数列表
139                 for j in digit_list:
140                     random_number = create_random_number(int(j))
141                     # 调用生成随机数字的方法
142                     value = value.replace("{__RN" + j + "}", random_number)
143
144             if "__RL" in value:
145                 digit_list = re.findall("{__RL(.+?)}", value)
146                 # 获取位数列表
147                 for i in digit_list:
148                     random_letters = create_random_letters(int(i))
149                     # 调用生成随机字母的方法
150                     value = value.replace("{__RL" + i + "}", random_letters)
151
152             requests_list[index] = value
153
154         api = requests_list[0]
155         payload = requests_list[1]
156         headers = requests_list[2]
157         query_string = requests_list[3]
158
159         if payload != "":
160             payload = demjson.decode(payload)
161         if headers != "":
162             headers = demjson.decode(headers)
163         if query_string != "":
164             query_string = demjson.decode(query_string)
165
166         url = service_domain + api
167         # 拼接完整地址
168
169         logger.info("请求方式为:{}", request_mode)
170         logger.info("地址为:{}", url)
171         logger.info("请求体为:{}", payload)
172         logger.info("请求头为:{}", headers)
173         logger.info("请求参数为:{}", query_string)
174
175         logger.info("预期的响应代码为:{}", expected_code)
176         logger.info("预期的响应结果为:{}", expected_result)
177
178         response = requests.request(
179             request_mode, url, data=json.dumps(payload),
180             headers=headers, params=query_string, timeout=(9, 15))
181         # 发起HTTP请求
182         # json.dumps()序列化把字典转换成字符串,json.loads()反序列化把字符串转换成字典
183         # data请求体为字符串,headers请求头与params请求参数为字典
184
185         actual_time = response.elapsed.total_seconds()
186         # 实际的响应时间
187         actual_code = response.status_code
188         # 实际的响应代码
189         actual_result_text = response.text
190         # 实际的响应结果(文本格式)
191
192         logger.info("实际的响应代码为:{}", actual_code)
193         logger.info("实际的响应结果为:{}", actual_result_text)
194         logger.info("实际的响应时间为:{}", actual_time)
195
196         if regular:
197             # 如果正则不为空
198             extract_list = []
199             # 定义一个提取结果列表
200             for i in regular["expression"]:
201                 regular_result = re.findall(i, actual_result_text)[0]
202                 # re.findall(正则表达式, 实际的响应结果)返回一个符合规则的list,取第1个
203                 extract_list.append(regular_result)
204                 # 把提取结果添加到提取结果列表里面
205
206             temporary_dict = dict(zip(regular["variable"], extract_list))
207             # 把变量列表与提取结果列表转为一个临时字典
208
209             for key, value in temporary_dict.items():
210                 self.variable_result_dict[key] = value
211             # 把临时字典合并到变量名与提取的结果字典,已去重
212         else:
213             pass
214
215         for key in list(self.variable_result_dict.keys()):
216             if not self.variable_result_dict[key]:
217                 del self.variable_result_dict[key]
218         # 删除变量名与提取的结果字典中为空的键值对
219
220         actual_result_text = re.sub("{|}|\"|\\[|\\]", "", actual_result_text)
221         # 去除{、}、"、[与]
222         actual_result_list = re.split(":|,", actual_result_text)
223         # 把响应文本转为列表,并去除:与,
224
225         if expected_code == actual_code:
226             if set(expected_result) <= set(actual_result_list):
227                 # 预期的响应结果与实际的响应结果是被包含关系
228                 # 判断是否是其真子集
229                 logger.info("{}>>>执行通过", case_name)
230             else:
231                 logger.error("{}>>>执行失败", case_name)
232             self.assertTrue(set(expected_result) <= set(actual_result_list))
233             # 布尔表达式断言
234         else:
235             logger.error("{}>>>请求失败,请检查域名、路径与请求参数是否正确!", url)
236             logger.error("{}>>>执行失败", case_name)
237             self.assertTrue(set(expected_result) <= set(actual_result_list))
238
239         logger.info("##########用例分隔符##########\n")
240         # sleep(3)
241         # 等待时间为3秒,也可以调整为其他值
242
243
244 if __name__ == ‘__main__‘:
245     beautiful_report_run(DemoTest)
246     # 调用BeautifulReport运行方式

project_config.py文件:

 1 """
 2 整个工程的配置文件
 3 """
 4
 5 import os
 6 import sys
 7 import time
 8
 9 from loguru import logger
10
11 parameter = sys.argv[1]
12 # 从命令行获取参数
13
14 environment = os.getenv("measured_environment", parameter)
15 # 环境变量
16
17 if environment == "dev":
18     service_domain = "http://www.dev.com"
19     # 开发环境
20     db_host = ‘mysql.dev.com‘
21     db_port = 3306
22 elif environment == "test":
23     service_domain = "http://www.test.com"
24     # 测试环境
25     db_host = ‘mysql.test.com‘
26     db_port = 3307
27 elif environment == "pre":
28     service_domain = "http://www.pre.com"
29     # 预生产环境
30     db_host = ‘mysql.pre.com‘
31     db_port = 3308
32 elif environment == "formal":
33     service_domain = "https://www.formal.com"
34     # 生产环境
35     db_host = None
36     db_port = None
37
38 db_user = ‘root‘
39 db_password = ‘123456‘
40 db_database = ‘‘
41 # MySQL数据库配置
42
43
44 current_path = os.path.dirname(os.path.dirname(__file__))
45 # 获取当前目录的父目录的绝对路径
46 # 也就是整个工程的根目录
47 case_path = os.path.join(current_path, "case")
48 # 测试用例的目录
49 yaml_path = os.path.join(current_path, "resource")
50 # yaml文件的目录
51 today = time.strftime("%Y-%m-%d", time.localtime())
52 # 年月日
53
54 report_path = os.path.join(current_path, "report")
55 # 测试报告的目录
56 if os.path.exists(report_path):
57     pass
58 else:
59     os.mkdir(report_path, mode=0o777)
60
61 log_path = os.path.join(current_path, "log")
62 # 日志的目录
63 if os.path.exists(log_path):
64     pass
65 else:
66     os.mkdir(log_path, mode=0o777)
67
68 logging_file = os.path.join(log_path, "log{}.log".format(today))
69
70 logger.add(
71     logging_file,
72     format="{time:YYYY-MM-DD HH:mm:ss}|{level}|{message}",
73     level="INFO",
74     rotation="500 MB",
75     encoding="utf-8",
76 )
77 # loguru日志配置

4、运行:

unittest模式:

python+测试文件名+环境缩写

python ./case/demo_test.py dev

python ./case/demo_test.py test

python ./case/demo_test.py pre

python ./case/demo_test.py formal

pytest模式:

pytest -v

完整命令为:

pytest -v --reruns 3 --reruns-delay 3 --timeout=60 --junitxml=./report/report.xml --html=./report/report.html --self-contained-html --alluredir=./report/allure-report

已写进pytest.ini配置文件

原文地址:https://www.cnblogs.com/yjlch1016/p/12286091.html

时间: 2024-10-28 23:14:48

yamlapi接口测试框架的相关文章

http接口测试框架-python

简单分解一下 接口测试框架设计: 主入口 -> 遍历接口/用例 -> 发送请求+接收响应 ->结果的对比 -> 生成报告 ->发送email 分成几大类:主入口的py文件 src-核心代码文件    遍历case,发送请求和接收响应 存放case的    2.数据库里维护    3.excel里维护        包括的字段:编号,接口名称,地址前缀,请求地址,请求方法,请求数据类型,请        求数据,检查点,是否运行,测试结果,响应参数 公共函数:被多次重复调用,且

[python测试框架] http接口测试框架

https://testerhome.com/topics/5631 Http 接口测试框架 (思路 + 实现中 + 开源 + 可能难产) Http 接口测试框架疑问解答 Fiddler 保存会话 (请求) 说明 由于部分内容涉及公司机密,已用字母替换,不影响阅读 实际效果 验证1000个接口平均耗时6s(看机器配置及网速) 第一次投入使用,马上发现5个接口异常并且该验证过程不到30s的时间 框架的下一步 目前已兼容我们公司所有app response body全字段验证(含字段类型) 已有思路

初探接口测试框架--python系列7

点击标题下「蓝色微信名」可快速关注 坚持的是分享,搬运的是知识,图的是大家的进步,没有收费的培训,没有虚度的吹水,喜欢就关注.转发(免费帮助更多伙伴)等来交流,想了解的知识请留言,给你带来更多价值,是我们期待的方向,有更多兴趣的欢迎切磋,我们微信订阅号,联系方式如下: 更多书籍,敬请期待 背景说明 python系列课程也有段时间了,我们坚持,一步步来,今天是最后一课的分享,看看接口测试框架的神秘,小怪带领着大家一起完成第7课,希望能看完知识点,自己动手练习,然后和给出的例子代码对比,最后看看作业

利用接口测试框架实现web状态的监控

之前,我们已经说明了如何实现一个我们的接口测试框架RATF,当然这个框架不止可以用于管理我们的接口测试代码,我们还可以用他来对我们的web进行简单粗暴的监控. 原理: 1. 通过使用配置文件,对要监控的域名进行配置 2. 然后编写运行器html_runner.py 对要监控的域名都行轮询 3. 通过消息推送模块,推送给对应的人员

基于Python的接口测试框架实例

文章来源:http://www.jb51.net/article/96481.htm 下面小编就为大家带来一篇基于Python的接口测试框架实例.小编觉得挺不错的,现在就分享给大家,也给大家做个参考.一起跟随小编过来看看吧 背景 最近公司在做消息推送,那么自然就会产生很多接口,测试的过程中需要调用接口,我就突然觉得是不是可以自己写一个测试框架? 说干就干,由于现有的接口测试工具Jmeter.SoupUI等学习周期有点长,干脆自己写一个吧,不求人,所有功能自己都能一清二楚. 当然,写工具造轮子只是

初探接口测试框架--python系列1

点击标题下「蓝色微信名」可快速关注 坚持的是分享,搬运的是知识,图的是大家的进步,没有收费的培训,没有虚度的吹水,喜欢就关注.转发(免费帮助更多伙伴)等来交流,想了解的知识请留言,给你带来更多价值,是我们期待的方向,有更多兴趣的欢迎切磋,我们微信订阅号,联系方式如下: 更多书籍,敬请期待 背景说明 我们清楚的知道自动化测试包含UI自动化.接口自动化.单元自动化,其中接口自动化测试可以说是性价比比较高的,我们今天不讲如何开展接口自动化测试,而是来看看接口测试框架的神秘.小怪带领着大家一起简单轻松的

主流接口测试框架对比

公司计划系统的开展接口自动化测试,需要我这边调研一下主流的接口测试框架给后端测试(主要测试接口)的同事介绍一下每个框架的特定和使用方式.后端同事根据他们接口的特点提出一下需求,看哪个框架更适合我们. ### 需求: 1.接口编写方便. 2.方便调试接口. 3.支持数据初始化. 4.生成测试报告. 5.支持参数化. #### robot framework 优点 关键字驱动,自定义用户关键字. 支持测试日志和报告生成. 支持系统关键字开发,可扩展性好. 支持数据库操作. 缺点 接口测试用例写起来不

Python接口测试实战4(上) - 接口测试框架实战

如有任何学习问题,可以添加作者微信:lockingfree 课程目录 Python接口测试实战1(上)- 接口测试理论 Python接口测试实战1(下)- 接口测试工具的使用 Python接口测试实战2 - 使用Python发送请求 Python接口测试实战3(上)- Python操作数据库 Python接口测试实战3(下)- unittest测试框架 Python接口测试实战4(上) - 接口测试框架实战 Python接口测试实战4(下) - 框架完善:用例基类,用例标签,重新运行上次失败用例

基于HttpRunner,解析swagger数据,快速生成接口测试框架

使用HttpRunner默认生成的项目是这样的 命令:httprunner --startproject  项目名称 so,根据这个项目的目录结构,使用python解析swagger接口参数,可以快速生成api.testcases.testsuites文件夹中用到的json文件 运行后的目录是这样的 api目录 按swagger中的tags区分为多个文件夹,每个文件夹下包含各自的api文件 testcases目录 按swagger中的tags区分为不同的json文件,每个文件包含所有的api接口