奇技指南
10月20日,360技术嘉年华第八季——测试之美在360大厦如期举行
360测试经理董十月分享的《接口自动化测试统一工具》获得大家一致好评,小编特意邀请十月讲师将分享内容整理成文章,以便大家学习
前言
作为QA
你还在为选取接口测试工具而发愁么?
还在为手写代码实现接口自动化而感到神秘么?
还在迷茫什么样的场景可以使用接口自动化么?
正开发的接口自动化不够系统应用场景单一怎么办?
本期分享依据搜索接口自动化测试的最佳实践应用,揭开接口自动化测试的神秘面纱。
-
接口功能自动化的背景和框架设计思路
-
自动化工具核心功能实现方法
-
接口功能自动化工具在搜索业务中的实践
-
效果平台化展示
接口功能自动化的背景和框架设计思路
什么是接口
首先来统一下接口的概念,简单了解下图1搜索服务在线部分架构简图,从用户输入查询词到搜索结果的最终展示,先后经过了dispatcher、searcher和merger等很多服务模块,不同模块有不同的职能,比如有负责排序功能的、有负责纠错功能的等等,但最终他们会把结果统一返回给前端展示,而这些上下游服务模块间是如何沟通的呢,这里就引入了接口的概念,这些上下游就是以接口形式传递数据,而接口返回的数据格式多以JSON、text、HTML片段等为主。
图1 搜索服务在线部分架构简图
为什么做接口测试
图2 web服务金字塔分层模型
接口功能测试的策略
图3 接口用例设计策略简述
接口功能测试方法&工具
在线调试
第三方工具辅助
编写代码
为什么选择写代码实现接口自动化
下面先来看下通常情况下我们的业务诉求:
加密、带签名接口
被测接口中返回内容为加密数据,并且有的接口需要带token访问怎么办?
多接口组合调用
比如包含登录、支付、翻页等功能需要多接口顺序访问的接口如何测试可扩展性(线上监控、定制发邮件)
预期实时监测线上接口服务的运行情况,定制发送测试结果邮件怎么办?
基于以上这些业务需求背景激发了我们开发接口自动化测试工具的想法,于是Auto_ApiTest工具就诞生了。
Auto_ApiTest自动化工具核心特性
支持多种API接口请求方法;
(如get/post/http/https/加密/鉴权等等)
数据与代码分离;(满足多样化数据需求,维护简单快捷)
支持线上监控报警;(ip/端口号分离、动态拼接URL,定时启动,定向发送邮件等)
测试执行方式简单灵活;(多接口间互相独立,通过传参方式支持单接口、批量调用等)
-
测试报告简介清晰;(测试过程日志结构化存储,并对日志二次分析,自由拼接报告内容)
Auto_ApiTest自动化工具分层图
图4 工具分层图
Auto_ApiTest自动化工具执行流程
第二部分:爬取接口,获取第一部分拼接好的接口url的返回内容,用于判断返回结果是否符合预期;
第三部分:结果输出,分析日志,格式化处理后发送邮件和报告等
图5 工具执行流程图
2
核心功能实现方法详解
数据管理
图6 翻译接口数据管理
2、测试数据如何构造?
普通数据构造:如上图翻译接口测试数据,返回结果比较稳定,在测试之前完全可以按照预期输入和预期输出生成,此类数据构造有两种方式,一种是人工本地构造,第二种是通过单独的自动化脚本在线上获取日志作为测试数据(需要自动处理成标准的数据格式如列名逗号分割等);
特殊数据构造:如视频接口的测试数据,由于测试query会实时更新,如果按照普通数据那样写死会出现误报的情况,所以这类数据需要动态获取;
构造例子如下图所示:
图7 地图接口数据构造-人工构造
图8 mip接口数据构造-线上日志获取
图8中的数据为线上实时获取Mip测试数据,在生成数据脚本中已经加入了判断标准如 mip:或者amp:等,所以如果想用线上获取的数据作为测试用例数据,必须有预期值并且这个预期值是可以自动生成的,否则线上获取的数据会存在对于结果验证是否合理的问题。
配置层
配置层分为两部分,一部分主要采用config.ini文件配置数据,一个接口一个配置文件,每个文件包含通用配置如线程数、log存储地址和端口号等信息,个性化配置可以精确到单个test_*用例函数的特性配置,如单个用例的数据通过百分比等,如下图10所示
图10 接口配置文件
图11 通用配置文件
公共函数层
1、封装读取测试数据函数
这里的测试数据是指用于拼接Url和所要对比参考用的数据,如query,except等。采用python的csv模块获取我们前面讲的构造的那些测试数据,主要函数方法如下:
# encoding: utf-8
import csv
class DataReader:
def __init__(self, data_file_path):
self.data_file_path = data_file_path
self.data_file = open(self.data_file_path)
# 过滤以#开头的行
self.data_reader = csv.DictReader(filter(lambda row: row[0]!='#', self.data_file))
self.datas = []
for row in self.data_reader:
self.datas.append(row)
def getDatas(self):
return self.datas
习惯称获取接口内容叫爬取,有一部分同学这里会不太明白,为什么要爬取,爬取的是什么数据之类的,这里再解释下,爬取的是接口返回的内容(如json串),是接口自动化最核心的一部分,要验证接口返回的内容是否是我们需要的,通常python会采用resquests或者urllib2模块来实现。为节省空间,只保留重要代码如下:
爬取加密/post/带host接口函数:
def getPostDate(base_url,data):
base64data = base64.b64decode(data)
req_header = {'Host':'tip.f.360.cn'}
//访问url、response、接口状态和响应时间等统一存储在一个dict中,在报错时能带上更多的现场信息,便于排查问题。
return_dict={}
return_dict["url"]=base_url
try:
req = urllib2.Request(base_url, data=base64data, headers=req_header)
res = urllib2.urlopen(req, timeout=3)
doc = res.read()
status = res.getcode()
except Exception,e:
return return_dict
else:
return_dict["data"]=doc
return_dict["status"]=status
return return_dict
爬取普通接口通用函数(不定长参数传递)
def getDataforNlp(base_url,**args):
for key in args:
urlargs += "&" + key + "=" + args[key]
url = base_url + urlargs
return_dict = {}
return_dict["url"]=url
try:
req = requests.get(url,timeout=1,headers=req_header)
response_time = req.elapsed.microseconds
status = req.status_code
return_dict["status"]=status
return_dict["response_time"] = "%sms"%(response_time/1000)
except Exception,e:
print e
else:
doc_ = json.loads(req.text)
return_dict["data"]=doc_
return return_dict
测试用例管理
这里的测试用例管理是指对test_*文件的管理,采用unittest模块主要做一些初始化和善后操作,case以接口为单位,一个接口一个py文件,每个py文件包含多个test_*函数,主要是接口返回结果校验,主要代码如下:
from common import commonMethod //导入公共函数模块
from common import CommonTestCase //导入初始化函数模块,主要处理setup和teardown
import unittest
from conf import data //导入json节点和title标题等配置文件
class Mip(CommonTestCase.CommonTestCase)
//本函数为验证线上mip页面展示内容是否为真正的mip页面,主要通过html标签来判断,
//测试输入数据为上面数据管理章节提到的线上获取mip测试数据
def test_result(self):
def fuc(row):
query = row["query"].strip()
test_result = "Fail"
except_type = "mip" if query.startswith("mip:") else "amp"
message = ''
log = []
url = self.__class__.url
return_dataall = commonMethod.getDataForImg(url,query,"url=")
return_data = return_dataall.get("data")
allurl = return_dataall.get("url")
status = return_dataall.get("status")
response_time = return_dataall.get("response_time")
content = commonMethod.dict_get(return_data,data.DataResult.content)
errorno = commonMethod.dict_get(return_data,data.DataResult.todaynewserrorno)
if return_data:
if content:
htm_type = "mip" if "<html mip" in second_line or "mip>" in second_line or "mip=" in second_line else "amp"
if except_type == htm_type:
test_result = "Pass"
else:
message = "格式不正确query显示为%s但content中目前为%s"%(except_type,htm_type)
else:
message = "接口访问失败未返回content数据statuscode=%s|response_time=%sms"%(status,response_time)
else:
message = "接口访问失败未返回data数据statuscode=%s|response_time=%sms"%(status,response_time)
log = [test_result, query, message, pageurl,content]
if test_result == 'Pass':
self.pass_num += 1
log.insert(0, self.comm_log_str)
self.logger.info(u'{0[0]},{0[1]},{0[2]},{0[3]}'.format(log))
elif test_result == 'Fail':
self.fail_num += 1 //结果统计使用
log.insert(0, self.comm_log_str)
self.logger.error(u'{0[0]},{0[1]},{0[2]},{0[3]},{0[4]},{0[5]}'.format(log)) //日志结构化存储
commonMethod.pool_map(fuc,self.datas, self.thread_num)
日志处理
日志结构化输出,从上面的用例代码中最后一部分我们可以看到日志存储部分的处理,而最终的输出效果如下图所示,这样我们就可以实现对测试报告内容展示做到根据各自需求灵活选择的目的:
执行测试
主要使用shell脚本来实现调用,启动时传递各种参数,如端口号、hostname、收件人和执行目录等等,启动入口主要分为jenins调用、监控调用,例子如下图所示:
3
自动化工具在搜索应用实践案例
1、持续集成,下图是图搜Merger接口服务的持续集成pipeline交付流水线,简单需求经过持续集成无须QA介入可直接上线。
下图为全自动触发的持续构建结束后的邮件通知
监控中如何避免污染线上数据呢?搜索这边是通过在访问URL中加入特殊标签来解决,如:gasucs/query_rec/?src=qa_test&ret_type=json&req=kw,当RD在读取此接口的访问日志时会直接过滤掉。
4
效果量化展示
1、保障所有垂搜接口的功能自动化测试工作
2、监控线上所有机房接口服务
5
结束语
希望这些实践经验能让大家在各自的业务自动化思考上有一点点启发,借鉴的同时不脱离业务使用场景,在解决业务痛点中产生有用高效的测试工具来。
界世的你当不
只做你的肩膀
无
360官方技术公众号
技术干货|一手资讯|精彩活动
空·