JMockit是google code上面的一个java单元测试mock项目,她很方便地让你对单元测试中的final类,静态方法,构造方法进行mock,功能强大。项目地址在:http://jmockit.googlecode.com http://jmockit.org/。详细用法可以在上面找到答案。

JMockit的测试方式可以通过下面2个途径实现

一.根据用例的测试路径,测试代码内部逻辑

对于这种情景,可以使用jmockit的基于行为的mock方式。在这种方式中,目的是测试单元测试及其依赖代码的调用过程,验证代码逻辑是否满足测试路径。  由于被依赖代码可能在自己单测中已测试过,或者难以测试,就需要把这些被依赖代码的逻辑用预定期待的行为替换掉,也就是mock掉,从而把待测是代码隔离开,这也是单元测试的初衷。 这种方式和白盒测试接近。

二.根据测试用例的输入输出数据,测试代码是否功能运行正常。

对于这种情景,可以使用jmockit基于状态的mock方式。目的是从被测代码的使用角度出发,结合数据的输入输出来检验程序运行的这个正确性。使用这个方式,需要把被依赖的代码mock掉,实际上相当于改变了被依赖的代码的逻辑。通常在集成测试中,如果有难以调用的外部接口,就通过这个方式mock掉,模拟外部接口。 这种方式有点像黑盒测试。

下面根据一个简单例子简单介绍JMockit的几个常用测试场景和使用方法。

被测试类:一个猜骰子点数的类。new Guess(int n)时候指定最大猜数次数,并且生成实际点数。在n次猜测内猜中则输出成功,n次猜测失败后通过failHandle()输出错误。结果输出通过GuessDAO来保存。但GuessDAO还没实现。

