【AICon】探索八个行业创新案例,教你在教育、金融、医疗、法律等领域实践大模型技术! >>> 了解详情
写点什么

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

评论

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

Java基础 日期和时间

java易二三

程序员 计算机 java 编程

百度智能云连拿四年第一,为什么要深耕AI公有云市场

脑极体

AI 大模型

热烈祝贺埃文科技荣获CCF第38届中国计算机应用大会计算机应用科学技术二等奖

郑州埃文科技

Spring AOP 中,切点有多少种定义方式?

江南一点雨

Java spring

拥抱AIGC,他们有话说——百度李双龙:AIGC将赋能多个场域并惠及千行百业

百度Geek说

人工智能 百度 企业号 7 月 PK 榜 AICG

3D建模和3D渲染是吃CPU还是显卡?以及专业图形显卡和游戏显卡的区别

Finovy Cloud

3D

密集发布AI应用后,微软2023财报传递了什么信号|TE解读

TE智库

Kratos 大乱炖 —— 整合其他Web框架:Gin、FastHttp、Hertz

喵个咪

golang gin Kratos

Linux系统Apache优化与防盗链详细教程

百度搜索:蓝易云

Apache 云计算 Linux 运维 云服务器

直播程序源码开发建设:洞察全局,数据统计与分析功能-山东布谷科技创作

山东布谷科技

软件开发 直播 源码搭建 程序源码 mac数据分析统计软件

Golang微服框架Kratos与它的小伙伴系列 - ORM框架 - Ent

喵个咪

golang ORM Kratos

Linux系统Nginx优化与防盗链详细教程

百度搜索:蓝易云

nginx 云计算 Linux 运维 云服务器

快速玩转 Llama2!阿里云机器学习 PAI 推出最佳实践(二)——全参数微调训练

阿里云大数据AI技术

人工智能

Golang微服务框架kratos实现SSE服务

喵个咪

golang websocket Kratos openai

用故事给予企业全面预算管理一个灵魂

智达方通

全面预算管理 企业全面预算管理 预算场景

NineData已支持「最受欢迎数据库」PostgreSQL

NineData

postgresql 客户端 数据源 NineData 集成AI

开发语音APP源码的小知识

山东布谷网络科技

app源码

Golang微服务框架kratos实现Socket.IO服务

喵个咪

golang socket websocket Kratos

Golang微服务框架Kratos实现GraphQL服务

喵个咪

golang graphql Kratos

澜舟科技创始人兼CEO周明受邀出席“基础科学与人工智能论坛”

澜舟孟子开源社区

fastposter v2.16.0 让海报开发更简单

物有本末

图片处理 海报生成器 海报生成

Java程序员常用的日志框架有哪些?

java易二三

Java 编程 程序员 计算机

JAVA和JVM运行原理是什么?

java易二三

Java 编程 JVM 计算机 程序猿

Cassandra SSTable 合并策略(一):STCS

冰心的小屋

Cassandra STCS Compaction

Golang微服务框架kratos实现SignalR服务

喵个咪

golang SignalR Kratos

Golang微服务框架Kratos实现Thrift服务

喵个咪

Dify.AI:简单易用的 LLMOps 平台,可视化创造和运营你的 AI 原生应用

Dify

AI LLMOps

分布式事务两阶段提交和三阶段提交有什么区别?

王磊

java面试

Golang微服框架Kratos与它的小伙伴系列 - ORM框架 - GORM

喵个咪

golang ORM gorm Kratos

视觉套件专项活动!与飞桨技术专家一起提升技术实力,更多荣誉奖励等你领取

飞桨PaddlePaddle

人工智能 百度 paddle 飞桨 百度飞桨

Qualcomm WiFi7 Routerboard,IPQ9574,4X4,4XM.2,SFP,Industrial High Power|DR9574

wallyslilly

ipq9574

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