目录
框架结构
快速尝试
测试文件
测试用例yaml文件
RequestUtil
初始化
standand_yaml
send_request
read_testcase
参数化
用例关联
断言
test文件封装
pytest配置文件
conftest.py
config.yaml
测试报告
总结
本文介绍一个接口自动化测试框架,只需要编写yaml文件,即可实现接口自动化测试。阅读这个框架的结构并了解代码实现。由于是一个无名的框架,我把它起名叫做ApiRunner。
程序入口run.py,调用的pytest库执行用例。
执行后,可以看到通过了一条用例。
下面看看测试用例如何执行的。
test_login.py中为测试用例
该文件为一个测试类,无继承,每一个test为一个测试函数,这也是pytest中的概念。
该测试函数,采用parametrize进行数据驱动,用read_testcase函数读取login.yaml文件,并起名为caseinfo。
在测试函数中,调用RequestUtil类,传入base_test_url,然后调用standard_yaml函数,解析caseinfo。
login.yaml文件,按照关键字写好接口测试用例,如name,request,validate部分。
测试用例在初始化该类时,传入的base_test_url来自conifg.yaml文件,作用为读取baseurl的值。
解析yaml文件,发送请求并断言
def standard_yaml(self,caseinfo):
caseinfo_keys= caseinfo.keys()
#判断一级关键字是否包含:name,request,validate
if "name" in caseinfo_keys and "request" in caseinfo_keys and "validate" in caseinfo_keys:
#判断request下面是否包含:method、url
request_keys=caseinfo["request"].keys()
if "method" in request_keys and "url" in request_keys:
print("yaml基本架构检查通过")
method = caseinfo['request'].pop("method") #pop() 函数用于移除列表中的一个元素,并且返回该元素的值。
url= caseinfo['request'].pop("url")
res = self.send_request(method,url,**caseinfo['request']) #caseinfo需要解包加**
return_text=res.text
return_code = res.status_code
return_json=""
try:
return_json = res.json()
except Exception as e:
print("extract返回的结果不是JSON格式")
# 提取值并写入extract.yaml文件
if "extract" in caseinfo.keys():
for key, value in caseinfo["extract"].items():
if "(.*?)" in value or "(.+?)" in value: # 正则表达式
zz_value = re.search(value, return_text)
if zz_value:
extract_value = {key: zz_value.group(1)}
YamlUtil().write_yaml(extract_value)
else: # jsonpath
js_value = jsonpath.jsonpath(return_json, value)
if js_value:
extract_value = {key: js_value[0]}
YamlUtil().write_yaml(extract_value)
#断言:
self.assert_result(caseinfo['validate'],return_json,return_code)
else:
print("在request下必须包含method,url")
else:
print("一级关键字必须包含name,request,validate")
获取caseinfo中的key,判断是否包含必须的name,request,validate关键字,如果不包含,说明写的用例格式错误(这里限制了必须包含着三个关键字)
获取request中的关键字,判断是否有method和url,没有说明格式错误。
从request中取出method,url,调用send_request方法发送请求。并获取返回结果。
如果caseinfo包含extract关键字,则进行解析,将对应值写给extract.yaml文件,调用assert_result方法进行断言。。
extrat支持正则和jsonpath,提取出的key和value存放在extract.yaml文件中
def send_request(self,method,url,**kwargs):
method=str(method).lower() #转换小写
#基础路径的拼接和替换
url= self.base_url + self.replace_value(url)
print(url)
#参数替换
for key,value in kwargs.items():
if key in ['params','data','json','headers']:
kwargs[key]=self.replace_value(value)
print(kwargs[key])
elif key == "files":
for file_key, file_path in value.items():
value[file_key] = open(file_path, 'rb')
res = RequestUtil.sess.request(method, url, **kwargs)
print(res.text)
return res
读取yaml数据,以列表嵌套字典形式返回
def read_testcase(yaml_name):
with open(os.getcwd() + '\\testcases\\' + yaml_name, mode='r', encoding='utf-8') as f:
caseinfo = yaml.load(f, yaml.FullLoader)
print((caseinfo))
if len(caseinfo)>=2:
return caseinfo
else:
if "parameterize" in dict(*caseinfo).keys():
new_caseinfo = ddt(*caseinfo)
return new_caseinfo
else:
return caseinfo
该函数读取测试用例yaml文件,读取出的caseinfo为一个列表。如果这个列表长度大于等于2,则直接返回,否则如果列表转换后的字典有parameterize关键字,则用ddt函数进行解析,返回new_caseinfo,否则直接返回caseinfo。
[{'name': '$ddt{name}', 'request': {'method': 'post', 'url': '/auth/login', 'headers': {'Content-Type': 'application/json'}, 'json': {'username': 'tester', 'password': 'tester'}}, 'validate': [{'equals': {'status_code': 200}}]}]c
在login_data里写测试数据,用parameterize关键字传进去,在测试用例理用$ddt{}进行读取。
在ddt函数里,对caseinfo中的带$ddt{}的数据进行了替换,返回的new_caseinfo为解析ddt后的数据。
在测试数据文件里,还可以直接读取extract.yaml里的值,或者直接运用test.py文件中的函数,如图的调用生成时间函数。
在login.yaml中用extract关键字提取出token,在下一个接口中用read_extract_data进行读取。
之所以能用${}读取值,是因为在send_request里调用了replace_value,将引用替换成了真实的值。
断言目前支持,断言返回状态码和用jsonpath断言值。
方法只支持equals和contains,其他需要自己补充方法。
# 相等断言
def equals_assert(self,value,return_code,sj_result):
"""
:param value: 断言字典如{msg:"登录成功"}
:param return_code: 响应状态码
:param sj_result: 响应json数据
:return:
"""
flag=0
for assert_key,expect_value in value.items():
print(assert_key,expect_value)
if assert_key=="status_code": #状态断言
expect_value==return_code
if expect_value!=return_code:
flag=flag+1
print("断言失败,返回的状态码不等于%s"%expect_value)
else:#用jsonpath进行断言
lists=jsonpath.jsonpath(sj_result,'$..%s'%assert_key)
if lists:
if expect_value not in lists:
flag=flag+1
print("断言失败:"+assert_key+"不等于"+str(expect_value))
else:
flag = flag + 1
print("断言失败:返回的结果不存在:"+assert_key)
return flag
# 包含断言
def contains_assert(self,value,sj_result):
flag=0
if str(value) not in str(sj_result):
flag = flag + 1
print("断言失败:返回的结果中不包含:"+str(value))
return flag
test文件封装的是常用的一些方法,可以直接用${函数名}在测试用例中调用,之所以能调用,是因为在replace_value中进行了值替换。
pytest.ini文件是pytest的主配置文件,可以改变pytest的运行方式,它是一个固定的文件pytest.ini文件,读取配置信息,按指定的方式去运行。
conftest可以写一些fixture,做一些全局的前后置操作,如清理测试数据,如UI自动化测试中的打开浏览器。
不需要import导入 conftest.py,pytest用例会自动识别该文件,放到项目的根目录下就可以全局目录调用了
配置文件,可以写一些url,username等配置。
在run.py里写上allure生成报告的两句命令,执行后即可自动显示allure报告。
这个框架总体来说比较小而美,使用起来简单,结构基本齐全。
缺陷是,框架还有很多优化的空间,如没有封装日志,打印提示很不友好,很多地方没有进行异常处理,断言只支持equals,有兴趣可以继续优化。