东亚银行、岚图汽车带你解锁 AIGC 时代的数字化人才培养各赛道新模式! 了解详情
写点什么

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

评论

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

编程基础:CPU资源监控

正向成长

CPU调度

流媒体:依托于声网的连麦解决方案

声网

音视频 流媒体

kube-ovn 组网结构

Geek_f24c45

k8s Kube-OVN cni

数字化助力能源高效利用

CECBC

全球国家简码信息表

入门小站

工具

北鲲云超算平台凝聚技术实力,赋能高算力需求行业加速发展

北鲲云

分分合合分分,谷歌医疗走向大败退

脑极体

java拼团小程序源码(毕设)

清风

毕业设计

ebay支付账务系统架构解析之“读”一无二

贾奇 (Jacky)

支付系统 CQRS 读写分离

GraphQL 快速入门【1】简介

码语者

Rest graphql

坍缩的企业

涛哥 数字产品和业务架构

企业架构

MySQL五个常见的高可用方案

hanaper

自定义aop实现Cacheable注解(零拷贝), CacheItemGet,CacheMapGet,CacheMapPut

张音乐

Java 缓存 注解 9月日更

【得物技术】浅谈重复http请求的取消

得物技术

方法 HTTP 场景 crud 请求

测试九剑之-破箭式

薛飞

自动化测试 测试管理

Python Qt GUI设计:UI界面可视化组件、属性概述(基础篇—4)

不脱发的程序猿

Python qt GUI设计 PyQt5

spine动画文件转dragonbones骨骼文件

风翱

9月日更 dragonbones

如何从数据到资产

奔向架构师

数据治理 9月日更

大厂敲门砖!P9技术官级别的顶级并发编程宝典,献给想去大厂的你

Java 编程 面试 程序人生 p9

从零到MySQL架构师学习内容整理

hanaper

Python——内置函数----让你偷懒的工具

在即

9月日更

【SoCC2018论文】DAGOR:微信「大规模微服务过载控制系统」

OpenIM

上线半天下载量破100W!美团大佬的Java性能调优实战手册,超详细

Java 编程 面试 性能调优

用诸葛孔明的智慧,读懂新型数据中心的“四新”机遇与产业逻辑

脑极体

网卡修改网速和buffer

耳东@Erdong

9月日更 网卡

linux之chattr命令

入门小站

Linux

网络攻防学习笔记 Day132

穿过生命散发芬芳

网络模型 9月日更

HTML进阶(二)

Augus

html 9月日更

低代码时代来临,织信Informat携手低码星球探讨「企业数字化转型之路」

优秀

低代码

区块链技术解决信任问题

CECBC

【网络安全】Spring框架漏洞总结(一)

网络安全学海

黑客 网络安全 信息安全 渗透测试 安全漏洞

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