免费下载案例集|20+数字化领先企业人才培养实践经验 了解详情
写点什么

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

评论

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

【JavaWeb】Java Web三大组件之Filter过滤器

No8g攻城狮

javaWeb #web

从非洲到全球,看华为云连接CC如何助力出海企业更好发展

IT科技苏辞

未来智安入围《2022年度中国数字安全能力图谱》威胁检测与响应领域能力者

未来智安XDR SEC

“灵、简、畅、安”,就选华为云桌面

科技之光

华为云桌面为建筑行业BIM落地实施提供有力支撑

科技之光

架构实战营 3-6 消息队列架构详设随堂练习

西山薄凉

「架构实战营」

HTTP响应首部字段

穿过生命散发芬芳

HTTP 12月月更

Maya的7个实用操作技巧

Finovy Cloud

云渲染 Maya,渲染

华为云弹性负载均衡服务,如何助企业应付流量压力

科技说

国内首家!博睿数据获信通院AIOps能力成熟度模型优秀评级

博睿数据

智能运维 博睿数据 Swift AI K可观测性 荣誉奖项

有奖评测 | Serverless 应用引擎 SAE 征集令开启

阿里巴巴云原生

阿里云 Serverless 云原生

华为云桌面,助力企业智慧办公

科技之光

《数字经济全景白皮书》中国产业数字化趋势报告2023

易观分析

产业数字化 报告

华为云连接CC——多场景构建数据网,助力企业享受高质量办公体验

IT科技苏辞

刨根问底系列之grpc-java入门

零点999

软件测试丨一文搞定 Postman 接口自动化测试

测试人

软件测试 Postman 自动化测试 接口测试 测试开发

NFTScan 2022 年度总结

NFT Research

NFT 数据基础设施

2022年混过的那些SAP项目

SAP虾客

wms 2022年 SAP-QM

华为云弹性负载均衡ELB,如何保障服务器不瘫痪?

科技说

使用NineData实现企业级数据库备份, 数据备份告别“拆盲盒” ?

NineData

sql 数据恢复 多云架构 数据管理工具 数据备份

主数据的3大特征、4个超越和3个二八原则

用友BIP

Fastjson的反序列化漏洞复现

网络安全学海

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

时序数据库 TDengine 签约华锐技术,助力行情数据处理分析

TDengine

数据库 tdengine 时序数据库

怎么搭建自己的小型渲染农场?个人渲染农场配置

Renderbus瑞云渲染农场

渲染农场 搭建渲染农场

大势所趋_ 华为云企业交换机ESW助力智慧医院转型

科技说

Web3 聚合平台 AIDAMETA,圣诞BNB 大放送

股市老人

【前端】JS(javascript)中this的几种用法实例详解

No8g攻城狮

JavaScript js

华为云全球加速GA,为现代企业跨国办公保驾护航

清欢科技

微服务洞察,让微服务更透明

阿里巴巴云原生

阿里云 微服务 云原生

基于声网 Flat 实现“成语解谜”的 Web 小游戏

声网

JavaScript 开源 Web 互动白板

华为全球加速GA带给你稳定的网络体验!

清欢科技

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