写点什么

自研框架 pmock 介绍

  • 2020-03-22
  • 本文字数:4473 字

    阅读完需:约 15 分钟

自研框架pmock介绍

一、为什么要做单元测试?

工作多年,经历了很多 bug,感觉大部分 bug 基本都是逻辑不严密或者粗心导致。系统简单还好说,如果复杂,大部分时间花在环境启动集成连通上,这时,bug 造成的线上问题,工单、客诉,影响非常大。并且后续善后也非常耗费时间,或有可能因处理 BUG 产生其他问题。


开发流程中能解决上述问题的,我觉得最重要的是单元测试。当某块程序写完后,case 覆盖率全面的单元测试,就是一个 bug 扫雷器,具有强大的侦查能力,以前需要 debug 几个小时,现在几分钟就能找到。当所有程序写完,通过 testNG 将相应的所有单元测试组合在一起,一键做回归测试,不用再害怕修复 bug 引起别的 bug。


目前情况,开发完毕测试直接进行集成和连通性测试;或者等着依赖的模块完成,进行自测。现在的系统都非常复杂,如果这样测试,可能产生各种各样的问题。

二、什么是单元测试?

单元测试是指对软件中的最小可测试单元进行检查和验证,准确、快速地保证程序基本模块的正确性,主要是开发人员或测试研发发起。在 java 系统中,最小指一个方法的测试。单元测试好处多多。


  • 通过先测试最小模块,保证最小模块的质量来最大保证系统质量;

  • 可以倒逼对程序的抽象、重构,达到“眼中无码、心中有码”的境界;

  • 能更好理解 TDD(测试驱动开发)、BDD(行为驱动开发);

  • 积累一定的单元测试训练后,即使直接开发代码,也能更好做到代码的提炼和抽象 。

一、单元测试示例和问题

先看看要进行单元测试的类 PersonBusinessServiceImpl 和被测试方法 queryStudents。queryStudents 方法是从不同性别的人员中,挑选出学生,返回去。


下图大红框里是逻辑,是要覆盖测试的;小框里是测试的 queryStudents 依赖的数据和对象,是 personBusinessDao 的 queryPersonList,也是重点要造的数据。(这个方法的逻辑和依赖比较简单)



1、下图单元测试,从男性人员列表中,筛选出学生列表。即对 PersonBusinessServiceImpl 的 queryStudents 方法进行单元测试。setUp 里对 queryStudents 方法里依赖的对象 dao 进行赋值。



问题:setUp 创建的 dao,如它里面依赖的东西路径很多很深,需要层层进行 new 对象赋值,操作麻烦,会造成 setUp 很多。而且依赖 personBusinessDao 的 queryPersonList 返回的数据,提前无法确定,程序运行时才知道。


2、再看看自测的单元测试(用 testNG 代替 junit)。下图,使用 spring 的依赖注入,代码简单。



问题:实际上这个不是单元测试,而是集成测试、连通测试。queryStudents 方法里可能调用很多外部系统,比如数据库。且这种配置直接将整个 spring 容器启动,很耗时,如系统复杂更耗时(我开发了一个插件,可以只加载特定的 bean,快速启动 spring 容器)。


3、能不能对 queryStudents 方法里依赖的对象,进行简单赋值?可以,使用 mock 框架测试,前面的单元测试鸡汤终于给勺子了。


mock 测试定义:对于某些不容易构造或者不容易获取的对象,用一个虚拟的对象来创建以便测试的测试方法。实际在分布式系统里,不容易获取的对象不是某些,是全部。


下图是 mockito 框架的示例。图中红框里,通过 mockito 框架,mock 了一个 PersonBusinessDao 对象和对象方法 queryPersonList 的返回数据(没有设定 queryPersonList 的输入参数)。



mock 的关键代码:有 when、thenReturn。还有其他的一些函数,如下



mockito 相比 easymock 框架已经算比较好用的 mock 框架,下面再看一个更好用的。


