> 文章列表 > 接口自动化测试框架ApiRunner实战+框架解析

接口自动化测试框架ApiRunner实战+框架解析

接口自动化测试框架ApiRunner实战+框架解析

目录

框架结构

快速尝试

测试文件

测试用例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。

测试用例yaml文件

login.yaml文件,按照关键字写好接口测试用例,如name,request,validate部分。 

RequestUtil

初始化

测试用例在初始化该类时,传入的base_test_url来自conifg.yaml文件,作用为读取baseurl的值。

standand_yaml

解析yaml文件,发送请求并断言

 def standard_yaml(self,caseinfo):caseinfo_keys= caseinfo.keys()#判断一级关键字是否包含:name,request,validateif "name" in caseinfo_keys and "request" in caseinfo_keys and "validate" in caseinfo_keys:#判断request下面是否包含:method、urlrequest_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.textreturn_code = res.status_codereturn_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:  # jsonpathjs_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文件中

 

 

 

 

 

 

 send_request

  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

read_testcase

读取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 caseinfoelse:if "parameterize" in dict(*caseinfo).keys():new_caseinfo = ddt(*caseinfo)return new_caseinfoelse: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=0for assert_key,expect_value in value.items():print(assert_key,expect_value)if assert_key=="status_code":  #状态断言expect_value==return_codeif expect_value!=return_code:flag=flag+1print("断言失败,返回的状态码不等于%s"%expect_value)else:#用jsonpath进行断言lists=jsonpath.jsonpath(sj_result,'$..%s'%assert_key)if lists:if expect_value not in lists:flag=flag+1print("断言失败:"+assert_key+"不等于"+str(expect_value))else:flag = flag + 1print("断言失败:返回的结果不存在:"+assert_key)return flag# 包含断言def contains_assert(self,value,sj_result):flag=0if str(value) not in str(sj_result):flag = flag + 1print("断言失败:返回的结果中不包含:"+str(value))return flag

test文件封装

test文件封装的是常用的一些方法,可以直接用${函数名}在测试用例中调用,之所以能调用,是因为在replace_value中进行了值替换。

pytest配置文件

pytest.ini文件是pytest的主配置文件,可以改变pytest的运行方式,它是一个固定的文件pytest.ini文件,读取配置信息,按指定的方式去运行。

 

 conftest.py

conftest可以写一些fixture,做一些全局的前后置操作,如清理测试数据,如UI自动化测试中的打开浏览器。

不需要import导入 conftest.py,pytest用例会自动识别该文件,放到项目的根目录下就可以全局目录调用了

 config.yaml

配置文件,可以写一些url,username等配置。

 

测试报告

在run.py里写上allure生成报告的两句命令,执行后即可自动显示allure报告。

 

 总结

这个框架总体来说比较小而美,使用起来简单,结构基本齐全。

缺陷是,框架还有很多优化的空间,如没有封装日志,打印提示很不友好,很多地方没有进行异常处理,断言只支持equals,有兴趣可以继续优化。