JAVA单元测试——JMockit教程

摘要

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

首先,不会测试的不是好开发,不会Mock的不是好测试。因而,不会Mock的不是好开发。当然,Java Mock工具很多,比如easyMock,Mockito等等。为什么要选择JMockit呢?其实也没有啥特别原因,Mock工具的原理都差不多,就看Mock工具封装的API是否易用了。JMockit的API易用,丰富! 写出来的Mock程序代码完全面向对象。

一、JMockit的示例

java 复制代码
  <dependency>
    <groupId>org.jmockit</groupId>
    <artifactId>jmockit</artifactId>
    <version>1.36</version>
    <scope>test</scope>
  </dependency>
java 复制代码
//一个简单的类,能用不同语言打招呼
public class HelloJMockit {
    // 向JMockit打招呼
    public String sayHello() {
        Locale locale = Locale.getDefault();
        if (locale.equals(Locale.CHINA)) {
            // 在中国,就说中文
            return "你好,JMockit!";
        } else {
            // 在其它国家,就说英文
            return "Hello,JMockit!";
        }
    }
}
//HelloJMockit类的测试类
public class HelloJMockitTest { 
 
    /**
     * 测试场景:当前是在中国
     */
    @Test
    public void testSayHelloAtChina() {
        // 假设当前位置是在中国
        new Expectations(Locale.class) {
            {
                Locale.getDefault();
                result = Locale.CHINA;
            }
        };
        // 断言说中文
        Assert.assertTrue("你好,JMockit!".equals((new HelloJMockit()).sayHello()));
    }
 
    /**
     * 测试场景:当前是在美国
     */
    @Test
    public void testSayHelloAtUS() {
        // 假设当前位置是在美国
        new Expectations(Locale.class) {
            {
                Locale.getDefault();
                result = Locale.US;
            }
        };
        // 断言说英文
        Assert.assertTrue("Hello,JMockit!".equals((new HelloJMockit()).sayHello()));
    }
}

在上面的例子中,为了对依赖(当前的位置)进行Mock,用简单的3行代码即可搞定。把测试代码的依赖抽象成期待(Expectations),并把期待类Expectations作为本测试程序的内部类,可以任意访问本测试程序类的所有成员,为编写Mock程序提供极大便利。API面向对象特性封装良好。

此外,JMockit还提供了注解,支持泛型的Mock API用于对类/对象的属性,方法(支持static,private,final,native),构造函数,初始代码块(含静态初始代码块)灵活Mock。可以说,JMockit是一款功能强大,API易用,不可或缺的Java Mock工具。

二、JMockit的程序结构

java 复制代码
//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 /* 这是一个测试参数 */) {
        // 录制(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();
                // 验证helloJMockit.sayHello()这个方法调用了1次
                times = 1;
            }
        };
    }
}

通过上述例子可以看出,JMockit的程序结构包含了测试属性或测试参数,测试方法,测试方法体中又包含录制代码块,重放测试逻辑,验证代码块。

测试属性:

即测试类的一个属性。它作用于测试类的所有测试方法。在JMockit中,我们可以用JMockit的注解API来修饰它。这些API有**@Mocked, @Tested, @Injectable,@Capturing**。在上述例子中,我们用@Mocked修饰了测试属性HelloJMockit helloJMockit,表示helloJMockit这个测试属性,它的实例化,属性赋值,方法调用的返回值全部由JMockit来接管,接管后,helloJMockit的行为与HelloJMockit类定义的不一样了,而是由录制脚本来定义了。那@Mocked修饰后的helloJMockit,JMockit对它到底做了什么, 会在后续的章节中详细讲述。

测试参数:

即测试方法的参数。它仅作用于当前测试方法。给测试方法加参数,原本在JUnit中是不允许的,但是如果参数加了JMockit的注解API(@Mocked, @Tested, @Injectable,@Capturing),则是允许的。测试参数与测试属性的不同,主要是作用域的不同。

Record-Replay-Verification

Record-Replay-Verification 是JMockit测试程序的主要结构。

  1. Record: 即先录制某类/对象的某个方法调用,在当输入什么时,返回什么。
  2. Replay: 即重放测试逻辑。
  3. Verification: 重放后的验证。比如验证某个方法有没有被调用,调用多少次。

