JMockit简单使用(二)

       JMockit使用时,建议使用Expectations{}块来录制行为,这样mock对象在运行时必须 依据Expectations块中定义的顺序依次调用方法,不能多调用也不能少调用,可以省略掉Verifications块。一旦多调用或则少调用,那么测试就不会通过,这样可以清晰的把握单元测试每一步、每一次执行的过程。
       但是,测试的方法中如果存在线程的调用来执行录制行为,很多情况用Expectations{}块测试都不会通过,而使用NonStrictExpectations{}块来录制就不会有问题。

示例:
测试目标:
package org.zero.jmockit;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

class OtherVO {
    private String value;

    public String getValue() {
        return value;
    }

    public void setValue(String value) {
        this.value = value;
    }
}

public class Work {
    private OtherVO otherVO;
    private ExecutorService executor = Executors.newFixedThreadPool(4);

    public void task() {
        executor.execute(new Runnable() {
            public void run() {
                otherVO.getValue();
            }
        });
    }
}
测试文件:
package org.zero.jmockit;

import mockit.Deencapsulation;
import mockit.Expectations;
import mockit.Mocked;
import mockit.Tested;
import mockit.integration.junit4.JMockit;

import org.junit.Test;
import org.junit.runner.RunWith;

@RunWith(JMockit.class)
public class WorkTest {
    @Tested
    @Mocked
    Work work;

    @Test
    public void testTask() {
        final OtherVO otherVO = new OtherVO();
        new Expectations(OtherVO.class) { // mock OtherVO的行为
            {
                otherVO.getValue();
                result = "zero";
            }
        };
        new Expectations() {
            {
                Deencapsulation.setField(work, "otherVO", otherVO);
            }
        };
        // --- 通过以上方式可以将mock出来的otherVO设置到work中,当然可以采取其它简便方式
        
        work.task();
    }
}
运行测试,会发现测试不通过,会抛出一下异常:
mockit.internal.MissingInvocation: Missing 1 invocation to:
org.zero.jmockit.OtherVO#getValue()
   on mock instance: org.zero.jmockit.OtherVO@3419866c
	at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50)
	at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:459)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:675)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:382)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:192)
Caused by: Missing invocations
	at org.zero.jmockit.OtherVO.getValue(Work.java)
	at org.zero.jmockit.WorkTest$1.<init>(WorkTest.java:23)
	at org.zero.jmockit.WorkTest.testTask(WorkTest.java:21)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.lang.reflect.Method.invoke(Method.java:497)
	... 6 more
明明调用了work.task(),task()里会调用getValue()方法,但是怎么会报Missing invocations错误(少调了OtherVO#getValue()方法)。为什么呢?原因在于线程的调用,executor.execute()只是通知线程去执行runnable,runnable中任务的执行需要JVM去调度,至于什么时候去执行,可能发生在整个testTask()结束后,而Expectations{}检测到录制行为在testTask()结束前依然没被回放,因此就会报错。

怎么解决呢?
       在work.task()调用后sleep()一段时间,以等runnable中任务被执行了再结束testTask()不就可以了么。这也是一个办法。但最好的解决方式是mock executor的execute()方法,让它不执行runnable.start(),而是执行runnable.run(),如下:
@RunWith(JMockit.class)
public class WorkTest {
    @Tested
    @Mocked
    Work work;

    private ExecutorService executor;

    @Before
    public void initThread() {
        executor = new MockUp<ThreadPoolExecutor>() {
            @Mock
            public void execute(Runnable command) {
                command.run();
            }
        }.getMockInstance();
    }

    @Test
    public void testTask() {
        final OtherVO otherVO = new OtherVO();
        new Expectations(OtherVO.class) { // mock OtherVO的行为
            {
                otherVO.getValue();
                result = "zero";
            }
        };
        new Expectations() {
            {
                Deencapsulation.setField(work, "otherVO", otherVO);
            }
        };
        // --- 通过以上方式可以将mock出来的otherVO设置到work中,当然可以采取其它简便方式

        new Expectations() {
            {
                Deencapsulation.setField(work, "executor", executor);
                // --- 将mock出来executor设置到work中
            }
        };

        work.task();
    }
}
另一种简单方式:
@RunWith(JMockit.class)
public class WorkTest {
    @Tested
    @Mocked
    Work work;

    @Test
    public void testTask() {
        final OtherVO otherVO = new OtherVO();
        new Expectations(OtherVO.class) { // mock OtherVO的行为
            {
                otherVO.getValue();
                result = "zero";
            }
        };
        new Expectations() {
            {
                Deencapsulation.setField(work, "otherVO", otherVO);
            }
        };
        // --- 通过以上方式可以将mock出来的otherVO设置到work中,当然可以采取其它简便方式

        new MockUp<ThreadPoolExecutor>() {
            @Mock
            public void execute(Runnable command) {
                command.run();
            }
        };

        work.task();
    }
}



你可能感兴趣的:(JMockit简单使用(二))