HarmonyOS开发者限时福利来啦!最高10w+现金激励等你拿~ 了解详情
写点什么

自研框架 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:041034

评论

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

区块链支付系统源码开发,USDT承兑支付平台

13530558032

一条龙!CI / CD 、打造小团队前端工程化服务

久违

Vue 大前端 jenkins React

【API进阶之路】破圈,用一个API代替10人内容团队

华为云开发者联盟

内容 编辑 API 华为云 文本摘要

LeetCode题解:84. 柱状图中最大的矩形,双循环暴力,JavaScript,详细注释

Lee Chen

大前端 LeetCode

35K成功上岸华为商城事业部,只因学透了这几个开源的商城项目

Java迁哥

Java 华为 源码 资料 商城项目

程序员如何获取一份高薪工作?阿里P8大牛给你一些中肯的建议

Java迁哥

Java 华为 程序员 面试 资料

区块链交易所开发源码,数字货币交易所app开发

13530558032

CPU中的程序是怎么运行起来的(预告篇)

良知犹存

cpu

JDK8 Unsafe.java 源码

Darren

源码 并发 CAS 代码注释 unsafe

Java创建对象的方法有哪些?

奈学教育

Java

分析HiveQL 生成的MapReduce执行程序

任小龙

高效程序员的45个习惯:敏捷开发修炼之道(7)

石云升

敏捷开发 晨会

【运维探讨】RPA落地实践,提升IT运维工作效能!

嘉为蓝鲸

RPA 运维自动化 标准化 系统运维 流程

ArCall远比你想象的要强大的多

anyRTC开发者

WebRTC 在线教育 直播 RTC 安卓

2019年我最喜欢的三款数码产品。

徐说科技

手机 苹果

鲲鹏迁移第一批吃螃蟹的人,践行技术国际化

华为云开发者联盟

鲲鹏920 服务器 华为云 ARM芯片 X86

vivo商城前端架构升级-总览篇

vivo互联网技术

node.js Vue 大前端 架构设计

面经手册 · 第9篇《队列是什么?什么是双端队列、延迟对列、阻塞队列,全是知识盲区!》

小傅哥

数据结构 小傅哥 队列 ArrayDeque

架构设计复杂度来源

escray

学习 从零开始学架构 架构师预科班

为什么阿里巴巴的程序员成长速度这么快,看完他们的内部资料我明白了

Java迁哥

Java 阿里巴巴 程序员 成长 笔记

Docker 网络模式详解及容器间网络通信

哈喽沃德先生

Docker 容器 微服务

Java中强、软、弱、虚四种引用详解

古月木易

Java

Java中强、软、弱、虚四种引用详解

奈学教育

Java

花两个半月吃透这份Java手打面经,成功从外包上岸到京东

Java迁哥

Java 学习 腾讯 面试 资料

java安全编码指南之:Mutability可变性

程序那些事

Java java安全编码 编码指南 可变性

拥抱K8S系列-01-CentOS7安装docker

张无忌

Docker centos 运维

实战案例丨使用云连接CC和数据复制服务DRS实现跨区域RDS迁移和数据同步

华为云开发者联盟

迁移 灾备 数据复制 云连接 数据同步

Java创建对象的方法有哪些?

古月木易

Java

9块钱,构建个私有网盘,关键不限速

华为云开发者联盟

网站 OBS 在线网盘 华为云 云存储

JVM中unsafe.cpp源码

Darren

c++ 源码 JVM unsafe

week12 homework

burner

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