其实,Record-Replay-Verification与JUnit程序的AAA(Arrange-Action-Assert)结构是一样的。

Record对应Arrange,先准备一些测试数据,测试依赖。Replay对应Action,即执行测试逻辑。Verification对应Assert,即做测试验证。

2.1 @Mocked原理

当@Mocked修饰一个类时

java 复制代码
 //@Mocked注解用途
public class MockedClassTest {
    // 加上了JMockit的API @Mocked, JMockit会帮我们实例化这个对象,不用担心它为null
    @Mocked
    Locale locale;
 
    // 当@Mocked作用于class
    @Test
    public void testMockedClass() {
        // 静态方法不起作用了,返回了null
        Assert.assertTrue(Locale.getDefault() == null);
        // 非静态方法(返回类型为String)也不起作用了,返回了null
        Assert.assertTrue(locale.getCountry() == null);
        // 自已new一个,也同样如此,方法都被mock了
        Locale chinaLocale = new Locale("zh", "CN");
        Assert.assertTrue(chinaLocale.getCountry() == null);
    }
  
}

当@Mocked修饰一个接口/抽象类时

java 复制代码
//@Mocked注解用途
public class MockedInterfaceTest {
 
    // 加上了JMockit的API @Mocked, JMockit会帮我们实例化这个对象,尽管这个对象的类型是一个接口,不用担心它为null
    @Mocked
    HttpSession session;
 
    // 当@Mocked作用于interface
    @Test
    public void testMockedInterface() {
        // (返回类型为String)也不起作用了,返回了null
        Assert.assertTrue(session.getId() == null);
        // (返回类型为原始类型)也不起作用了,返回了0
        Assert.assertTrue(session.getCreationTime() == 0L);
        // (返回类型为原非始类型,非String,返回的对象不为空,这个对象也是JMockit帮你实例化的,同样这个实例化的对象也是一个Mocked对象)
        Assert.assertTrue(session.getServletContext() != null);
        // Mocked对象返回的Mocked对象,(返回类型为String)的方法也不起作用了,返回了null
        Assert.assertTrue(session.getServletContext().getContextPath() == null);
    }
}

通过上述例子,可以看出:@Mocked修饰的类/接口,是告诉JMockit,帮我生成一个Mocked对象,这个对象方法(包含静态方法)返回默认值。即如果返回类型为原始类型(short,int,float,double,long)就返回0,如果返回类型为String就返回null,如果返回类型是其它引用类型,则返回这个引用类型的Mocked对象(这一点,是个递归的定义,需要好好理解一下)。

什么测试场景,我们要使用@Mocked:当我们的测试程序依赖某个接口时,用@Mocked非常适合了。只需要@Mocked一个注解,JMockit就能帮我们生成这个接口的实例。 比如在分布式系统中,我们的测试程序依赖某个接口的实例是在远程服务器端时,我们在本地构建是非常困难的,此时就交给@Mocked,就太轻松啦!

博文参考

JMockit技术的分享文章

相关推荐
IT学长编程4 分钟前
计算机毕业设计 玩具租赁系统的设计与实现 Java实战项目 附源码+文档+视频讲解
java·spring boot·毕业设计·课程设计·毕业论文·计算机毕业设计选题·玩具租赁系统
莹雨潇潇6 分钟前
Docker 快速入门(Ubuntu版)
java·前端·docker·容器
杨哥带你写代码25 分钟前
足球青训俱乐部管理:Spring Boot技术驱动
java·spring boot·后端
XKSYA(小巢校长)43 分钟前
NatGo我的世界联机篇
开发语言·php
Cons.W1 小时前
Codeforces Round 975 (Div. 1) C. Tree Pruning
c语言·开发语言·剪枝
憧憬成为原神糕手1 小时前
c++_ 多态
开发语言·c++
VBA63371 小时前
VBA信息获取与处理第三个专题第三节:工作薄在空闲后自动关闭
开发语言
郭二哈1 小时前
C++——模板进阶、继承
java·服务器·c++
A尘埃1 小时前
SpringBoot的数据访问
java·spring boot·后端
yang-23071 小时前
端口冲突的解决方案以及SpringBoot自动检测可用端口demo
java·spring boot·后端