揭开接口自动化测试的神秘面纱

原创
2019/11/14 16:38
阅读数 263

10月20日,360技术嘉年华第八季——测试之美在360大厦如期举行 360测试经理董十月分享的《接口自动化测试统一工具》获得大家一致好评,小编特意邀请十月讲师将分享内容整理成文章,以便大家学习

前言

作为QA

  • 你还在为选取接口测试工具而发愁么?
  • 还在为手写代码实现接口自动化而感到神秘么?
  • 还在迷茫什么样的场景可以使用接口自动化么?
  • 正开发的接口自动化不够系统应用场景单一怎么办?

本期分享依据搜索接口自动化测试的最佳实践应用,揭开接口自动化测试的神秘面纱。

本文将会从以下几个方面来阐述:

  • 接口功能自动化的背景和框架设计思路
  • 自动化工具核心功能实现方法
  • 接口功能自动化工具在搜索业务中的实践
  • 效果平台化展示

接口功能自动化的背景和框架设计思路

什么是接口

首先来统一下接口的概念,简单了解下图1搜索服务在线部分架构简图,从用户输入查询词到搜索结果的最终展示,先后经过了dispatcher、searcher和merger等很多服务模块,不同模块有不同的职能,比如有负责排序功能的、有负责纠错功能的等等,但最终他们会把结果统一返回给前端展示,而这些上下游服务模块间是如何沟通的呢,这里就引入了接口的概念,这些上下游就是以接口形式传递数据,而接口返回的数据格式多以JSON、text、HTML片段等为主。

图1 搜索服务在线部分架构简图

为什么做接口测试

接下来看下图2 web服务的金字塔分层模型,从理论层来讲,越底层越稳定,越底层越高效,当然越底层技术专业性越高,投入人力成本就越大,综合考虑接口自动化比较容易实现,可以获得较高的投资回报。其次业务本身特点决定,比如搜索服务接口数量较多,且彼此隔离,单个接口需求的变动,单接口测试更灵活更高效。

图2 web服务金字塔分层模型

接口功能测试的策略

接口测试也属于功能测试的一种,所以与我们以往的功能测试流程和用例设计策略并没有太大区别,如图3

图3 接口用例设计策略简述

接口功能测试方法&工具

在线调试

get方式访问的接口在浏览器中直接输入url即可,再高级一点的可以给浏览器安装一个类似JsonView的插件,进行简单的接口测试;

第三方工具辅助

如Fiddler、Postman、Soupui、Jmeter等,可以满足大部分的接口测试需求如常用的post接口测试等;

编写代码

python+requests/urllib2、Java+Httpclient,通过手写代码方式实现接口自动化测试,也就是今天要分享的重点内容;

为什么选择写代码实现接口自动化

下面先来看下通常情况下我们的业务诉求:

1,加密、带签名接口 被测接口中返回内容为加密数据,并且有的接口需要带token访问怎么办?

2,多接口组合调用 比如包含登录、支付、翻页等功能需要多接口顺序访问的接口如何测试

3,可扩展性(线上监控、定制发邮件) 预期实时监测线上接口服务的运行情况,定制发送测试结果邮件怎么办? 基于以上这些业务需求背景激发了我们开发接口自动化测试工具的想法,于是Auto_ApiTest工具就诞生了。

Auto_ApiTest自动化工具核心特性

1,支持多种API接口请求方法; (如get/post/http/https/加密/鉴权等等)

2,数据与代码分离;(满足多样化数据需求,维护简单快捷)

3,支持线上监控报警;(ip/端口号分离、动态拼接URL,定时启动,定向发送邮件等)

4,测试执行方式简单灵活;(多接口间互相独立,通过传参方式支持单接口、批量调用等)

5,测试报告简介清晰;(测试过程日志结构化存储,并对日志二次分析,自由拼接报告内容)

Auto_ApiTest自动化工具分层图

图4 工具分层图

Auto_ApiTest自动化工具执行流程

第一部分:前期准备阶段,加载测试数据。如被测接口url中需要输入的参数值,用于拼接完整的访问url,以及部分结果判断的预期值;

第二部分:爬取接口,获取第一部分拼接好的接口url的返回内容,用于判断返回结果是否符合预期;

第三部分:结果输出,分析日志,格式化处理后发送邮件和报告等

图5 工具执行流程图

核心功能实现方法详解

数据管理

1、哪些数据才需要管理?如何存储?

一切可变的输入参数值、特定场景值以及需要对比的预期值都需要管理。根据不同的业务需求,选择需要存储的数据到txt文件中,以列为单位逗号分割,如下图6的翻译接口数据存储示例。