4、groovy 语言的 spock 框架,在 jvm 上运行,where 的参数设定甩了其他框架几条街。这个框架充分利用了 groovy 的动态语言特性,书写很方便,也兼容 java 的书写风格,通过不同 block 块让代码简单清晰,可以 BDD 开发(就是先写每个 block 块的详细描述,再去开发实际代码),有 given、and、when、then、where。


二、几款 mock 框架对比

spock 和其他 mock 框架的对比如下



mock 框架优点


  • 模拟资源

  • 隔离系统或模块

  • 并行开发

  • TDD 模式开发

  • 快速演示

  • 覆盖度广


mock 框架缺点


  • 需要很多硬编码。因为测试覆盖度广,很多不同的 case 数据需要硬编码

  • 侵入性强。好用的 spock 框架,很难和 spring 风格的单元测试结合起来

  • 应对需求不足。需求变化时,单元测试和 case 难以维护

  • 学习成本高

看了不少单元测试的书和文章,感觉里面大量的测试 case 和覆盖率让人负担很重。虽然我曾为一个模块花了整整两天时间,编写大量的单元测试,运行他们的那一刻,获得了深深的满足感;也节约了大量测试时间,面对复杂的逻辑和分支流程,添加几个 case 运行即可。并且,在变更需求加逻辑和分支的过程中,这些写好的单元测试仍然能发挥很好作用。但写单元测试确实非常耗时。同时,我也不认同测试 case 覆盖率:测试你认为容易出错的地方。实际上,程序员最容易阴沟里翻船,在认为最保险的地方出错。所以就有了开发一款 mock 框架的初衷。


在我心目中,mock 测试应该无侵入式,0 学习成本,单纯保存 case 的输入参数和输出参数就好了。所以,按照这个产品思路,制作了一个 mock 框架,先取名 pmock。


案例:按照习惯用 spring 集成 unit 进行测试的风格,只要在 vm 参数添加


-javaagent:realpathpmock-agent.jar 即可无侵入进行 mock 测试(后面有示例设置 javaagent)。



具体怎么实现的,先讲讲上面提到的产品思路:单纯保存 case 的输入参数和输出参数。如下图示意,只关心 mock 对象(可以是内部或者第三方的接口)的方法 queryPersonList,在不同的输入参数,响应不同的返回参数。



以下示例图是更直观的影响,图中是针对不同入参的返回,可以随意编写 cese。



具体如何使用,以下是配置介绍,需要依赖 pmock-agent.jar 包和 case 配置


1、首先需要本地配置 case 和初始化


  • 在自己工程下的测试(test)目录下资源(resources)目录,添加 pmock 文件夹。



  • pmock 文件下有 initConfig.properties 文件,里面的属性 loadSource=local,表示告诉 pmock 从本地硬盘也就是 caseConfig 目录下读取 case 文件;如果 loadSource=net,表示告诉 pmock 从 pmockserver 中心服务器,拉取 case 配置。

  • caseConfig 下,每个被 mock 的类,可以由你创建一个 groovy 脚本文件,并且根据测试需要,创建 mock 方法,方法内是不同的 case 数据,也就是一堆 if else。以下是详细示意图。如果不确定脚本写的是否有问题,可以在 main 函数里进行编写测试。



2、那么 mock 是如何执行这些 case?


  • 首先是无侵入式的 mock,通过-javaagent:realpath\pmock-agent.jar 进行配置。主要适用于被 mock 的对象是类,不是接口。idea 里的配置示意图。至于 eclipse、tomcat 如何设置 javaagent 可以自行解决。




前面提过 spring 的单元测试,可以无缝使用 pmock。但做真正的单元测试,是不会启动非常耗时的 spring 容器的。以下图示意,这时注销头两行的 spring 启动,测试类=null 需要制造测试类对象,这时加上 javaagent 就可以正常运行了(文章最后,我会演示如何不用注销也不会启动 spring 容器的)



  • 接口的 mock 创建。如果被 mock 的对象是接口,是不能 javaagent 的,需要硬编码(但算不上侵入,如上图那样显式声明对象而已)。很简洁,最大兼容了其他 mock 框架的函数式编程风格,且非常灵活。



