InfoQ Geekathon 大模型技术应用创新大赛 了解详情
写点什么

接口自动化对比工具实践

  • 2020-05-15
  • 本文字数:4636 字

    阅读完需:约 15 分钟

接口自动化对比工具实践

背景

接口自动化一直以来都是质量保障的重要一环,在接口自动化日常工作中,我们致力于场景的覆盖与结果校验。随着业务的高速发展,高效保质的迭代自动化用例成了我们的一个研究方向,其中用例结果校验的及时性、完整性、可维护性是我们遇到的一个很大的难题。

痛点

笔者所属团队,日常工作是围绕商品相关业务展开。在平时的自动化脚本编写中,我们发现:


  1. 商品模型返回字段多(一个模型一般有几十到上百个字段),逐字段人工断言,成本较高;

  2. 商品原自动化工程里有大量重复的校验逻辑,梳理成本较高;

  3. 随着业务发展部分非核心字段逐渐也变成了核心字段,例如商品编码,现在已经成为了很多商家 ERP 系统识别商品数据的关键标识;

  4. 部分字段更新如何保证其他字段没有被更新掉,尤其是一些存在默认值的字段,更新的时候极易被默认值覆盖。


传统校验方式我们一般只会校验核心字段或者用例相关字段,比如:价格、库存等等。但是基于上述第 3、4 点原因,我们发现需要去做全字段校验,而 全字段校验学习成本高、维护代价大、代码熟悉程度要求高 是面临的三大难题,那么如何做到快速、优雅全字段校验成为我们必须去解决的问题。

目标

我们的目标是争取对用例返回字段进行全量校验,同时也要大幅提升用例编写效率。

场景分析

我们对现有的自动化用例场景进行分析,得到以下结论:


  • 待测试的后端接口一般分为操作接口和查询接口两类;

  • 一个操作类接口落库后的数据一般会对应一个或者多个查询类接口;

  • 查询类接口会返回大量业务字段。


接下来我们分别针对操作、查询这两类接口进行处理。

实践前的准备

为了让大家更好的理解后续内容,我们先对有赞目前的测试环境进行一个概述:(详细内容可参见:有赞环境解决方案),环境示意图如下:



目前有赞测试环境采取的是弱隔离策略,分为基础环境和测试环境。基础环境部署应用的代码分支版本同线上一致,项目环境部署的则是应用特性分支代码,两个环境共用一套存储。当一个业务请求进来时,根据一个标志位(内部简称 sc)来判定是否要走到项目环境,如果请求的是项目环境且项目环境有该应用,那么此请求会被路由到项目环境中,否则请求到基础环境里。

实践

下面介绍一下我们整体思路:


  • 读接口校验:分别请求基础环境和项目环境,对比两个环境的返回结果,如果一致说明代码改动对此接口用例没有影响,进而可以判定用例校验通过;

  • 写接口校验:一般写接口落库的数据可以通过一个或者多个读接口拿到,那么同样的写接口分别在基础环境和项目环境进行落库,只要对应环境的读接口返回结果一致,那么校验通过;

  • 不论读写,都有一些随机字段,为了降低接入成本,需要提供计算忽略字段能力。

读接口校验思路

读接口校验相对简单,分别请求基础环境和项目环境,根据返回值的异同来判定用例是否通过。我们可以借鉴 AOP 的思路,切入点为 dubbo 请求前后,在切面中分别请求基础环境和 sc 环境,根据两次返回值来判定用例是否通过。


整体流程如下:



PS:sc 环境即为部署了应用特性分支代码的环境


根据上述流程图,可以看到重点在忽略字段生成以及比对逻辑,思路如下:


  • dubbo 接口返回值基本都是一个对象,参考 jsonpath 思路,通过递归可以获得一个 Map(k:路径,v:路径值)。举个例子,返回值的类为


public class ItemSavedModel {    private Long itemId;    private Long shopId;}
复制代码


假设返回对象 itemId 为 1,shopId 为 2,那么拆解出 Map 为{"/itemId":“1”,"/shopId":“2”},对象的比较转换为 Map 的比较。通过两次基础环境返回值的比较,不同的路径值对应的路径,就是下次比较要忽略的路径。