图6 翻译接口数据管理

2、测试数据如何构造?

针对于搜索的接口测试数据主要分为普通数据和特殊数据两大类。 普通数据构造:如上图翻译接口测试数据,返回结果比较稳定,在测试之前完全可以按照预期输入和预期输出生成,此类数据构造有两种方式,一种是人工本地构造,第二种是通过单独的自动化脚本在线上获取日志作为测试数据(需要自动处理成标准的数据格式如列名逗号分割等); 特殊数据构造:如视频接口的测试数据,由于测试query会实时更新,如果按照普通数据那样写死会出现误报的情况,所以这类数据需要动态获取;

构造例子如下图所示:

图7 地图接口数据构造-人工构造

图8 mip接口数据构造-线上日志获取

图8中的数据为线上实时获取Mip测试数据,在生成数据脚本中已经加入了判断标准如 mip:或者amp:等,所以如果想用线上获取的数据作为测试用例数据,必须有预期值并且这个预期值是可以自动生成的,否则线上获取的数据会存在对于结果验证是否合理的问题。

图9 视频实时数据构造-在线实时获取

3、数据目录设计 为了满足该自动化工具同时支持线上监控功能,这里采用监控数据和自动化数据分离的方法存储,既能满足监控速度要快,也要满足自动化验证数据要全的目标,所有数据以接口为单位分别存放。

配置层

配置层分为两部分,一部分主要采用config.ini文件配置数据,一个接口一个配置文件,每个文件包含通用配置如线程数、log存储地址和端口号等信息,个性化配置可以精确到单个test_*用例函数的特性配置,如单个用例的数据通过百分比等,如下图10所示

图10 接口配置文件

另一部分是通过py文件配置,如json节点配置、测试报告标题对照、机房ip对照(主要用于线上监控报警排查问题使用)等,如下图11所示![]

图11 通用配置文件

配置层中的ip、port等跟Url相关的配置是如何拼接成最终的可访问的URL的呢,下图为url拼接方式,ip和port单独参数化主要是为了满足不同测试环境的问题。

公共函数层

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

2、接口调用封装(爬取函数)

习惯称获取接口内容叫爬取,有一部分同学这里会不太明白,为什么要爬取,爬取的是什么数据之类的,这里再解释下,爬取的是接口返回的内容(如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

通过上面两个函数例子,现在是否能理解前面我讲到的此接口自动化工具可以支持多种api请求,因为无论什么样的接口我们只需要在公共函数类中封装一个单独的爬取函数就可以解决了。

测试用例管理

这里的测试用例管理是指对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调用、监控调用,例子如下图所示:

自动化工具在搜索应用实践案例

1、持续集成,下图是图搜Merger接口服务的持续集成pipeline交付流水线,简单需求经过持续集成无须QA介入可直接上线。

下图为全自动触发的持续构建结束后的邮件通知

2、线上监控

通过crontab实现定时任务监控,遍历所有机房后,如果有报错最终以邮件、短信或者蓝信公众号等形式通知到对应负责人;

线上监控之功能扩展:query白名单功能,主要解决线上问题在修复过程时重复报警问题,在读取测试数据时,从列表中剔除白名单中的数据,不予监控;

监控中如何避免污染线上数据呢?搜索这边是通过在访问URL中加入特殊标签来解决,如:gasucs/query_rec/?src=qa_test&ret_type=json&req=kw,当RD在读取此接口的访问日志时会直接过滤掉。 对于有数据写入需求的,建议与RD协商使用测试库或者添加特殊小尾巴来解决;

效果量化展示

1、保障所有垂搜接口的功能自动化测试工作

覆盖十余个业务的70多个接口的自动化测试工作,数据用例累加8000余条; 嵌入多条持续集成流程中,服务端测试无须QA介入全自动化实现; 满足多个业务的接口单独调用功能,用于开发人员本地调试接口功能;

2、监控线上所有机房接口服务

多次发现线上问题,如第三方服务问题、环境问题、数据更新问题等等,周期性反馈,警示杜绝重复问题在线上发生;

结束语

希望这些实践经验能让大家在各自的业务自动化思考上有一点点启发,借鉴的同时不脱离业务使用场景,在解决业务痛点中产生有用高效的测试工具来。

想了解更多十月讲师的分享内容,关注360技术公众号,后台回复“8”即可领取第八季测试之美的PPT及视频

展开阅读全文
加载中
点击引领话题📣 发布并加入讨论🔥
0 评论
0 收藏
0
分享
返回顶部
顶部