美文网首页
JMockit学习

JMockit学习

作者: 追云的帆 | 来源:发表于2019-08-27 23:31 被阅读0次

2019年8月27日 随笔

JMockit学习

概述

JMockit是一款Java类/接口/对象的Mock工具,目前广泛应用于Java应用程序的单元测试中。


配置

Maven porm.xml

<!-- 先声明jmockit的依赖 -->
   <dependency>
    <groupId>org.jmockit</groupId>
    <artifactId>jmockit</artifactId>
    <version>1.36</version>
    <scope>test</scope>
  </dependency>
<!-- 再声明junit的依赖 -->
   <dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.9</version>
    <scope>test</scope>
  </dependency>

JUnit4.x及以下注意,如果你是通过mvn test来运行你的测试程序 , 请确保JMockit的依赖定义出现在JUnit的依赖之前。

JMockit Coverage配置

JMockit的代码覆盖率功能

 <plugin>
     <artifactId>maven-surefire-plugin</artifactId>
    <configuration>
    <argLine>-javaagent:"${settings.localRepository}/org/jmockit/jmockit/1.36/jmockit-1.36.jar=coverage"</argLine>
    <disableXmlReport>false</disableXmlReport>
    <systemPropertyVariables>
    <coverage-output>html</coverage-output>
    <coverage-outputDir>${project.build.directory}/codecoverage-output</coverage-outputDir>
    <coverage-metrics>all</coverage-metrics>
    </systemPropertyVariables>
    </configuration>
 </plugin>

JMockit的程序结构

//JMockit的程序结构
public class ProgramConstructureTest {
 
    // 这是一个测试属性
    @Mocked
    HelloJMockit helloJMockit;
 
    @Test
    public void test1() {
        // 录制(Record)
        new Expectations() {
            {
                helloJMockit.sayHello();
                // 期待上述调用的返回是"hello,david",而不是返回"hello,JMockit"
                result = "hello,david";
            }
        };
        // 重放(Replay)
        String msg = helloJMockit.sayHello();
        Assert.assertTrue(msg.equals("hello,david"));
        // 验证(Verification)
        new Verifications() {
            {
                helloJMockit.sayHello();
 
                times = 1;
            }
        };
    }
 
    @Test
    public void test2(@Mocked HelloJMockit helloJMockit /* 这是一个测试参数 */) {
        ...
    }
}

测试属性&测试参数

测试属性

测试类的一个属性。它用作与测试类的所有方法。

在JMockit中,用@Mocked修饰了测试属性HelloJMockit helloJMockit,表示helloJMockit这个测试属性,它的实例化,属性赋值,方法调用的返回值全部由JMockit来接管,接管后,helloJMockit的行为与HelloJMockit类定义的不一样了,而是由录制脚本来定义了。

测试参数

测试方法的参数,它仅仅用作与当前测试方法。测试参数和测试属性的不同是在作用域上。

Record-Replay-Verification

  • Record-Replay-Verification:
    是JMockit测试程序的主要结构。
  • Record:
    即先录制某类/对象的某个方法调用,在当输入什么时,返回什么。
  • Replay:
    即重放测试逻辑。
  • Verification:
    重放后的验证。比如验证某个方法有没有被调用,调用多少次。

@Mocked

功能

@Mocked修饰的类/接口,是告诉JMockit,帮我生成一个Mocked对象,这个对象方法(包含静态方法)返回默认值。
即如果返回类型为原始类型(short,int,float,double,long)就返回0,
如果返回类型为String就返回null;
如果返回类型是其它引用类型,则返回这个引用类型的Mocked对象;

什么时候使用@Mocked

当我们的测试程序依赖某个接口时,只需要@Mocked一个注解,JMockit就能帮我们生成这个接口的实例。

比如在分布式系统中,我们的测试程序依赖某个接口的实例是在远程服务器端时,我们在本地构建是非常困难的,此时就交给
@Mocked。


@Injectable 和 @Mocked

区别

@Injectable 也是告诉 JMockit生成一个Mocked对象,但@Injectable只是针对其修饰的实例,而@Mocked是针对其修饰类的所有实例。
此外,@Injectable对类的静态方法,构造函数没有影响。因为它只影响某一个实例。

使用总结

@Injectable 也表示一个Mocked对象,相比@Mocked,只不过只影响类的一个实例;
@Mocked默认是影响类的所有实例;
@Tested表示被测试对象。如果该对象没有赋值,JMockit会去实例化它;

若@Tested的构造函数有参数,则JMockit通过在测试属性&测试参数中查找@Injectable修饰的Mocked对象注入@Tested对象的构造函数来实例化,不然,则用无参构造函数来实例化。除了构造函数的注入,JMockit还会通过属性查找的方式,把@Injectable对象注入到@Tested对象中。

注入的匹配规则:先类型,再名称(构造函数参数名,类的属性名)。若找到多个可以注入的@Injectable,则选择最优先定义的@Injectable对象。

使用场景

