QCon 演讲火热征集中,快来分享技术实践与洞见! 了解详情
写点什么

有赞线上拨测系统实践(一)

  • 2020-03-11
  • 本文字数:4753 字

    阅读完需:约 16 分钟

有赞线上拨测系统实践(一)

前言

一直以来,作为互联网软件工程师接触最多的事务之一便是持续集成(Continuous integration,简称 CI)。持续集成俨然已成为主流互联网软件开发流程中一个重要的环节。现今有赞内部在实践持续交付(Continuous delivery,简称 CD),它可以被看成是后持续集成时代的产物。需要强调的是,不管是 CI 还是 CD,更多的是强调作为软件开发交付过程中的实践,而一旦交付到生产环境 CI 和 CD 就无能为力了。有赞线上拨测系统正是为了弥补这一不足。现有的线上保障手段可分为运维层面、产品层面、安全层面、服务层面和测试层面等维度。本文重点介绍我们在测试层面的实践。

基于测试脚本的线上监控产生

我们做测试线上拨测系统的初衷有以下几点:


  1. 主动预警线上问题。有赞有很多个业务线,各个业务线有不同的开发测试同学对接,我们很难做到每次发布都把影响面评估得十分准确。运维层面的监控更多的是被动告警,即用户流量触发了线上 bug,我们才会收到报警,用户体验不够好。我们需要在线上 bug 预警方面变被动为主动,周期性地知晓各个业务线的健康状况。

  2. 小流量下敏捷发现线上问题。通常我们软件的发布都是在凌晨流量非常低的时候进行。发布完成后,回归时间长(靠手动),测试面有限(无法做到次次发布全量回归)。此时需要敏捷构造一波覆盖面全的流量,在小流量背景下,敏捷发现线上问题。

  3. 知晓紧急情况下业务的受影响范围以及后续收敛情况。例如当生产环境出现网络异常等非软件故障时,需要清楚业务层面的影响;当网络恢复后,需要知道业务影响是否都已经收敛。 在此之前这些场景都需要测试人员手工介入,灵活度敏捷度都非常差。有了这套系统后,测试人员可以增加自己关注的场景,场景可以通过主动触发和定时触发来执行,通过告警系统通知到有关人员,做到第一时间排查问题,减少故障影响,降低故障时长。

基础版

1.0 版本我们使用通用的 SpringWeb 搭建,有赞内部称为线上机器人检查。系统结构如下:



1.0 版系统架构图


系统主要为三个模块:


  1. 任务调度模块。该模块将用例执行封装成系统任务,使用 Spring Quartz 来定时触发。对外提供 API 对接有赞发布平台,每当系统发布上线完成后主动触发用例执行。

  2. 测试用例模块。包括业务访问,断言和告警。测试场景需要各个业务线的测试同学投入开发。

  3. 告警模块。对接有赞内部告警平台。



1.0 版流程图


系统将用例分为基础用例和场景用例,支持场景并发或者顺序同步执行。具体执行策略由用例设计者结合具体情况在用例开发过程中设定。

存在的问题

基础版满足了最小可用,这种方式优点在于前期能够快速投入使用,且对于经常写集成用例的人来说成本不高,但对其他人(测试新人、开发、运维等)则不然。概括而言,其缺点主要集中在以下几点:


  1. 业务线一旦多起来,用例代码开发成本提高;

  2. 随着用例数量增加,后期用例维护成本很大;

  3. 用例上线不灵活,每次用例改动需要重新发布;

  4. 无法直观看到运行情况和业务覆盖情况;

  5. 每次执行不区分业务,全量执行;

  6. 用例代码存在冗余,效率比较低。

配置化和可视化