/** 在n次机会随机猜骰子点数 ,结果保存到数据库中 */public class Guess {private int maxTryTime;                         // 最大重试次数private int tryTime = 0;                        // 当前重试次数private int number = (int) (Math.random() * 6); // 目标数字private GuessDAO guessDAO;                      // 持久化依赖public Guess(int maxRetryTime) {             this.maxTryTime = maxRetryTime;}public void doit() {while (tryTime++ < maxTryTime && !tryIt()) {// 到达最大尝试次数仍不成功则调用handleif (tryTime == maxTryTime) {failHandle();}}}public boolean tryIt() {                        // 最坏情况下调用maxRetryTime次tryIt(),猜中则保存信息if (number == randomGuess()) {guessDAO.saveResult(true, number);return true;}return false;}public void failHandle() {                      // 失败处理,猜不中时调用guessDAO.saveResult(false, number);}private int randomGuess(){                      // 猜的随机过程return (int) (Math.random() * 6);}public void setGuessDAO(GuessDAO guessDAO) {this.guessDAO = guessDAO;}}

下面通过3个测试用例来说明如何使用jmockit

以下代码基于jmockit1.0 左右版本,新版去废除了一些功能(如@Mocked不能修饰成员)

1. 测试当没有一次猜中时,代码逻辑如何执行。

先上测试代码:

public class GuessTest {@Tested        // 表明被修饰实例是将会被自动构建和注入的实例Guess guess = new Guess(3);@Injectable    // 表明被修饰实例将会自动注入到@Tested修饰的实例中,并且会自动mock掉,除非在测试前被赋值GuessDAO guessDAO;/*** 连续3次失败*/@Testpublic void behaviorTest_fail3time() {new Expectations() {        // Expectations中包含的内部类区块中,体现的是一个录制被测类的逻辑。@Mocked(methods="tryIt")  // 表明被修饰的类对tryIt()方法进行mock。Guess g;{g.tryIt();             // 期待调用Guess.tryIt()方法result = false;        // mock掉返回值为false(表明猜不中)times = 3;             // 期待以上过程重复3次guessDAO.saveResult(false, anyInt); // 期待调用guessDAO把猜失败的结果保存}};guess.doit();               // 录制完成后,进行实际的代码调用,也称回放(replay)}
}

说明下这个测试代码的目的: 测试行为是guess.doit(),代码期望在调用doit()函数后,会发生:
        1.调用tryIt,并把结果mock为false;
        2.重复第一步3次;
        3.把结果通过guessDAO保存。即调用3次均猜错数字

可以看出,JMockit在基于行为的测试中,体现3个步骤。第一个是脚本录制,也就是把期望的行为记录下来。在上面例子中,在Expectation内部类的区块中的代码就是期待发生的行为。第二是回放,也就是guess.doit()触发的过程。第三是检验,在这里没有确切体现出,但是的确发生着检验:假设doit方法调用后,代码的逻辑没有符合录制过程中的脚本的行为,那么测试结果失败(其实Jmockit有专门的Verifications做检验,但是这里Expecation已经包含了这个功能,如果用NonStrictExpecation就需要有检验块)。

再介绍下这段代码中用到的各个JMockit元素(结论源自文档及自己代码测试):

@Tested和@Injectable: 对@Tested对象判断是否为null,是则通过合适构造器初始化,并实现依赖注入。调用构造方法时,会尝试使用@Injectable的字段进行构造器注入。普通注入时,@Injectable字段如果没有在测试方法前被赋值,其行为将会被mock成默认值(静态方法和构造函数不会被mock掉)。Injectable最大作用除了注入,还有就是mock的范围只限当前注释实例。一句话:@Injectable的实例会自动注入到@Tested中,如果没初始赋值,那么JMockit将会以相应规则初始化。

@Mocked:@Mocked修饰的实例,将会把实例对应类的所有实例的所有行为都mock掉(无论构造方法,还是private,protected方法,够霸气吧)。在Expectation区块中,声明的成员变量均默认带有@Mocked,但是本例没有省略,是因为@Mocked会mock掉所有方法,而回放的代码中doit函数我们是不希望它也被mock,所以通过method="tryIt"来设置被mock的类只对tryIt方法进行mock。

Expectations:这是录制期望发生行为的地方。result和times都是其内定成员变量。result可以重定义录制行为的返回值甚至通过Delegate来重定义行为,times是期待录制行为的发生次数。在Expectations中发生的调用,均会被mock。由于没定义result,所以guessDAO.saveResult()调用的结果返回空。

2. 当多次失败后,最后一次猜数成功时,代码逻辑如何执行。

在上面的测试代码中,加多一个测试方法:

   /*** 两次失败,第三次猜数成功*/@Testpublic void behaviorTest_sucecess() {new Expectations(Guess.class) {                          // 构造函数可以传入Class或Instance实例{   guess.tryIt();result = false;times=2;invoke(guess, "randomGuess", new Object[]{});    // invoke()能调用私有方法result = (Integer)getField(guess, "number");     // getField()能操作私有成员guessDAO.saveResult(true, (Integer)getField(guess, "number"));}};guess.doit();}

第二个测试用例是期待先猜2次失败,第3次猜中。

所以录制中会先调用2次tryIt并返回false,在发生第3次调用时,通过invoke调用私有方法randomGuess,并期待其返回被测实例的私有成员number,通过这种作弊的方式,自然肯定能在第三次猜中数字。最后期待guessDAO把结果保存。

这段代码和之前的区别是,在Expectation中没定义成员变量,而把Guess.class显式地通过构造函数传入。这么做也是为了只对tryIt方法mock,因为在Expectation构造函数传入Class对象或Instance对象后,只会区块内Class或Instance对应的行为进行mock。

通过以上2个基于行为mock的例子,应该对JMockit如何测试代码内部逻辑有了解了吧。下面再对基于状态的mock介绍:

3. 模拟正常猜骰子,观察输出猜中的概率

再加入第三各测试方法:

    /*** 模拟正常执行,计算抽中概率,把DAO mock掉*/@Testpublic void stateTest_mockDAO() {final Map<Integer, Integer> statMap = new HashMap<Integer, Integer>(); // statMap.get(0)为猜中次数,statMap.get(1)为失败次数statMap.put(0, 0);statMap.put(1, 0);guessDAO = new MockUp<GuessDAO>() {            // MockUp用来定义新的代码逻辑@SuppressWarnings("unused")@Mockpublic boolean saveResult(boolean isSuccess, int n) {if (isSuccess) {statMap.put(0, statMap.get(0)+1);System.out.println("you guess it! dice:" + n);} else {statMap.put(1, statMap.get(1)+1);System.out.println("you didn't guess it. dice:" + n);}return true;}}.getMockInstance();  for (int i=0; i<1000; i++) {Guess guess = new Guess(3);guess.setGuessDAO(guessDAO);guess.doit();}System.out.println("hit" + statMap.get(0));System.out.println("not hit" + statMap.get(1));double rate =((double) statMap.get(0)) / (statMap.get(0)+statMap.get(1));System.out.println("hit rate=" + rate);}

第三个用例目的是,测试在指定尝试次数下猜中数字的概率。这就不再盯着代码内部逻辑,而从整体功能角度进行测试,把内部无法调用的的依赖接口mock掉。

在基于状态的mock中,看不到了Expectations,@Mocked等字样了。取而代之的是MockUp,@Mock。

代码中对GuessDAO的保存方法进行了重定义。让其直接从控制带输出消息。

通过这种方式,不仅可以进行功能黑盒测试,还可以尽快地让测试代码跑起来。

MockUp中的泛型类型是被重定义的类,重定义的方法需要和原类中的方法签名一致。但是,static方法可以省区static关键字。如:

        new MockUp<Calendar>() {@Mockpublic Calendar getInstance() {return calendar1;}};

至此,通过三个例子,把JMockit的2个测试方式简单介绍了。但是JMockit的功能不仅如此,详细能请查看官方文档和实例。

=============

过程中遇到还未解决的疑问:

1. 基于行为的mock,需要对回放的类的具体类型类mock,没法针对父类类型mock?

Guess g = new Guess(3);
new Expectations() {@Mocked(methods="tryIt")GuessParent mg;      // 对Guess父类进行mock{mg.tryIt();result=true;}};
g.doit();

假如声明的mg类型是Guess的父类,则回放中调用Guess.doit()将不能捕捉道mg.tryIt();导致测试失败。
除非在expectation构造函数传入实例g才可以。

2. 基于行为的动态mock, 文档说: If the Class object for a given class is passed, the methods and constructors defined in that class are considered for mocking,but not the methods and constructors of its super-classes. If an instance of a given class is passed, then all methods defined in the whole class hierarchy, from the concrete class of the given instance up to (but not including) Object, are considered for mocking; the constructors of these classes, however, are not (since an instance was already created). 粗体的不是很理解,是说mock的父类的方法和构造函数不被mock?但测试结果却不是这样

其他Mock框架与jmockit对比

单元测试mock框架——jmockit实战相关推荐

  1. 【C#】【xUnit】【Moq】.NET单元测试Mock框架Moq初探!

    在TDD开发模型中,经常是在编码的同时进行单元测试的编写,由于现代软件开发不可能是一个人完成的工作,所以在定义好接口的时候我们就可以进行自己功能的开发(接口不能经常变更),而我们调用他人的功能时只需要 ...

  2. 单元测试Mock框架--Mockito

    文章目录 目前开发中,单元测试遇到的问题 解决方案--Mock Junit4 + Mockito: Mockito常用注解: Mockito常用方法: Tips: 总结 目前开发中,单元测试遇到的问题 ...

  3. java unit test moke_java unit test Mock框架jMockit示例教程 - 另一种基于状态的Mock,随穿随脱?...

    2014-07-09 06:30:01 阅读( 395 ) 除了使用@MockUp的另外一种基于状态的mock测试的方式. package cn.outofmemory.jmockit.target; ...

  4. jmockit教程_java unit test Mock框架jMockit示例教程 - Mock 构造方法,基于状态的Mock

    2015-03-09 06:30:01 阅读( 318 ) package cn.outofmemory.jmockit.target; public class Constructor { priv ...

  5. Mock框架的三次迭代,让你的单元测试更高效

    如何定义单元 对于单元测试中的单元,不同的人有不同的看法:可以理解为一个方法,可以理解为一个完整的接口实现,也可以理解为一个完整的功能模块或者是多个功能模块的一个耦合. 根据以往的单元测试经验,在设计 ...

  6. Mock和Java单元测试中的Mock框架Mockito介绍

    什么是Mock? 在面向对象程序设计中,模拟对象(英语:mock object,也译作模仿对象)是以可控的方式模拟真实对象行为的假的对象.程序员通常创造模拟对象(mock object)来测试其他对象 ...

  7. 史上最轻量​!阿里新型单元测试Mock工具开源了

    简介:为了探索更轻量易用的Mock测试手段,阿里云云效团队尝试给工具减负,在主流Mock工具的基础上让Mock的定义和置换干净利落,最终设计了一款极简风格的测试辅助工具TestableMock,无需初 ...

  8. Java单元测试之Mock框架

    一.引言 二.为什么要用Mock 三.Mock使用场景 四.Mock定义 五.Mock框架 五.Mockito 5.1 Mockito基本使用 5.2 MockMVC测试 5.2.1 初始化MockM ...

  9. Mock工具Jmockit使用介绍

    Mock工具Jmockit使用介绍 在写单元测试的过程中我们会发现需要测试的类有很多依赖,这些依赖的类或者资源又会有依赖,导致在单元测试代码里无法完成构建,我们应对的方法是Mock.简单的说就是模拟这 ...

最新文章

  1. 扫个地用得到5TOPS算力?自动驾驶芯片公司这样说
  2. 让 Python 代码更易维护的七种武器——代码风格(pylint、Flake8、Isort、Autopep8、Yapf、Black)测试覆盖率(Coverage)CI(JK)...
  3. HTTP中post方法提交不同格式的数据
  4. UESTC 1851 Kings on a Chessboard
  5. MySQL事务及锁机制大揭秘 - 公开课笔记
  6. Win32ASM学习[11]:逻辑运算
  7. 开源代码分析技巧之——打印调用逻辑
  8. 资深程序员的笔记:工作多年对于编程语言的理解,新手建议了解!
  9. centos下设置自启动和配置环境变量的方法
  10. 以整体思维看问题:解决单页应用,系统角色请求覆盖身份唯一标识(本项目中是session_id命名的)发送请求问题
  11. 苹果电脑驱动下载查询
  12. 创新科技 新BMW 7系 有你夫复何求
  13. Delphi著名皮肤控件库大全
  14. 兔子繁殖问题(递归解决)
  15. 智库大会 | 高端对话:智能科技推动管理学变革...
  16. Auto.js实现自动删除朋友圈照片
  17. mac下 Github添加SSH keys
  18. 第12课:如何理解技术管理者(上)
  19. 驰骋BPM系统-表单引擎-流程引擎 2020年大换装
  20. package编译不过如何处理

热门文章

  1. zabbix4.4.3使用postgresql数据库
  2. C语言:添加和显示,数据保存在文件中,下次打开可以获取之前录入的内容
  3. 用python做数值计算_用python进行数据分析的基本步骤和方法
  4. 数组指针(指向数组的指针)
  5. android4.1不支持微信,华为EMUI 4.1(android6.0)手机能否安装微信小程序?
  6. IEEE754 浮点数:简读+案例=秒懂
  7. 《巴黎协定》生效 越南和印尼有望至2020年各新增5GW光伏容量
  8. 深度优先搜索是什么?
  9. 目前市场上流行的嵌入式操作系统
  10. 万事无忧之SEO GOOGLE优化秘诀