对象拆解成 Map 核心代码如下:


 /**     * 获取对象路径     *     * @param obj     * @return     */    public static HashMap<String, Object> getObjectPathMap(Object obj) {
HashMap<String, Object> map = new HashMap<>(); getPathMap(obj, "", map); return map; } private static void getPathMap(Object obj, String path, HashMap<String, Object> pathMap) { if (obj == null) { return; } Class<?> clazz = obj.getClass(); //基本类型 if (clazz.isPrimitive()) { pathMap.put(path, obj); return; } //包装类型 if (ReflectUtil.isBasicType(clazz)) { pathMap.put(path, obj); return; } //集合或者map if (ReflectUtil.isCollectionOrMap(clazz)) { //todo:默认key为基础类型 if (Map.class.isAssignableFrom(clazz)) { Map map = (Map) obj; map.forEach((k, v) -> { if (k != null) { getPathMap(v, path + "/" + k.toString(), pathMap); } }); } else { Object[] array = ReflectUtil.convertToArray(obj); for (int i = 0; i < array.length; i++) { getPathMap(array[i], path + "/" + i, pathMap); } } return; } //pojo //获取对象所有的非静态变量字段 List<Field> fields = ReflectUtil.getAllFields(clazz); fields.forEach(field -> getPathMap(ReflectUtil.getField(obj, field), path + "/" + field.getName(), pathMap)); return; }
复制代码


  • 按照上述代码拆解出项目环境返回值对应的 Map(k:路径,v:路径值),根据上一步骤获得的忽略路径和基础环境返回值,即可计算出两次返回值一致与不一致的字段路径。代码不再赘述,感兴趣的读者可以在留言区讨论。

写接口校验思路

写接口相对读接口会复杂一些,篇幅所限,主要讲解核心逻辑。写接口校验整体逻辑与读接口类似:总共触发三次请求,前两次所有读写接口在基础环境执行,计算出忽略字段以及记录下来基础环境返回值。第三次所有请求都在项目环境,获取接口在项目环境的返回值,接下来排除掉忽略字段,比较基础环境和项目环境接口对应的返回值即可完成校验。


整体流程如下:



  • 重试时机很重要:写接口不同于读接口,读接口可以在不同环境里多次重试,而写接口考虑到幂等性,在数据清理之前是不能发起重试的。清理数据可能在 afterMethod、afterClass、afterTest、AfterSuite 各种阶段,为了保证数据清理代码在第二次执行之前被执行,我们考虑实现监听器方法 ISuiteListener.onFinish 方法来触发第二次。

  • 如何触发第二次执行:TestNG 除了通过 xml 文件触发,还支持通过新建对象来触发执行,我们采用新建 TestNG 对象来触发第二次执行。

  • 忽略字段计算时机:切面需要在用例执行第 2 遍时候进行计算忽略字段,执行第 3 遍进行接口返回值的比较。


//触发三次请求public class GlobalCoverISuiteListener implements ISuiteListener {
public static ConcurrentHashMap<String,Integer> suiteFinishMap=new ConcurrentHashMap<>();

@Override public void onStart(ISuite suite) { if(suiteFinishMap.size()==0 ){ //第一次进来 设置globalCoverFlag为1 后面会new testng两次 System.setProperty("globalCoverFlag", "1"); if(System.getProperty("globalCoverFlag").equals("1")) { suiteFinishMap.put(suite.getXmlSuite().getName(),1);
TestNG tng = new TestNG(); tng.setXmlSuites(Arrays.asList(suite.getXmlSuite())); tng.run(); }
} }
@Override public void onFinish(ISuite suite) {
suite.getResults().forEach((suitename, suiteResult)->{ ITestContext context = suiteResult.getTestContext(); if(System.getProperty("globalCoverFlag").equals("1")) { int before = suiteFinishMap.get(suite.getXmlSuite().getName());
//第二次结束 表示计算忽略字段已经结束 可以进行正常的跑case了 if(suiteFinishMap.get(suite.getXmlSuite().getName())==2){ suiteFinishMap.put(suite.getXmlSuite().getName(),++before); System.setProperty("globalCoverFlag", "0"); return; } suiteFinishMap.put(suite.getXmlSuite().getName(),++before);
TestNG tng = new TestNG(); tng.setXmlSuites(Arrays.asList(context.getCurrentXmlTest().getSuite())); tng.run(); }

}); }}
复制代码


  • 测试用例中一般都存在着读、写接口两类用例,只有写操作用例需要在三遍 suite 中均执行,读操作用例只需要在最后一次 suite 执行即可。综合考虑,前两次的 suite 希望只执行写操作的 case。因此实现 testNg 监听器方法 IMethodInterceptor.intercept,拦截器上只返回此次 suite 执行的测试用例,从而达到前两次只执行写操作的 case。我们采用在写操作用例挂上一个注解,标识为写操作用例,方便拦截器判断用例类型。核心代码如下:


