今天分享主要面向java语言的开发和测试开发同学。 先说单元测试、mock测试的定义,再比较几个主流的用于单元测试的mock框架。 说说我遇到的痛点,我在跟踪了公司很多工程的git源码,很少同学进行单元测试,即使做单元测试,java开发童鞋也是使用springjunit,springjunit实际上要启动整个spring框架,相当于基于联通、联调式的单元测试。 根据我的痛点和大家面临的问题,设计了产品思路,然后快速用代码实现了一个mock框架,右面迭代了几个版本,添加了好几个特性。
一、单元测试&mock
1.1 单元测试定义
定义:为检查某个方法是否如预期工作,而写的测试代码。
单元:代码中可度量的最小单元,方法或者函数。
结果:不同的case输入对应的输出是否与预期一致。
1.2 测试层次
如微基准测试、单元测试、功能测试、集成联调测试。如下图,还有一些,我没列出来。
1.3 单元测试的例子
public int sum(int a,int b){
return a + b;}@Testpublic void sumTest(){
int sum = sum(1,2);
assertTrue(sum==3);}
例子很简单,现实中很少这样简单的代码,现实中的代码很复杂,相互依赖多。就引发了mock造假数据进行测试。
二、mock测试的简介
定义:用来虚拟制造对象及数据的,被虚拟制造的对象。
作用:辅助单元测试快速进行的主要手段。
原因:被测试的方法里,依赖的对象,需要被快速被虚拟制造。
mock就是俗称的挡板。
mock测试的好处,主要是隔离系统或者模块,快速并行开发测试和演示。
当然单元测试本身也会倒逼你进行代码抽象和简洁。通常进行codereview,从单元测试入手,是比较鸡贼的好方法。
2.1 被测试方法的示例
public List saveStudent(Student student){
List<Student> studentList = studentDao.query(student);
。。。。。
studentDao.save(student);
}
省略了很多代码(可以见文末的github源码)。这示例里,case做的不太好,篇幅限制。 mock对象:studentDao,mock方法:query、save。 用例case:输入参数student,mock的返回值,示例里没有展示。
2.2 主流mock框架
java用于单元测试的主流mock框架:easymock、mockito、powermock、spock。 这些框架要学习的关键词包括:given、and、when、 then、 thenReturn 、andReturn、 verify、 where、 times、 expect、 replay等…… 底层实现原理:无非是对被mock的类、接口、方法,进行动态代理或者字节码增强;
2.3 Mockito使用示例
左边图片是被测试的方法,里面调用几个外部对象,包括dao、rpc,然后根据返回的数据进行过滤、处理。右边是mockito框架进行mock测试,可以看出用了刚才提到的很多关键词。 这些都是高阶函数,目前jdk8也支持了函数式编程。函数编程用户体验好。
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when;
可以通过import static 引入让高阶函数达到最简易。
2.4 Spock使用示例
它使用groovy脚本编写。 groovy另一个杀手级应用是gradle,代替xml实现maven经纬度的繁琐配置。 groovy最大程度兼容了java语法,同时也能用自己的胶水语言风格,也能和java所用框架轻易结合起来,比容spring。 左边图不变,是被测试方法内容。 右边是groovy的mock测试,坐过BDD开发的,应该感到很亲切。
以上介绍了两款mock测试框架,熟悉java开发的同学,应该看出来了,有些缺点。
2.5 主流mock框架缺点
基于以上问题,我根据痛点出发,琢磨了自己的产品思路。
三、pmock框架介绍
我做的这个产品,取名pmock。主要特点:
-
集中管理case、cese
-
支持多种jvm脚本编写
-
0学习成本
-
无侵入兼容springjunit测试。
如下图的概览。
3.1 pmock代码学习成本
如上图的mockTarget、mockObject、mockField、target四个词。
实现原理:是通过对被mock的类、方法,绑定case脚本文件,进行代理或者代码增强。
如下图:被mock的类和caseConfig目录下的脚本文件一 一映射。可以支持js、groovy、ruby、python等脚本。
3.2 具体使用讲解
-
PersonBusinessDao.java里有queryPersonList方法,所以需要对PersonBusinessDao的方法queryPersonList进行mock,那么如上图脚本文件里也有。
-
上图可以认为就是某个被mock方法的case管理,case无非就是一堆if else,根据输入参数,返回预期想要的数据。
-
输入参数由pmock框架对输入对象序列化成json字符串,然后返回的json串有pmock反序列化成对象。当然如果是基本类型,pmock也会自动识别。
另外,如果要返回异常、设置超时、设置响应时间,都可以在脚本的方法里,根据脚本语言特性自己编写。
3.3 PMock特性
详细介绍下pmock优点的几个特性
特性1:兼容springjunit测试,零侵入。即启动spring容器进行单元测试。
下图是正常的springjunit单元测试,实际上PersonBusinessService是被pmock框架进行代理注入的。最终queryStudents方法里面依赖外部调用,都走mock。 不小心创造的好处:单元测试之外的mock。 最后一个特点,可以方便让基于spring框架的微服务之间快速进行各种case的mock测试。不用跨网络,在本机调用就可以。 强调一点:一定要对spring容器启动进行优化,不该注入别注入(可以索要ppt,里面讲如何优化spring容器启动)。
特性2:显式使用pmock方法,侵入性极简。使用了mockTarget、mockObject、mockField、target四个词进行mock制作。
特性3:case在线集中配置,让case配置彻底脱离工程,这和配置中心一个原理。
右图和左图在一个页面。右图可以在线运行你左图配置的case,在线输入参数,然后返回你预期的数值。 pmock框架启动,如果不走本地工程case映射,将向case中心请求case文件。源码工程pmockserve需要单独部署服务器,用于case配置中心。
特性4:也是最早的版本,使用探针javaagent无侵入
下图红框里即是通过探针进行代码增强的。现在不鼓励使用这个特性,容易忘记配置javaagent,且不能实现接口mock。