由于这些不可规避的问题,我们重新设计并发布了 2.0 版本。对应解决以上问题:


  1. 测试用例和测试场景支持配置化,可以从管理平台上配置;

  2. 用例配置标准化,给定标准用例结构和断言策略;

  3. 通过管理平台来管理自己的用例,用例改动实时生效,无需发布;

  4. 增加前端展示,通过图表直观展示运行情况和业务覆盖情况,方便不同人群查阅;

  5. 对接发布平台,按照指定的应用名来区分跑哪些用例;

  6. 设计用例执行框架,实现核心代码复用。


新版系统架构图如下:



2.0 版系统架构图


用例模型如下:


字段是否必填说明
用例名称建议命名格式:“用例类型:服务:方法”
用例类型两种类型可选 http 或 dubbo
用例描述场景描述
所属业务用例所属业务阈
请求urlhttp 协议调用的 url
请求头http header
请求参数http 或 dubbo 的请求入参。支持动态参数注入实现用例间依赖
服务名称对应请求 dubbo 协议的接口名(包名+类名)
请求方法http 协议:GET、POST、PUT 等;dubbo 协议:方法名
断言支持多个
是否开启控制开关,关闭后不再运行。默认开启
是否登录开启后,使用默认账号进行登录操作。默认不开启
是否重试开启后,⽤例失败重试1次。默认否
前/后置检查执行⽤例前/后,先执行前/后置检查,失败则中断


* 此处略去了部分有赞内部使用的字段


为了更直观展示线上业务的健康状况我们增加了丰富前端报表:



数据展示


新版本与老版本的主要区别在于:


  1. 将执行流和数据流进行了分离,测试用例设计无需编码,支持配置化,用例作为数据存放到 DB 中重复使用,用例的执行引擎管理用例的执行流。

  2. 对通用的事务进行了封装,比如登录、切换店铺等操作,通过统一的线程池进行管理。

  3. 支持动态参数注入,实现了用例间的相互依赖,后面再单独介绍这块内容。


任务执行流程图如下:



2.0 版流程图


任务执行引擎通过不同的工作线程实现。不同业务用例并发执行,业务内部用例串行执行。系统根据不同的用例的类型(http/dubbo)分发到具体任务流中。



核心类设计

用例间依赖的实现

从用例的复杂度上讲,我们的用例主要分为两大类:单一场景的基础用例和复杂场景的组合用例。组合用例是在基础用例的基础上进行一定的集成,用例的输入输出存在一定的依赖。我们实现用例依赖的方式有两种:


  1. 通过配置用例的前置后置关系。

  2. 通过参数注入。


第一种方式,在配置用例的时候,给它一个前置用例,当然前置用例也是在平台中管理的。这样当执行到该用例的时候,执行引擎会先去执行前置用例。


第二种方式,针对 Json 格式的入参,我们定义如下格式进行参数注入:


`#a,b,c#`


各个字段分别代表的含义为:


a:被依赖用例的 ID


b:被依赖用例响应的字段(key 值),比如:name


c:可选字段,当被依赖值位于 array 里面时,取其 index 下标


举例: {"code":"$#8,data,0#$","type":"$#10,type#$"}


参数注入的流程如下:



参数注入流程图

断言模块设计

在新版系统里面,我们设计了四种类型的通用断言,几乎可以满足我们自己的所有应用场景。这四种类型分别是:


  1. 是否包含。


响应内容包含指定内容为 true,反之为 false。


  1. 非空/null。


响应内容非空/null 为 true,为空/null 为 false。


JSON 特定位置的值的“相等”判断。


这种情况系统首先会将响应内容转换成 json,添加断言时需要指定待比较对象在 json 串中的坐标。如果该坐标上的值与指定的值相等则为 true,反之为 false。


那么如何给一个 json 串的每个值设置一个独一无二的坐标呢?考虑到 json 存在嵌套关系且 key 可能重复,我们通过一种复合 key 的来表示这个坐标,例如有如下 json:


{
"data": {
"list": [
"1",
"2"
],
"info": {
"name": "张三",
"age": 18
}
},
"code": 200
}
复制代码