//case层方法加上注解    @WriteCaseDiffAnnotation    @Test    public void testAdd(){        //写操作        //读操作    }
// IMethodInterceptor.intercept中判断注解,获取写操作用例@Override public List<IMethodInstance> intercept(List<IMethodInstance> methods, ITestContext context) { List<IMethodInstance> testMethods = new ArrayList<>();//获取写操作测试用例方法 for (IMethodInstance methodInstance : methods) { if (isQualified(methodInstance.getMethod())) { testMethods.add(methodInstance); } }
if (System.getProperty(GlobalOperatorType.GLOBAL_COVER.getStr()). equals(GlobalOperatorType.GLOBAL_COVER.getFlag())) {//前两次suite,只返回写操作测试方法 return testMethods; } else {//最后一次suite,返回所有的测试方法 return methods; } }//判断测试方法是否有WriteCaseDiffAnnotation注解 public boolean isQualified(ITestNGMethod iTestNGMethod) { Method m = iTestNGMethod.getConstructorOrMethod().getMethod(); WriteCaseDiffAnnotation writeAnnotation = m.getAnnotation(WriteCaseDiffAnnotation.class); Test test = m.getAnnotation(Test.class); if (writeAnnotation != null && test != null) { return true; } return false; }
复制代码

不足

  • 目前仅支持 dubbo 接口,后期考虑扩展到前端 node 层接口校验

  • 目前强依赖基础测试环境,为了更好的兼容性,后期考虑引入存储方式来解除基础环境依赖


本文转载自公众号有赞 coder(ID:youzan_coder)。


原文链接


https://mp.weixin.qq.com/s/_9HIrichpu4sXXXASTWVBQ


活动推荐:

2023年9月3-5日,「QCon全球软件开发大会·北京站」 将在北京•富力万丽酒店举办。此次大会以「启航·AIGC软件工程变革」为主题,策划了大前端融合提效、大模型应用落地、面向 AI 的存储、AIGC 浪潮下的研发效能提升、LLMOps、异构算力、微服务架构治理、业务安全技术、构建未来软件的编程语言、FinOps 等近30个精彩专题。咨询购票可联系票务经理 18514549229(微信同手机号)。

2020-05-15 14:052506

评论

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

常规加密算法是什么?原理是怎么样?有哪些?

行云管家

算法 加密算法 国密

通过支付网关提高第三方支付接入效率

产品海豚湾

产品经理 产品设计 支付系统 产品架构 11月月更

web前端和java培训学编程哪个更好

小谷哥

火山引擎DataLeap的Data Catalog系统公有云实践

字节跳动数据平台

大数据 火山引擎 大数据研发

开源共建 | TIS整合数据同步工具ChunJun,携手完善开源生态

袋鼠云数栈

大数据 开源 数据同步工具

跨越速运如何构建实时统一的运单分析

StarRocks

#数据库

基于云边协同架构的五大应用场景革新

阿里云视频云

阿里云 边缘计算 边缘云

老工程师荐读!PCB设计避坑指南

华秋PCB

PCB PCB设计

瓴羊Quick BI自助式报表分析工具,令企业的运营服务更高效

夏日星河

教你用JavaScript完成进度条

小院里的霍大侠

JavaScript 编程开发 初学者 入门实战

制造业数字化发展,瓴羊Quick BI引起了需求者的关注

夏日星河

QuTrunk与MindSpore量子神经网络初探

启科量子开发者官方号

人工智能 ai框架 量子编程

亚信科技亮相南京软博会,数智赋能百行千业

亚信AntDB数据库

AntDB aisware antdb AntDB数据库

元器选型攻略之 电阻

元器件秋姐

元器件知识 元器件科普 电阻 电阻定义 常见电阻

磐久网络|揭秘阿里云HAIL数据中心网络

云布道师

阿里云 数据中心 基础设施建设

java程序员,是不是很想进字节跳动?开发三年的我拿到了入职通知

钟奕礼

Java 程序员 java面试 java编程

阿里P8面试官总结的《2022最新年底java面试题》,搞定90%以上的技术面

钟奕礼

Java 程序员 java面试 java编程

Intel Arch SIG:介绍下一代数据中心互联协议CXL及在龙蜥的规划 | 第 54 期

OpenAnolis小助手

开源 直播 intel 龙蜥大讲堂 CXL

java 环境变量配置详细教程(2023 年全网最详细,没有之一)

千锋IT教育

前端培训程序员不好招吗,应该怎么学习

小谷哥

供应链-数字化招投标/采购/供应商管理系统

金陵老街

数字化 Java‘’ Vue 3 spring-boot

New Features | NFTScan 推出 BlueChip、Watch List、Activity Overview

NFT Research

区块链 NFT 数据基础设施

Service Mesh 的下一站是 Sidecarless 吗?

SOFAStack

MOSN

华夏银行:详解iDo平台一体化运维的落地过程

嘉为蓝鲸

运维 金融 银行 数字化

深入了解瓴羊Quick BI,对于商业智能BI发展情况更好分析

巷子

云计算和虚拟化的三个小区别简单说明

行云管家

云计算 虚拟化

TCL 基于 StarRocks 构建统一的数据分析平台

StarRocks

#数据库

LeetCode题解:938. 二叉搜索树的范围和,栈,JavaScript,详细注释

Lee Chen

JavaScript LeetCode

火山引擎 DataTester 智能运营,帮企业实现“千人千面”精准营销

字节跳动数据平台

A/B 测试

透明LED屏幕如何设计显示效果更好?

Dylan

LED LED显示屏

降价背后,函数计算规格自主选配功能揭秘

阿里巴巴云原生

阿里云 云原生 函数计算

  • 扫码添加小助手
    领取最新资料包
接口自动化对比工具实践_软件工程_魏士超_InfoQ精选文章