当我们需要手工管理被测试类的依赖时,就需要用到@Tested & @Injectable。两者搭配起来用,JMockit就能帮我们轻松搞定被测试类及其依赖注入细节。


@Capturing

@Capturing主要用于子类/实现类的Mock

使用场景

当我们只知道父类或接口时,但我们需要控制它所有子类的行为时,子类可能有多个实现(可能有人工写的,也可能是AOP代理自动生成时)。就用@Capturing。


Expectations

Expectations的作用主要是用于录制。即录制类/对象的调用。

录制脚本规范

new Expectations() {
    // 这是一个Expectations匿名内部类
    {
          // 这是这个内部类的初始化代码块,我们在这里写录制脚本,脚本的格式要遵循下面的约定
        //方法调用(可是类的静态方法调用,也可以是对象的非静态方法调用)
        //result赋值要紧跟在方法调用后面
        //...其它准备录制脚本的代码
        //方法调用
        //result赋值
    }
};
 
还可以再写new一个Expectations,只要出现在重放阶段之前均有效。
new Expectations() {
      
    {
         //...录制脚本
    }
};

录制的两种方式

1.通过引用外部类的Mock对象(@Injectabe,@Mocked,@Capturing)来录制
//Expectations对外部类的mock对象进行录制
public class ExpectationsTest {
    @Mocked
    Calendar cal;
 
    @Test
    public void testRecordOutside() {
        new Expectations() {
            {
                // 对cal.get方法进行录制,并匹配参数 Calendar.YEAR
                cal.get(Calendar.YEAR);
                result = 2016;// 年份不再返回当前小时。而是返回2016年
                // 对cal.get方法进行录制,并匹配参数 Calendar.HOUR_OF_DAY
                cal.get(Calendar.HOUR_OF_DAY);
                result = 7;// 小时不再返回当前小时。而是返回早上7点钟
            }
        };
        Assert.assertTrue(cal.get(Calendar.YEAR) == 2016);
        Assert.assertTrue(cal.get(Calendar.HOUR_OF_DAY) == 7);
        // 因为没有录制过,所以这里月份返回默认值 0
        Assert.assertTrue(cal.get(Calendar.DAY_OF_MONTH) == 0);
    }
 
}

在Expectations匿名内部类的初始代码块中,我们可以对外部类的任意成员变量,方法进行调用。大大便利我们书写录制脚本。

2.通过构建函数注入类/对象来录制

有时候,我们只希望JMockit只mock类/对象的某一个方法。

//通过Expectations对其构造函数mock对象进行录制
public class ExpectationsConstructorTest2 {
 
    // 把类传入Expectations的构造函数
    @Test
    public void testRecordConstrutctor1() {
        Calendar cal = Calendar.getInstance();
        // 把待Mock的类传入Expectations的构造函数,可以达到只mock类的部分行为的目的
        new Expectations(Calendar.class) {
            {
                // 只对get方法并且参数为Calendar.HOUR_OF_DAY进行录制
                cal.get(Calendar.HOUR_OF_DAY);
                result = 7;// 小时永远返回早上7点钟
            }
        };
        Calendar now = Calendar.getInstance();
        // 因为下面的调用mock过了,小时永远返回7点钟了
        Assert.assertTrue(now.get(Calendar.HOUR_OF_DAY) == 7);
        // 因为下面的调用没有mock过,所以方法的行为不受mock影响,
        Assert.assertTrue(now.get(Calendar.DAY_OF_MONTH) == (new Date()).getDate());
    }
 
    // 把对象传入Expectations的构造函数
    @Test
    public void testRecordConstrutctor2() {
        Calendar cal = Calendar.getInstance();
        // 把待Mock的对象传入Expectations的构造函数,可以达到只mock类的部分行为的目的,但只对这个对象影响
        new Expectations(cal) {
            {
                // 只对get方法并且参数为Calendar.HOUR_OF_DAY进行录制
                cal.get(Calendar.HOUR_OF_DAY);
                result = 7;// 小时永远返回早上7点钟
            }
        };
 
        // 因为下面的调用mock过了,小时永远返回7点钟了
        Assert.assertTrue(cal.get(Calendar.HOUR_OF_DAY) == 7);
        // 因为下面的调用没有mock过,所以方法的行为不受mock影响,
        Assert.assertTrue(cal.get(Calendar.DAY_OF_MONTH) == (new Date()).getDate());
 
        // now是另一个对象,上面录制只对cal对象的影响,所以now的方法行为没有任何变化
        Calendar now = Calendar.getInstance();
        // 不受mock影响
        Assert.assertTrue(now.get(Calendar.HOUR_OF_DAY) == (new Date()).getHours());
        // 不受mock影响
        Assert.assertTrue(now.get(Calendar.DAY_OF_MONTH) == (new Date()).getDate());
    }
}

MockUp & @Mock

它的Mock方式最直接。
MockUp & @Mock比较适合于一个项目中,用于对一些通用类的Mock,以减少大量重复的new Exceptations{{}}代码。