对标红的值的断言可以这样表示: {"data":{"info":{"name":"张三"}}},如果返回的位置的值为"张三"则判断结果为 true,否则为 false。


  1. 面向 JSON 的伪代码表达式判断


前面三种类型的断言仅满足了部分场景,对于一些复杂的断言仍然无法满足,比如上文 json 中 list size 的断言。为此,我们引入第四种断言方式—伪代码断言。针对 list size 的断言我们可以这样写:


getJSONObject("data")getJSONObject("list").size()>0
复制代码


代码在处理的时候会将该表达式拼接在 json 对象后进行执行。整段代码执行的结果为真断言为 true,否则为 false。


伪代码的动态编译、加载和调用,采用 GroovyShell 来实现。该部分代码实现如下:


public Result compare(String response) {  Result result = new Result();
// 单例获取GroovyShell GroovyShell shell = SingleGroovyUtil.getGroovyShell();
Binding binding = null; JSONObject jsonObject = new JSONObject(); JSONArray jsonArray = new JSONArray(); Object value = null; try { if (response.startsWith("[")){ jsonArray = JSON.parseArray(response); binding = new Binding(); binding.setVariable("data", jsonArray); value = InvokerHelper.createScript(shell.getClass(), binding).evaluate("data." + textStatement); }else { jsonObject = JSON.parseObject(response); binding = new Binding(); binding.setVariable("data", jsonObject); value = InvokerHelper.createScript(shell.getClass(), binding).evaluate("data." + textStatement); }
if((Boolean)value) { result.setSuccess(true); }else { result.setSuccess(false); String msg = JsonUtil.findErrMsgByJsonObject(jsonObject); result.setMsg(String.format("断言失败。断言的内容[%s], 错误描述[%s]", this.textStatement, msg.length()>0?msg:response)); }
} catch (Exception e) { result.setSuccess(false); String msg = JsonUtil.findErrMsgByJsonObject(jsonObject); result.setMsg(String.format("断言时发生异常。ErrMsg=[%s],actual=[%s]", e.getMessage(), msg.length()>0?msg:response)); } finally { // 处理完后,主动将对象置为null binding = null; } return result;}
复制代码

插件化

新版系统满足了用例的可配置化以及可视化的要求,同时也牺牲了一部分的灵活性。例如一些复杂断言的伪代码会非常长,且可读性不高,一不留神就会出错;简单的用例依赖可以满足,复杂的用例依赖却很难满足。比如用例 A 在某些条件下依赖用例 B,其他条件下依赖用例 C,这种复杂依赖关系走配置化并不合适。基于以上考虑,我们在现有的系统的基础上又增加了插件化的特性,来支持复杂用例的接入。



3.0 版系统架构图


插件化的设计思想如下:


  1. 平台对外提供一套用例标准,测试同学开发符合标准的用例添加到平台即可运行。

  2. 用例与平台完全解耦,用例在平台可配置。

  3. 用例支持热插拔,平台无需重启。


用例标准通过接口的形式对外提供,封装成 jar 包暴露出来。用例设计者直接依赖该 jar 包并实现指定接口即可。用例接口定义如下:


public interface AbstractTestCase {
CaseResult before();
CaseResult run();
void after();}
复制代码


用例开发完成后打包成 jar 包上传到平台,一个 jar 包中可包含一个用例也可以包含多个用例。


jar 包上传后平台要做的事情如下:


  1. 动态把 jar load 进 JVM

  2. 解析实现了 AbstractTestCase 接口的类

  3. 按照指定策略调用类中的方法

  4. 上报并展示结果数据


获取 jar 包中实现了 AbstractTestCase 接口的代码如下:


/** * 获取jar包中某接口的实现类 */public static List<Class<?>> getAllImplClassesByInterface(Class c) {  List<Class<?>> filteredList = new ArrayList<Class<?>>();  //判断是否是接口  if (c.isInterface()) {    try {      //获取jar包中的所有类      List<Class> allClass = getClassesByPackageName();      allClass.forEach(clazz -> {        if (c.isAssignableFrom(clazz)) {          if (!c.equals(clazz)) {            filteredList.add(clazz);          }        }      });    } catch (ClassNotFoundException e) {      e.printStackTrace();    } catch (IOException e) {      e.printStackTrace();    }  }  return filteredList;}
复制代码

未来

未来有赞线上拨测系统会提供更丰富的功能,例如更灵活的用例执行策略,核心用例执行频率更高,边缘业务执行频率降低;更全面的报警策略,各个业务方可以自由定制关心的用例,线上问题第一时间触达;支持多机房,目前该系统只在单机房进行部署,有赞核心业务已完成多机房部署,拨测系统也会随之调整;系统支持分布式,为了防范系统单点故障,未来还会考虑进行分布式部署。


目前这套系统可以保障测试同学第一时间知晓有赞线上核心业务异常,将来保障的业务广度和深度会进一步提高,成为有赞线上质量保障至关重要的一环。


2020-03-11 22:201948

评论

发布
暂无评论
发现更多内容

Penpad获OKX 、Scroll生态大力支持,ETH质押创新高

大瞿科技

Higress 全新 Wasm 运行时,性能大幅提升

阿里巴巴云原生

阿里云 云原生 Higress

运维数字化转型必备宝典,13位行业资深运维专家力荐之作

嘉为蓝鲸

数字化转型 数字化运维 IT 运维

全民国家安全教育日,天翼云“红盾”筑牢数字安全防线

编程猫

深入探索垃圾收集

俞凡

Java 算法

从启发式到模型化,京东推荐广告排序机制演化

京东零售技术

算法 广告 搜索推荐 企业号 4 月 PK 榜

一款功能齐全的iOS混淆工具介绍及功能详解

数字化转型究竟能为企业带来哪些巨变?

AMT企源

数字化转型 AMT企源

最新《2023中国企业敏捷实践白皮书》发布|4月18日

爱吃小舅的鱼

敏捷开发 敏捷实践 中国企业敏捷实践白皮书

企业架构设计的一般过程-始于使命愿景和价值观

凌晞

企业架构 架构设计

系统架构基础知识入门指南-上

老张

系统架构 架构设计

企业架构设计的一般过程-以目标为导向

凌晞

企业架构 架构设计

Amazon SageMaker: 拓展机器学习边界,塑造未来创新趋势

亚马逊云科技 (Amazon Web Services)

机器学习 re:Invent 亚马逊云科技 生成式人工智能 Amazon SageMaker

通义灵码×西安交通大学携手打造“云工开物-高校训练营”,解锁 AI 时代编程学习与实战

阿里巴巴云原生

阿里云 云原生 通义灵码

你会拆分产品待办项(PBI)吗?

敏捷开发

项目管理 敏捷开发 软件开发 产品管理 用户故事

Python机器学习实战教程含代码

技术冰糖葫芦

api 货币化 API 测试 pinduoduo API

嘉为蓝鲸WeOps认证体系全面升级:构建专业本地化服务中心网络

嘉为蓝鲸

weops 嘉为蓝鲸

比特币减半:挑战与机遇

区块链软件开发推广运营

dapp开发 区块链开发 链游开发 NFT开发 公链开发

Penpad获OKX 、Scroll生态大力支持,ETH质押创新高

加密眼界

实现iOS App代码混淆

雪奈椰子

智能写手GPT出击!生成测试报告技巧大公开!

测吧(北京)科技有限公司

测试

天谋科技成为信创工委会技术活动单位

Apache IoTDB

揭秘智能写手GPT的测试报告生成技巧

霍格沃兹测试开发学社

有赞线上拨测系统实践(一)_文化 & 方法_有赞技术_InfoQ精选文章