业务流测试(各种功能) 对应的是单接口测试
业务流本质上还是接口关联,需要多个接口一起测试,并且完成业务流程,即同时完成多个接口的测试,观察业务流程是否成功,也是一次集成测试.
方法1:
直接把要关联的接口封装到APICase里,然后填入访问接口时所需要的参数,每次执行用例的时候调用一次,从关联接口中获得测试接口所需要的数据,测试用例执行的只是被测接口
优点:简单易懂
缺点:如果业务流需要多个接口,就要封装多个访问接口的函数,并且多次调用,代码变得繁琐
方法2:
不需要封装访问关联接口的函数,把测试需要关联接口数据都写在excel表格中,每次执行测试用例方法时候会一一访问不同的接口,获取返回结果中的对应数据,得到下一个接口所需要的参数,测试用例执行的是所有关联接口和被测接口
优点:代码变得简洁,方法都经过封装放入一个类中,而且关联接口比较多的时候,不需要一一封装和调用,直接通过测试用例函数进行访问,只需要从响应结果里获取其他接口需要的数据即可
缺点:难度较大,逻辑较复杂,调试不方便,可以通过日志观察
封装正则表达式替换数据的函数
-在APICase类里定义实例方法/类方法
@classmethod
def replace_data(cls,string):
# 给指定的字符串替换成动态数据
result = re.finditer("#(.*?)#", string)
for el in result:
old = el.group()
prop_name = el.group(1)
string = string.replace(old, str(getattr(cls, prop_name)))
return string
定义需要替换的数据名称,获得替换数据
参数填入需要替换的字符串,还需要准备的数据有替换的内容,内容可以从配置文件获取,作为前置条件,在开始执行测试用例函数之前获取(需要替换的数据名称要跟excel表格里面标记的##对应数据名称一致)
访问登录接口,需要有账号和密码,审核接口需要管理员操作.所以需要获得投资人和借款人还有管理员的账号和密码,我们把投资人和借款人设定为同一个人
@ddt
class TestInvest(unittest.TestCase,APICase):
@classmethod
def setUpClass(cls) -> None:
cls.investor_phone = secret_config.investor["mobile_phone"]
cls.investor_pwd = secret_config.investor["pwd"]
cls.loan_phone = secret_config.user["mobile_phone"]
cls.loan_pwd = secret_config.user["pwd"]
cls.admin_phone = secret_config.admin_user["mobile_phone"]
cls.admin_pwd = secret_config.admin_user["pwd"]
注意:这里设置的替换方法为实例方法,所以获得需要替换的数据可以定义为类属性,也可以是实例属性
如果替换数据定义为实例属性,而替换方法为类方法的话,类方法里就不能获取实例属性,导致不能替换成功
使用替换数据函数,进行动态数据替换(数据预处理)
@data(*info)
def test_invest(self,case_info):
#获取需要替换的数据
headers = case_info["headers"]
json_data = case_info["json"]
#正则表达式动态替换数据里面带#号的元素
headers = self.replace_data(headers)
json_data = self.replace_data(json_data)
headers = json.loads(headers)
json_data = json.loads(json_data)
expected = json.loads(case_info["expected"])
正则表达式方法的应用:
首先要获取需要替换的数据(字符串)
需要类里定义跟#标记的同名属性(从配置文件获得,通过接口响应结果获得)
使用替换函数,需要进行两次替换(因为headers和json_data分别是2个不同的字符串)
最后把替换的数据转化成字典
访问接口,获得响应结果
#获取响应结果
resp = requests.request(method=case_info["method"],
url = host+case_info["url"],
headers = case_info["headers"],
json = case_info["json"]).json()
*数据提取(extractor)
如果在编写测试用例时候,发现下一个接口需要的参数是从其他接口获得,就要从前面接口获取响应结果中的指定数据,设置成跟#里同名的属性,作为下一个接口访问时候需要的参数
在excel表格中添加title extractor,里面填入每次访问接口之后提取需要作为后面接口参数的响应数据,相当于方法1用函数访问接口,然后设置类属性
字典的key作为要设置属性的属性名,由字典的value作为模糊查询的表达式
通过jsonpath在响应数据中模糊查询得到相应的值作为类的属性值,然后通过动态设置类属性,得到类属性
首先要判断extractor里是否有内容
#数据提取
#判断extractor里面是否有数据,这个过程可以放到数据预处理阶段
if case_info["extractor"]:
#反序列化转换成字典
extractor = json.loads(case_info["extractor"])
#下面设置属性的过程要放在访问接口之后
#还需要判断一次extractor是否存在,才能进行遍历
if case_info["extractor"]:
for key,value in case_info["extractor"].items():
#动态设置类属性
# 字典的key为类属性名,
# 由字典的value作为模糊查询的表达式,通过jsonpath在响应数据中模糊查询得到相应的值作为类的属性值
setattr(APICase,key,jsonpath(resp,value)[0])
通过反序列化把里面的内容转换为字典后,再遍历字典
通过setattr动态设置属性,把key作为属性名,通过由value的jsonpath表达式查询到的响应结果作为属性值,得到类属性
设置成类属性是因为,需要用到该属性值的是之后的接口,实例能调用类属性
断言
for key, value in case_info["expected"].items():
self.assertEqual(value, resp[key])
完整流程
代码优化(封装)
1,数据预处理封装
定义好函数的名称-prev_data(case_info),需要返回替换后的case_info(字典)
def prev_data(self,case_info):
"""数据预处理,完成替换,转化为字典"""
# 获取需要替换的数据
headers = case_info["headers"]
json_data = case_info["json"]
# 正则表达式动态替换数据里面带#号的元素
headers = self.replace_data(headers)
if "#over_amount#" in json_data:
json_data = json_data.replace("#over_amount#", str(self.leave_amount + 1))
json_data = json_data.replace("#member_id#", str(self.member_id))
else:
json_data = self.replace_data(json_data)
if case_info["extractor"]:
extractor = case_info["extractor"]
case_info["extractor"] = json.loads(extractor)
#修改原先字典的value
case_info["headers"] = json.loads(headers)
case_info["json"] = json.loads(json_data)
case_info["expected"] = json.loads(case_info["expected"])
return case_info
*这里有一个单独对#over_amount#的字符串进行单独处理,是因为在提现接口中,其中有一天用例是余额不足的用例,余额不足需要提现的金额为#leave_amount#(余额)+1,但是读取excel的json的值是一个字符串 不能执行+1这个运算,所以这条用例需要手工提取出来进行+1
转换前字典case_info键为json和headers的值是字符串,经过数据替换,并且把值转换成字典,再通过对case_info重新赋值的方法,
得到的结果是字典嵌套字典{“json”: {"mobile_phone": "15100002222", "pwd": "12345678"}}
2,访问接口封装
定义函数的名称visit(case_info) 注意,这里的case_info 是通过数据预处理方法函数得出的返回值,也就是重新赋值后的case_info
def visit(self,case_info):
"""访问接口"""
# 参数是数据预处理后得到的返回结果,重新赋值后的case_info
resp = requests.request(method=case_info["method"],
url=host + case_info["url"],
headers=case_info["headers"],
json=case_info["json"]).json()
3,数据提取封装
定义函数的名称extract(case_info,resp),这里的case_info也是通过数据预处理函数得到的返回值
def extract(self,case_info,resp):
"""响应结果数据提取"""
#需要判断一次extractor是否存在, 才能进行遍历
if case_info["extractor"]:
for key, value in case_info["extractor"].items():
# 动态设置类属性
# 字典的key为类属性名,
# 由字典的value作为模糊查询的表达式,通过jsonpath在响应数据中模糊查询得到相应的值作为类的属性值
setattr(APICase, key, jsonpath(resp, value)[0])
因为extractor从第五个用例开始有None的值,所以需要判断是否存在数据
也可以把这个方法设置成类方法
4,断言(可以不用封装)
定义函数的名称assert_all(case_info,resp),这里的case_info也是通过数据预处理函数得到的返回值
def assert_all(self,case_info,resp):
"""断言"""
for key, value in case_info["expected"].items():
self.assertEqual(value, resp[key])
整体结果
5,封装全部(装*用)
定义函数名称steps(case_info),表示测试的步骤,包含之前封装的4个方法
def steps(self,case_info):
"""测试步骤"""
case_info = self.prev_data(case_info)
resp = self.visit(case_info)
self.extract(case_info, resp)
self.assert_all(case_info, resp)
最后剩下1行代码
装*就要撞到极致



