您好!
欢迎来到京东云开发者社区
登录
首页
博文
课程
大赛
工具
用户中心
开源
首页
博文
课程
大赛
工具
开源
更多
用户中心
开发者社区
>
博文
>
自动桩代码生成探讨
分享
打开微信扫码分享
点击前往QQ分享
点击前往微博分享
点击复制链接
自动桩代码生成探讨
自猿其说Tech
2021-10-27
IP归属:未知
26120浏览
测试
敏捷架构
### 1 目的&背景 在单元测试的过程中,为了专注测试单个类内的业务代码,而避免其它代码导致的影响,同时也为了避免某些场景下造数据困难,所以一般会引入桩代码,用于替换测试目标类内所依赖的外部代码,并按测试场景的需要定制外部代码原来预期返回值,从而对目标类的代码进行测试。通常说的单元测试的 mockito 和 powermock 是用于实现这个功能的单元测试桩框架。 因为桩代码并不实现实际的业务功能,只是方法数据的模拟,所以桩代码的核心就是对输入数据返回预期数据,实际上是数据模拟。 单元测试用例更多是开发人员在实际输出,但是这种模块/代码数据依赖的概念在功能/接口测试阶段同样有需要,只不过这个时候测试的目标扩大化了,它是一个接口,或者整个系统,同时,依赖的功能模块也不再是具体的某个类,而是功能模块甚至其它系统。功能测试需要花费大量的时间去进行系统间协调,造数据,测试,再反馈,如此循环。 如果可以对这种模块及系统的数据进行自动模拟,那无疑可以为测试带来很大的便利性。本文将对这个愿景进行探讨。所以这里的 “自动桩代码生成” 的概念将扩大化,不再仅仅局限于单个类代码的数据模拟,而是对系统/模块的数据进行模块,这是整个系统的代码。 下面将对这个模拟进行一些可行性的探讨。 ### 2 整体架构 本方案对于基础数据经常用并且不经常变化的场景,通过数据打桩保存下来,可被下次调用。整体实现结构如下: ![](//img1.jcloudcs.com/developer.jdcloud.com/06a0595c-53fd-4826-9312-4adbc2e9cc2120211027143921.png) 在业务客户端发起调用的时候(调用的协议不限, JSF , http, dubbo, grpc 等都可以),如果正确调用(rpc调用不抛异常/error, https 不返回错误码),则客户端调用结束后将把数据写入桩数据服务(输入数据和输出数据),为做数据桩做准备。所有认为正确调用的数据都不停进入数据池,自动建立一个大桩数据池子;同时,桩数据服务同样也接受手工录入的服务输入输出数据,手工建立桩数据,为特定场景或者新的需求而准备。 当下次客户端调用的时候,如果发现服务端异常,或者没有返回数据的时候,这个时候将从桩数据服务里根据输入值找出相应的输出,如果找到,将直接将相应的结果作为本次调用的结果输出,从而在系统异常或者无相应数据的时候也能够直接提供服务,保证整个业务流程可以继续流转。 这个桩数据要接纳不仅仅是单个应用的数据,单应用场景的价值相对较低。桩数据服务记录了各系统公开的业务接口的数据,所以更适用于跨业务系统的联调而准备。保证各个业务系统可以尽量正常返回,从而完成整个流程的测试。 ### 3 原理示意 ![](//img1.jcloudcs.com/developer.jdcloud.com/4751c488-11b2-4a82-a5ee-ab7518ec726820211027143941.png) - 整个解决方案围绕客户端解决 - 增强客户端的接口proxy字节码,在这层增加额外的处理,用于进行桩数据的处理。 - 当调用完远程方法发生异常或判断为空的时候,根据方法的输入值进行编码, 然后到桩数据服务找数据, 并最终从整个桩数据的 elasticsearch 的数据池里捞相应的数据,并最终以接口的返回值返回 这样就可以在不侵入业务代码的前提下对远程的调用进行干预。 ### 4 方案实现 #### 4.1 不同协议数据处理 不同协议在数据层面会有巨大的差异,比如 jsf 和 http,http 的输入(请求内容)可能分布在 URL,header,request body 这3个主要的地方,而输出(响应)的数据也可能各不一样,可能是XML格式,也可能是JSON,输出的方式即可能是 response body 的方式也可能是 stream 流的方式;而同样的RPC框架,jsf 和 dubbo 在处理额外信息,比如 context 的时候格式也有所不同,同样是 http 包,okhttp 和 apache httpclient 实现方式不一样,甚至使用的底层都可能不同,比如基于JDK的包封装还是基于 netty。所以面对各种不同的协议,需要专门定制不同的处理器来处理输入和输出。 ![](//img1.jcloudcs.com/developer.jdcloud.com/680efb51-fa9f-4743-a237-52f30795292520211027144016.png) 如上图所示:分别对不同协议进行处理,处理方式各不一样。比如 JSF 直接对客户端的接口代理生成的代码进行处理,在得到代理的对象后,再对代理对象的接口进行增强,因为客户端最终的调用的是代理对象,需要拦截代理对象的调用,并进行数据收集/模拟的处理;而像 okhttp 则进行对 client 进行处理即可,拦截到 request 和 response,最终字节码增加的实现可以是同一个,并具体针对不同的方法进行不同的处理。所有的协议处理实现将按插件的方式开发,并以 SPI配置的方式注入,从而避免不必要的包依赖。 下面再对字节码增加进行进一步的描述 #### 4.2 接口增强实现 增强实现主要是在原有代码的逻辑的处理附加 mock 数据的代码,在特定的错误或异常(rpc的exception,http 的 response code, response body 的特定字段值等)的情况按输入去查找现有的数据,并返回。此外, 还需要针对特定的数据结构进行特定的处理 输入输出数据结构的核心定义: ![](//img1.jcloudcs.com/developer.jdcloud.com/37021ef6-2d87-4977-9df4-b3ebee77952220211027144052.png) 即不同协议将根据需要实现不同的数据结构,但都可序列化(JSON方式)成字符串,并最终存到存储(ES)里。各自不同的实现类将需要有将输入数据序列化为JSON字符串和从JSON串反序列化为实际的业务输出对象的能力。 另外,在 Request 方法中将包含一个 getRequestId 的方法,此方法将输入的内容组装转成一个hash字符串,此字符串将用于从存储里查找Mock数据。 #### 4.3 业务流程 结合上面的数据,描述下整体的业务流程以及数据流转 ![](//img1.jcloudcs.com/developer.jdcloud.com/77cdeaad-7625-4589-bcf6-72a1bf532ec020211027144105.png) Mock 增强代码记录调用数据,为Mock数据做准备。 Mock 增加代码在服务调用异常之后, ,尝试查找 Mock 数据,如果没找到,则原样返回,如果找到,则按历史的Mock数据返回。 #### 4.4 数据来源 需要支持Mock数据的自动收集,在运行的时候抓取数据并存入存储中;同时, 由于新需求的加入,对于还来不及收集的数据,允许手工定制数据以支撑新需求的测试,针对新需求的输入数据定制输出。这块其实就是 Mock 数据的手工编辑功能,由于在整体业务数据/流程相对来比较独立,所以不再展开描述。 #### 4.5 特殊处理 不同的协议在数据处理上会有特殊的地方,比如 http 的 stream 流方式,这个不同的协议不同,无法具体描述,但需要在设计时考虑到这个场景,并考虑是否支持,如何支持,如何存储当需要进行跨系统测试/调试的时候,就可以自己根据自己的业务需要造相应的数据(jsf, jmq等),和单元测试的 mockito 一样,不需要真正对相应的业务代码里去操作产生相应的数据,从而更快更方便地验证功能。 ### 5 总结 本文针对自动桩代码生成,做了方案陈述,并且可实现。主要解决了几个问题 1. 外部接口依赖,解决环境不稳定问题及造数困难问题 1. 动态mock 1. 接近实际业务数据 #### 相关参考:概念解释 ##### Java 动态代理 参考 java.lang.reflect.Proxy (https://docs.oracle.com/javase/8/docs/api/java/lang/reflect/Proxy.html)。动态代理应用较为广泛,一般RPC的客户端都是动态代理(当然并不一定是 jdk 动态代理,也可能是 Javaassist,cglib之类的),客户端并没有真正的实现类,而是只有一个接口,动态代理将动态地生成一个实现类,并执行真正的操作, 比如发起远程调用。 ##### 字节码操作(bytecode manipulation) 字节码增强技术,和动态代理不一样的是,字节码增强是对实现类的增强,在不需要代码重新编译的前提下对方法执行前或执行后插入相应的代码,甚至替换整个方法 为什么要使用字节码操作? 这样可以在不用对开发的代码进行任何的侵入的情况进行代码的增强,这样也就可以达到数据模拟的效果,这点和 powermock 是一样的。只不过和powermock不同的是,我们将有目的对常用的调用(jsf, http, jmq)等进行模拟。当然模拟这块对于一些特殊的情况可能需要特殊的处理(比如http请求结果的模拟) ##### Java SPI机制 参考: https://docs.oracle.com/javase/7/docs/api/java/util/ServiceLoader.html 。基于接口编程按需即插即用。一般像各种RPC的Filter实现会采用这种方式,在基本的标准流程上,可以按需加入各种自定义的处理流程,同时原来的整个系统并不需要针对这个新的插件做任何的额外处理。一般这种方式依赖于整体的架构设计 ------------ ###### 自猿其说Tech-JDL京东物流技术发展部 ###### 作者:快递快运技术部 刘爱慧
原创文章,需联系作者,授权转载
上一篇:Vite开发构建工具在京喜达M站中的实践
下一篇:JDK8 如何优雅的处理时间
相关文章
安全测试之探索windows游戏扫雷
Jmeter压测实战:Jmeter二次开发之JSF采样器实现
Laputa自动化测试框架介绍
自猿其说Tech
文章数
426
阅读量
2149963
作者其他文章
01
深入JDK中的Optional
本文将从Optional所解决的问题开始,逐层解剖,由浅入深,文中会出现Optioanl方法之间的对比,实践,误用情况分析,优缺点等。与大家一起,对这项Java8中的新特性,进行理解和深入。
01
Taro小程序跨端开发入门实战
为了让小程序开发更简单,更高效,我们采用 Taro 作为首选框架,我们将使用 Taro 的实践经验整理了出来,主要内容围绕着什么是 Taro,为什么用 Taro,以及 Taro 如何使用(正确使用的姿势),还有 Taro 背后的一些设计思想来进行展开,让大家能够对 Taro 有个完整的认识。
01
Flutter For Web实践
Flutter For Web 已经发布一年多时间,它的发布意味着我们可以真正地使用一套代码、一套资源部署整个大前端系统(包括:iOS、Android、Web)。渠道研发组经过一段时间的探索,使用Flutter For Web技术开发了移动端可视化编程平台—Flutter乐高,在这里希望和大家分享下使用Flutter For Web实践过程和踩坑实践
01
配运基础数据缓存瘦身实践
在基础数据的常规能力当中,数据的存取是最基础也是最重要的能力,为了整体提高数据的读取能力,缓存技术在基础数据的场景中得到了广泛的使用,下面会重点展示一下配运组近期针对数据缓存做的瘦身实践。
自猿其说Tech
文章数
426
阅读量
2149963
作者其他文章
01
深入JDK中的Optional
01
Taro小程序跨端开发入门实战
01
Flutter For Web实践
01
配运基础数据缓存瘦身实践
添加企业微信
获取1V1专业服务
扫码关注
京东云开发者公众号