(根据自己学习过程一步步总结,我是小菜鸟)
1.第一条requests请求
### 1.1浅试一个post请求
url = "http://api.xxxx.com/futureloan/member/login"
请求类型:post
请求体
req_data = {
"mobile_phone": "13524050000",
"pwd": "123456789"
}
resp = requests.post(url, json=req_data,headers=headers)
print("登陆的响应结果: \n", resp.text)
print:
{"code":0,"msg":"OK","data":{"id":183953,"leave_amount":1000.0,"mobile_phone":"13524050000","reg_name":"小咸鱼","reg_time":"2024-03-14 10:38:02.0","type":1,"token_info":{"token_type":"Bearer","expires_in":"2024-03-14 10:46:58","token":"eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJfaWQiOjE4Mzk1MywiZXhwIjoxNzEwMzg3Nzg2fQ.9i3HO_chjQzKt11eAwcxUvdmKzaDPF5koL4_zRL7eP19tE4gjmeEXLgNfwpHtcfFpNLXoChBOOFBeHuNs1QtA"}},"copyright":"dd技术有限公司"}
# 1.2 常用调用接口的方法是提取出来返回数据,作为下一个接口的参数去请求
接着1.1的返回做提取
json_res = resp.json()
token = json_res["data"]["token_info"]["token"]
member_id = json_res["data"]["id"]
print('提取的token是: ',json_res["data"]["token_info"]["token"])
print('提取的手机号id是: ',json_res["data"]["id"])
print:
提取的token是: eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJfaWQiOjE4Mzk1MywiZXhwIjoxNzEwMzg3Nzg2fQ.9i3-HO_chjQzKt11eAwcxUvdmKzaDPF5koL4_zRL7eP19tE4gjmeEXLgNfwpHtcfFpNLXoChBOOFBeHuNs1QtA
提取的手机号id是: 183953
1.3提问
1)如果请求50个请求,是不是可以把相同部分封装一起调用?
2)有的接口需要token,有的不需要怎么处理?(实际工作中只有登录接口不需要token,其余接口都需要从登录接口获得的token作为请求的入参)
2.浅封装请求
2.1 把发送请求封装类,并把请求头的变量token字段封装,默认=None
class MyRequests:
# 初始化方法
def __init__(self):
# 请求头
self.headers = {"X-xxx-Media-Type": "xxx.v2"}
# 属性
# 方法 post/put.. json=XXX , get.. params=XXX
def send_requests(self,method,url, data,token=None):
# 处理请求头
self.__deal_header(token)
# 调用requests的方法去发起一个请求。并得到响应结果
if method.upper() == "GET":
resp = requests.request(method, url, params=data, headers=self.headers)
else:
resp = requests.request(method, url, json=data, headers=self.headers)
return resp
def __deal_header(self,token=None):
if token:
self.headers["Authorization"] = "Bearer {}".format(token)
2.2 调用下该封装类
mr = MyRequests()
# url地址
url = "http://api.xxxcom/futureloan/member/login"
# 请求类型:post
method = "post"
# 请求体
req_data = {
"mobile_phone": "13524050000",
"pwd": "123456789"}
resp = mr.send_requests(method,url, req_data)
print(resp.json())
print:
{'code': 0, 'msg': 'OK', 'data': {'id': 183953, 'leave_amount': 1000.0, 'mobile_phone': '13524050000', 'reg_name': 'py37小咸鱼', 'reg_time': '2024-03-14 10:38:02.0', 'type': 1, 'token_info': {'token_type': 'Bearer', 'expires_in': '2024-03-14 14:18:02', 'token': 'eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJfaWQiOjE4Mzk1MywiZXhwIjoxNzEwMzk3MDgyfQ.1PQxQSOoE_GnzcjT4dUN3elB5XmdqzRRMVsDJ2Tlcr0tfkRxO5MSrDEEMZKLLSwpHBZKs_uIRfuwukNhg7ZUjA'}}, 'copyright': 'Copyright嘻嘻信息技术有限公司'}
2.3 提问
请求清爽了,感觉参数比较多,没有规范,把全部参数整合一起?
3.浅整合入参
3.1 列表是测试用例集合,一个字典就是一条用例
datas = [
{"method": "post" ,
"url": "http://api.xxx.com/futureloan/member/register",
"req_data": {"mobile_phone": "13524050000","pwd": "123456789","reg_name": "py小咸鱼"}},
{"method": "post",
"url": "http://api.xxx/futureloan/member/login",
"req_data":{"mobile_phone": "13524050000","pwd": "123456789"}},
{"method": "post",
"url": "http://api.xxx.com/futureloan/member/recharge",
"req_data": {"member_id": None,"amount": 1000}}
]
执行下:
mr = MyRequests()
# 一组就是一条用例
#某一组即便运行失败了,下一组仍然会运行。
@pytest.mark.parametrize("item", datas)
def test_api1(item):
resp = mr.send_requests(item["method"], item["url"], item["req_data"])
print(resp.json())
print:
总结:通过封装调用方法,封装入参,使用驱动传参,全都封装到列表里,一个字典就是一组数据就是一条用例,有几个用例就执行几遍。用字典不同的key调用不同的值,即使失败了也不会影响其他用例,执行代码也少了。
补充:涉及pytest装饰器 @pytest.mark.parametrize 使用方法可以单独研究下
3.2 把测试用例写在excel,读取
#用例地址
excel_path=r"D:\EasyProject\day5\testdatas\测试用例.xlsx"
#打开一个工作簿
work_book=load_workbook(excel_path)
print(work_book.sheetnames)
#打开一个sheet页
work_sheet=work_book["注册接口"]
3.2.1 方法1
datas=[]
cases=[]
#values_only=True默认返回单元格的值,false返回cell对象(默认)
for row in work_sheet.iter_rows(min_row=1,values_only=True):
#每一行作为一个值存入datas列表
datas.append(row)
print(datas)
#datas列表中第二行数据行开始遍历,并且与第一行表头组合成想要的字典组合。
for i in datas[1:]:
cases.append(dict(zip(datas[0],i)))
print(cases)
3.2.2 方法2
cases=[]
#获取页面所有行的值
excel_datas = list(work_sheet.iter_rows(values_only=True))
#第一行作为key保存到key列表
keys_list=excel_datas[0]
#除非第一行的全部行保存到value列表
values_list=excel_datas[1:]
#循环value列表,取到每一行时都与第一行key列组合成一个字典格式的新数据
for i in values_list:
case=dict(zip(keys_list,i))
cases.append(case)
print(cases)
3.2.2 方法3
#得到当前sheet的总行号,总列号
row_nums = sh.max_row
col_nums = sh.max_column
data = list(sh.values)
print(data)
keys = data[0] # 获取所有的列名
all_data = []
for row in data[1:]:
row_dict = dict(zip(keys,row))
all_data.append(row_dict)
3.2.2 方法4
# 得到当前sheet的总行号,总列号
row_nums = sh.max_row
col_nums = sh.max_column
# 只读取第一行(作为key)
# 行号是1 通过代码自动得到列号
keys = []
for col_index in range(1, sh.max_column + 1):
keys.append(sh.cell(1, col_index).value)
print(keys)
# 遍历行号,取第一行
for row_index in range(2, sh.max_row + 1):
values = []
# 在每一行里面,从第1列开始,获取所有列的值
for col_index in range(1, sh.max_column + 1):
values.append(sh.cell(row_index, col_index).value)
# keys和values打包 - zip函数
case = dict(zip(keys, values))
print(case)
3.2.2 方法5
这个方法超级简单, 不需要像前4种方法,直接执行.excel_to_dict()方法即可。
from xToolkit import xfile
def read_data():
res=xfile.read("测试用例1229.xlsx").excel_to_dict("信息披露")
return res
if __name__ == '__main__':
# excel的文件路径
excel_path = r"D:\\testDatas\测试用例1229.xlsx"
rr=read_data()
print(rr)
4.封装断言、exel读取、logs 、path
我感觉写了几个接口后,发现确实很杂乱,试想如果在加20个接口100个用例呢?
屡下一个接口的实现流程,发起请求,读取用例,执行用例,对比结果断言,上下游接口间参数传递和使用,发送报告,无人值守定时运行。
发现在每个接口每个用例执行时,这些都是必做流程,所以现阶段现学习时先封装这几项。
4.1 封装断言
class MyAssert:
def assert_response_value(self,check_str, response_dict):
"""
:param check_str: 从excel当中,读取出来的断言列。是一个列表形式的字符串。里面的成员是一个断言
:param response_dict: 接口请求之后的响应数据,是字典类型。
:return: None
"""
# 所有断言的比对结果列表
check_res = []
# 把字符串转换成python列表
check_list = ast.literal_eval(check_str) # 比eval安全一点。转成列表。
for check in check_list:
# 通过jsonpath表达式,从响应结果当中拿到了实际结果
actual = jsonpath.jsonpath(response_dict, check["expr"])
if isinstance(actual, list):
actual = actual[0]
# 与实际结果做比对,check["type"]还有其他类型再接着判断即可
if check["type"] == "==":
check_res.append(actual == check["expected"])
if False in check_res:
logger.error("部分断言失败!,请查看比对结果为False的")
raise AssertionError
else:
logger.info("所有断言成功!")
4.2 封装excel
from openpyxl import load_workbook
class MyExcel:
def __init__(self, excel_path, sheet_name):
# 1、加载一个excel,得到工作薄 Workbook
wb = load_workbook(excel_path)
# 2、选择一个表单- 通过表单名 Sheet
self.sh = wb[sheet_name]
def read_data(self):
datas=[]
cases=[]
#values_only=True默认返回单元格的值,false返回cell对象(默认)
for row in work_sheet.iter_rows(min_row=1,values_only=True):
#每一行作为一个值存入datas列表
datas.append(row)
print(datas)
#datas列表中第二行数据行开始遍历,并且与第一行表头组合成想要的字典组合。
for i in datas[1:]:
cases.append(dict(zip(datas[0],i)))
return cases
4.3 封装excel
class MyLogger(Logger):
def __init__(self):
# conf = MyConf("conf.ini")
# file = conf.get("log", "file")
file = "api.log"
# 1、设置日志的名字、日志的收集级别
# super().__init__(conf.get("log","name"), conf.get("log","level"))
super().__init__("xxxapi", logging.INFO)
# 2、可以将日志输出到文件和控制台
# 自定义日志格式(Formatter)
fmt_str = "%(asctime)s %(name)s %(levelname)s %(filename)s [%(lineno)d] %(message)s"
# 实例化一个日志格式类
formatter = logging.Formatter(fmt_str)
# 实例化渠道(Handle).
# 控制台(StreamHandle)
handle1 = logging.StreamHandler()
# 设置渠道当中的日志显示格式
handle1.setFormatter(formatter)
# 将渠道与日志收集器绑定起来
self.addHandler(handle1)
if file:
# 文件渠道(FileHandle)
handle2 = logging.FileHandler(file, encoding="utf-8")
# 设置渠道当中的日志显示格式
handle2.setFormatter(formatter)
self.addHandler(handle2)
logger = MyLogger()
4.4 封装excel
path很好的解决路径读错,不适配等问题
import os
# 1、basedir
basedir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
print(basedir)
# 拼到配置文件路径
conf_dir = os.path.join(basedir, "Conf")
print(conf_dir)
# 拼接 测试数据路径
testdata_dir = os.path.join(basedir, "testdatas")
print(testdata_dir)
打印:
E:\sunqq_python\git_test\day7
E:\sunqq_python\git_test\day7\Conf
E:\sunqq_python\git_test\day7\testdatas
5. 说说封装断言的感想
此时此刻此能力的我,在学习到与数据库的值做断言时,一下子想通了一些串联的场景。从用例设置,封装断言,调用断言。
发现每一步骤都是关联,有小心机的。
- 5.1用例内容
在用例里加入断言数据库列,要求规则1、必须是列表形式;2、列表中的成员,必须是字典形式;3、字典必须有3个key:sql, expected, type_db;4、type_db表示查询数据库时,是获取结果条数(type为count),要是获取数据。
- 5.2封装断言
接上面4.1里断言类My_assert里新增assert_db()方法
def assert_db(self,check_db_str):
"""
1、将check_db_str转成python对象(列表),通过eval
2、遍历1中的列表,访问每一组db比对
3、对于每一组来讲,1)调用数据库类,执行sql语句。调哪个方法,根据type来决定。得到实际结果
2)与期望结果比对
:param check_db_str: 测试数据excel当中,assert_db列读取出来的数据库检验字符串。
示例:[{"sql":"select id from member where mobile_phone='#phone#'","expected":1,"type":"count"}]
:return:
"""
# 所有断言的比对结果列表
check_db_res = []
# 把字符串转换成python列表
check_db_list = ast.literal_eval(check_db_str) # 比eval安全一点。转成列表。
# 建立数据库连接
db = MyMysql()
# 遍历check_db_list
for check_db_dict in check_db_list:
# 根据type来调用不同的方法来执行sql语句。
if check_db_dict["db_type"] == "count":
# 执行sql语句
res = db.get_count(check_db_dict["sql"])
# 将比对结果添加到结果列表当中
check_db_res.append(res == check_db_dict["expected"])
if False in check_db_res:
return False
else:
logger.info("所有断言成功!")
return True
- 5.3调用使用
# 结果空列表
assert_res = []
if case["assert_db"]:
db_check_res = massert.assert_db(case["assert_db"])
assert_res.append(db_check_res)
# 最终的抛AsserttionError
if False in assert_res:
raise AssertionError
总结:用例设计里的[{“sql”:“select id from member where mobile_phone=‘#phone#’”,“expected”:1,“db_type”:“count”}
],字典[“sql”]的值其实就说实际结果,只是需要在运行后实现结果,所以这里写的是得到实际结果的手段。
字典[“db_type”]的值实际是看断言类型,然后可以在断言类My_assert类里调用目标方法即可。
做好5.1、5.2后,实际在5.3中只需要一句代码 db_check_res = massert.assert_db(case[“assert_db”])就可以实现。
今天先到这吧。
补充一点:对数据库做断言时,sql语句一定要写在一行然后复制,不然复制到excel中,再执行后是有空格的,我就踩了这个坑,日志看怎么都断言失败,找不到原因,原来是sql语句的问题。
原sql语句
把sql语句复制到用例里,也没啥问题。
执行时,报错了