mockTarget、mockOject、mockField、target 让 mock 一目了然,可以连续 mock 多个对象。注意:mockTarget 和 mockOject 位置可以互换,但 mockOject 和 mockField 最好成对出现,且每对里的 mockOject 在 mockField 前面;或者可以类似这样:



另外:当然如果觉得 javaagent 麻烦,也可以对类进行 mock。mockObject(PersonBusinessDao.class)换成 mockObject(PersonBusinessDaoImpl.class)就好。


注意:mock 接口对象,就要创建接口名的 groovy 文件 case,mock 类对象,就要创建类名的 groovy 文件 case。


  • 继续看不同的 mock 创建风格(这个示例可以直接看代码)。红框里极简式 mock 风格,完毕后,被测试类的实例可以继续使用。



如果不想使用函数式风格,可以继续用传统严肃的风格,图中两种。



注意:直接使用方法,是因为类被静态引入 import static com.jd.jr.pmock.agent.Pmock.*;

更多示例可以参考源码。这章讲讲产品实现的技术栈和原理。里面主要用到了 javassist、fastjson、groovy。查看源码的 pmock-agent 工程,只依赖这 3 个:


  • 接口 mock 对象的产生,主要原理就是进行代理。javassist 进行接口或者类的代理。另外完全无侵入式通过 javaagent 实现,里面主要对需要 mock 的类(接口不行)进行方法植入代码。javaagent 的思路主要参考了京东金融 apm 产品 sgm 的实现思路。

  • fastjson 方便快速序列化成对象,比 gsoon 好用。

  • groovy 用来做 case 编写。使用 groovy 是因为第一版风控规则就是动态执行 groovy 脚本,也因为对 spock 框架的惊艳,groovy 脚本学些成本非常低。注意:如果执行 groovy 脚本里 main 测试好使,执行 mock 却不好使,记得按照 java 风格加标点符号即可。


遇到的坑:使用 4 天时间开发,一半时间花在了 javassist 的代理实现上;javassist 下的类,进行代理操作,会擦掉泛型,没法使用,不断试版本也不行。最后逛官网文档,发现 javassist.util.proxy 下的 ProxyObject 很好使,操作简单,比 jdkproxy 代理接口更简洁。可以看源码 JavassistHelper 实现了多种代理。一开始就应该对接口进行 jdkproxy 代理,对类使用 javassist 植入方法代码的代理方式,可能速度更快点。对类使用 javassist 植入方法代码的代理方式,已经实现,但是破坏了加载器的双亲委托机制,怕有潜在风险。也可以使用别的如 cglib 进行代理,可以更方便点。javassist 文档:


http://jboss-javassist.github.io/javassist/html/overview-summary.html


遗留问题


  • pmock 如何和自动化测试结合起来,是下一步需要考虑的

  • 如何像 spock 那样方便的设置输入参数和响应数据。

  • 最后,单元测试虽好,但集成测试和连通测试也是不能替代的,甚至有时集成测试的 case 应该比单元测试更多。

番外

、如何将 spring 和 pmock 很好的结合起来。如图,在 setUp 里,进行开关配置判断,如果走 spring 测试,通过编码启动 spring 容器,不是注解方式。



、为了真实验证 rpc 接口,类似 http 请求,做了 PlayRpc 接口示例,并使用了 jdkproxy 进行代理,并且可以通过 spring 配置注入。可以看看 jdkproxy 的接口代理实现,和 javassist 的 ProxyObject 对比下,确实麻烦些。


、前文提到,直接加载整个 spring 容器非常耗时,如果真要启动 spring 容器,可以精简启动的 xml 配置。ScanDependencyBeanUtil.java 这是我之前写插件,扫描被测试类要依赖的注入对象,将依赖的注入单独配置 xml,通过上图 ClassPathXmlApplicationContext 进行变动启动 spring 容器。这个插件也可以用来检测被测试类的简洁性,如果依赖东西太多,可以考虑重构。