//Mockup & @Mock的Mock方式
public class MockUpTest {
 
    @Test
    public void testMockUp() {
        // 对Java自带类Calendar的get方法进行定制
        // 只需要把Calendar类传入MockUp类的构造函数即可
        new MockUp<Calendar>(Calendar.class) {
            // 想Mock哪个方法,就给哪个方法加上@Mock, 没有@Mock的方法,不受影响
            @Mock
            public int get(int unit) {
                if (unit == Calendar.YEAR) {
                    return 2017;
                }
                if (unit == Calendar.MONDAY) {
                    return 12;
                }
                if (unit == Calendar.DAY_OF_MONTH) {
                    return 25;
                }
                if (unit == Calendar.HOUR_OF_DAY) {
                    return 7;
                }
                return 0;
            }
        };
        // 从此Calendar的get方法,就沿用你定制过的逻辑,而不是它原先的逻辑。
        Calendar cal = Calendar.getInstance(Locale.FRANCE);
        Assert.assertTrue(cal.get(Calendar.YEAR) == 2017);
        Assert.assertTrue(cal.get(Calendar.MONDAY) == 12);
        Assert.assertTrue(cal.get(Calendar.DAY_OF_MONTH) == 25);
        Assert.assertTrue(cal.get(Calendar.HOUR_OF_DAY) == 7);
        // Calendar的其它方法,不受影响
        Assert.assertTrue((cal.getFirstDayOfWeek() == Calendar.MONDAY));
 
    }
}

弊端

  1. 一个类有多个实例。只对其中某1个实例进行mock。
    最新版的JMockit已经让MockUp不再支持对实例的Mock了。1.19之前的老版本仍支持。
  2. AOP动态生成类的Mock。
  3. 对类的所有方法都需要Mock时,书写MockUp的代码量太大。如web程序中,经常需要对HttpSession进行Mock。若用MockUp你要写大量的代码,可是用@Mocked就一行代码就可以搞定。

Verifications

Verifications是用于做验证。验证Mock对象(即@Moked/@Injectable@Capturing修饰的或传入Expectation构造函数的对象)有没有调用过某方法,调用了多少次。与Exceptations的写法相似。


new Verifications() {
    // 这是一个Verifications匿名内部类
    {
          // 这个是内部类的初始化代码块,我们在这里写验证脚本,脚本的格式要遵循下面的约定
        //方法调用(可是类的静态方法调用,也可以是对象的非静态方法调用)
        //times/minTimes/maxTimes 表示调用次数的限定要求。赋值要紧跟在方法调用后面,也可以不写(表示只要调用过就行,不限次数)
        //...其它准备验证脚本的代码
        //方法调用
        //times/minTimes/maxTimes 赋值
    }
};
  
//还可以再写new一个Verifications,只要出现在重放阶段之后均有效。
new Verifications() {
       
    {
         //...验证脚本
    }
};

通常,在实际测试程序中,我们更倾向于通过JUnit/TestNG/SpringTest的Assert类对测试结果的验证, 对类的某个方法有没调用,调用多少次的测试场景并不是太多。因此在验证阶段,我们完全可以用JUnit/TestNG/SpringTest的Assert类取代new Verifications() {{}}验证代码块。

参考资料:http://www.jmockit.cn/index.htm

相关文章

  • JMockit学习

    2019年8月27日 随笔 JMockit学习 概述 JMockit是一款Java类/接口/对象的Mock工具,目...

  • java中的mock工具-jmockit

    jmockit 标签(空格分隔): test mock 之前试过powermock,虽然可以mock 静态方法,但...

  • Jmockit(一): 入门

    1 pom配置 testng + jmockit,使用junit也可以 2 程序结构 三步走: 录制,mock方法...

  • JMockit初体验

    一.Mock的使用场景: 比如以下场景: 1. mock掉外部依赖的应用的HSF service的调用,比如uic...

  • JMockit+Junit 基于行为 mock学习

    最近有一个需求涉及到的外部系统别较多,只是一个小小的方法有5-6个rpc接口,还有4-5个查询数据库的连接,再加上...

  • 2019-01-19

    JMockit同一个类中Mock方法和跨类mock方法 输出

  • Jmockit(二): 场景应用

    1 概述 使用jmockit的目的,在于剥离代码间的依赖 比如A方法引用B方法,B方法读取数据库返回一条记录,此时...

  • jmockit使用环境部署

    使用jmockit是由于工作中项目需要,要将这套测试环境搭建起来,结果花了一天时间,搜了N个教程,都以失败而告终。...

  • JMockit/Mockito/PowerMockit/Robo

    目录 概念学习 代理模式 mockito原理 PowerMockito原理 Robolectric 原理 JMoc...

  • 2.1 创建并使用mock对象

    JMockit可以mock任意class、interface。可以将mock对象声明为域或者方法的参数。默认情况下...

网友评论

      本文标题:JMockit学习

      本文链接:https://www.haomeiwen.com/subject/cynyectx.html