JMockit单元测试用法

基础概念

  • what
    JMockit是一款Java类/接口/对象的Mock工具,目前广泛应用于Java应用程序的单元测试中
  • why
    这差不多是目前最强大、最方便和最简单的测试框架了
  • Record-Replay-Verification单元测试结构

Record: 即先录制某类/对象的某个方法调用,在当输入什么时,返回什么
Replay: 即重放测试逻辑
Verification: 重放后的验证

  • API
    @Mocked、@Tested、@Injectable、@Capturing、@Mock、Expectations、MockUp、Verifications

API用法

@Mocked

加上了JMockit的API @Mocked, JMockit会实例化这个对象
Mocked可以用来修饰类、接口和抽象类,返回默认值(如果是原始类型,返回原始值的默认值,如果为其他对象,则返回一个同样被Mocked的对象)
Mocked十分粗暴,会mock掉整个的实现

@Injectable

@Injectable功能跟@Mocked相似,区别是@Injectable只针对修饰的实例,对mock类的静态方法、构造函数没有影响

@Tested

@Tested表示被测试对象。如果该对象没有赋值,JMockit会去实例化它,若@Tested的构造函数有参数,则JMockit通过在测试属性&测试参数中查找@Injectable修饰的Mocked对象注入@Tested对象的构造函数来实例化,
不然,则用无参构造函数来实例化。除了构造函数的注入,JMockit还会通过属性查找的方式,把@Injectable对象注入到@Tested对象中。
注入的匹配规则:先类型,再名称(构造函数参数名,类的属性名)。若找到多个可以注入的@Injectable,则选择最优先定义的@Injectable对象

@Capturing

@Capturing主要用于子类/实现类的Mock,比如java动态代理生成的类,这些类没有名字,无法进行正常的mock
当只知道父类或接口时,但需要控制其子类的行为时,子类可能有多个实现(可能有人工写的,也可能是AOP代理自动生成时),需要使用@Capturing

Expectations

Record对象的核心方法,最核心和重要的注解了,可以和引用外部类的Mock对象(@Injectabe,@Mocked,@Capturing)来录制,也可以通过构建函数注入类/对象来录制
限制:不能mock对象的native方法和private方法

MockUp & Mock

MockUp & @Mock提供更改代码行为的Mock方式,最强大mock实现,主要有以下限制:

  • 无法mock单个实例
  • 无法mock动态代理对象
  • 对类的所有方法都进行Mock,书写MockUp的代码量太大
    可以用于项目中比较通用模块,减少大量重复的Exceptations

Verifications

Verifications是用于做验证,过程式编程的福音
验证Mock对象(即@Moked/@Injectable@Capturing修饰的或传入Expectation构造函数的对象)有没有调用过某方法,调用了多少次
主要用于一些没有返回值的代码验证

实例

//一个普通类 
public class AnOrdinaryClass {
    // 静态方法
    public static int staticMethod() {
        return 1;
    }
    // 普通方法
    public int ordinaryMethod() {
        return 2;
    }
    // final方法
    public final int finalMethod() {
        return 3;
    }
    // native方法,返回4
    public native int navtiveMethod();
    // private方法
    private int privateMethod() {
        return 5;
    }
    // 调用private方法
    public int callPrivateMethod() {
        return privateMethod();
    }
}
public class Sample {
    private SampleDepend sampleDepend;
    private SampleInterface sampleInterface = new SampleInterface() {
        @Override
        public int call() {
            return 1;
        }
    };

    public Sample() {
    }

    public int getSampleDependVal() {
        return sampleDepend.getVal();
    }

    private static int staticGetOne() { return 1; }
    public static int staticGetPrivateOne() { return staticGetOne(); }

    private int getOne() { return 1; }
    public int getPrivateOne() { return getOne(); }

    public void execute() {
        sampleDepend.getVal();
        doExecute(); }

    private void doExecute() {}

    public int interfaceCall() {
        return sampleInterface.call();
    }

    public interface SampleInterface {
        int call();
    }
}
public class SampleDepend {
    private int val;
    public int getVal() { return val; }
    public void setVal(int val) { this.val = val; }
}

@RunWith(JMockit.class)
public class SampleTest {
    @Tested
    private Sample sample;

    @Test
    public void testStaticGetPrivateOne() {
        new Expectations(sample) {
            {
                Deencapsulation.invoke(sample, "staticGetOne");
                result = 2;
            }
        };

        Assert.assertTrue(sample.staticGetPrivateOne() == 2);
    }

    @Test
    public void testGetPrivateOne() {
        new Expectations(sample) {
            {
                Deencapsulation.invoke(sample, "getOne");
                result = 2;
            }
        };

        Assert.assertTrue(sample.getPrivateOne() == 2);
    }

    @Test
    public void testGetSampleDependVal(@Injectable SampleDepend sampleDepend) {
        new Expectations(sampleDepend) {
            {
                sampleDepend.getVal();
                result = 2;
            }
        };
        Assert.assertTrue(sample.getSampleDependVal() == 2);
        Assert.assertTrue(sample.getSampleDependVal() == 0);
    }

    @Test
    public void testExecute(@Injectable SampleDepend sampleDepend) {
        sample.execute();
        new Verifications() {
            {
                sampleDepend.getVal();
                times = 1;
                Deencapsulation.invoke(sample, "doExecute");
                times = 1;
            }
        };
    }

    @Test
    public void testInterfaceCallWithInjectable(@Injectable Sample.SampleInterface sampleInterface) {
        Deencapsulation.setField(sample, "sampleInterface", sampleInterface);
        new Expectations() {
            {
                sampleInterface.call();
                result = 2;
            }
        };
        Assert.assertTrue(sample.interfaceCall() == 2);
    }

    @Test
    public void testInterfaceCallWithCapturing(@Capturing Sample.SampleInterface sampleInterface) {
        new Expectations() {
            {
                sampleInterface.call();
                result = 2;
            }
        };
        Assert.assertTrue(sample.interfaceCall() == 2);
    }
}

一个完整的PDF

推荐阅读更多精彩内容

  • JMockit提供了两套API,一套叫做Expectations,用于基于行为的单元测试;一套叫做Faking,用...
    孙兴斌阅读 736评论 0 0
  • 单元测试实践背景 测试环境定位bug时,需要测试同学协助手动发起相关业务URL请求,开发进行远程调试问题:1、远程...
    Zeng_小洲阅读 2,418评论 0 2
  • pdf下载地址:Java面试宝典 第一章内容介绍 20 第二章JavaSE基础 21 一、Java面向对象 21 ...
    王震阳阅读 73,743评论 25 504
  • 模拟类型和实例 期望 该记录重放验证模型 经常与严格的期望严格和非严格Mock 记录期望的结果将调用与特定实例匹配...
    欧阳冉冉阅读 8,103评论 1 2
  • 1.1 spring IoC容器和beans的简介 Spring 框架的最核心基础的功能是IoC(控制反转)容器,...
    simoscode阅读 2,983评论 2 20