四、pmockserver。case 的脚本从本地放在配置中心,让工程里的测试代码更加简单。支持使用 groovy、js、python、ruby 等多个脚本语言配置 case。在工程里设置好要从 local 还是 net 取 case 执行就可以了,且设好 pmockserver 的 url 地址。pmockserver 测试地址:172.25.35.164 pmock.jd.com



如感兴趣可以咚咚联系作者,欢迎沟通交流~


2020-03-22 21:041049

评论

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

支付宝花呗分期如何接入

盐焗代码虾

支付宝 经验分享 花呗分期

说说开放签电子签章开源的这一年

开放签开源电子签章

开源 电子合同 年终总结 电子签名 电子签章

【视频】小甲鱼零基础入门学习Python(全96集)

Geek_bbbdb0

Python

社区规划|隐语开源社区新年规划及寄语(附演讲视频)

隐语SecretFlow

隐私计算 开源社区 隐语 数据要素流通

Vision Pro 5 月将在中国区发售;全球科技大厂 1 月已裁员 32000 人丨RTE 开发者日报 Vol.145

声网

当我们一起走过 2023|Apache Doris 年度时刻盘点

SelectDB

跨境电商新风潮:充分发挥海外云手机的威力

Ogcloud

云手机 海外云手机 云手机海外版 国外云手机

TitanIDE v2.8.0正式发布,模板市场来袭!

行云创新

ide CloudIDE 代码编辑器 云端IDE 云端IDE+大模型

PS插件-模拟真实投影拖尾阴影工具 Shadowify v1.0.1

Rose

Navicat Premium 15 :专为简化数据库的管理及降低系统管理成本而设计

Rose

鸿蒙星河版启航!系统级原生智能实现应用「零成本」AI化

新消费日报

已解决org.springframework.context.NoSuchMessageException异常的正确解决方法,亲测有效!!!

小明Java问道之路

技术标准|隐语技术标准的 What & Why & How

隐语SecretFlow

技术标准 隐私计算 数据要素流通

网站被攻击有什么办法?

德迅云安全杨德俊

聚焦大模型!隐语技术团队研究成果被 ICASSP 与 ICLR 两大顶会收录

隐语SecretFlow

隐私保护 数据安全 大模型 隐语

AE脚本-真实三维立体文字标题排版空间翻转组合动画 TypeMonkey3D

Rose

关键帧缓入缓出曲线调节控制操作AE脚本 Kease for mac

Rose

AE脚本-智能快速图层对齐工具 Align Pro

Rose

solidity案例详解(二)众筹合约

BSN研习社

区块链 Solidity

完蛋!我把AI喂吐了!

有道技术团队

人工智能 大模型 QAnything

Flink 2.0 状态存算分离改造实践

Apache Flink

敏捷产品是双轨开发而非双轨制

ShineScrum

国内第一个开源免费电子签章系统发布了

开放签开源电子签章

开源 电子签章 开放签

聊聊在不确定环境下的个人成长

Phoenix

1 月 Web3 游戏行业概览:市场实现空前增长

Footprint Analytics

区块链游戏 gamefi

HarmonyOS SDK 助力新浪新闻打造精致易用的新闻应用

HarmonyOS开发者

HarmonyOS

EMQX Enterprise 5.3 发布:审计日志、Dashboard 访问权限控制与 SSO 一站登录

EMQ映云科技

影响2024年Web3赛道的三大事件

TechubNews

揭秘海外云手机的诸多优势

Ogcloud

云手机 海外云手机 云手机海外版 国外云手机

【视频】互联网Java工程师面试突击训练(三季)

Geek_bbbdb0

Java 面试

星星点灯——华为FTTR-B,照亮千行万业的数字化前程

脑极体

AI

自研框架pmock介绍_文化 & 方法_京东数字科技产业AI中心_InfoQ精选文章