最近有一个需求涉及到的外部系统别较多,只是一个小小的方法有5-6个rpc接口,还有4-5个查询数据库的连接,再加上开发环境,在自测(Junit)时发现环境各种不稳,所以决定将涉及到的相关接口mock掉。
环境准备 && 注意点
jdk + Junit + jmockit
注意:在pom文件中,jmock一定要放在junit的前面加载,不然会报错! 错误信息如下:
JMockit wasn't properly initialized; check that jmockit.jar precedes junit.jar in the classpath (if using JUnit; if not, check the documentation) at com.alipay.fc.process.bp.engine.bpms.executor.UserTaskCreateExecutorTest$1.<init>(UserTaskCreateExecutorTest.java:494) at com.alipay.fc.process.bp.engine.bpms.executor.UserTaskCreateExecutorTest.mockBPTaskService(UserTaskCreateExecutorTest.java:494) at com.alipay.fc.process.bp.engine.bpms.executor.UserTaskCreateExecutorTest.testCreatorAutoDeal2(UserTaskCreateExecutorTest.java:461) |
接口准备
现准备如下接口,sellFish卖鱼,接口如下:
1、DAO
//卖鱼 -- delete操作 public interface FishMarketDAO { /** * 卖鱼 */ boolean sellFish(Fish fish, Integer amount); } |
2、业务层的接口
public interface FishMarketInterface { /** * 卖鱼 */ boolean sellFish(Fish fish, Integer amount); } |
3、业务层实现类
public class FishMarketManageImpl implements FishMarketInterface{ @Autowired private FishMarketDAO fishMarketDAO; /** * 卖鱼 */ @Override public boolean sellFish(Fish fish, Integer amount) { System.out.println("开始卖鱼,卖了" + amount + "条"); boolean sellResult = fishMarketDAO.sellFish(fish, amount); if (sellResult) { System.out.println("卖鱼成功"); } else { System.out.println("卖鱼失败"); } return sellResult; } } |
4、测试类
@RunWith(JMockit.class) public class FishTest { @Tested private FishMarketManageImpl fishMarketManage; @Injectable private FishMarketDAO fishMarketDAO; @Test public void testSellFish() { new Expectations() { { fishMarketDAO.sellFish((Fish) any, 1); result = true; fishMarketDAO.sellFish((Fish) any, 2); result = false; } }; boolean sellTrue = fishMarketManage.sellFish(new Fish(), 1); Assert.assertTrue("卖鱼成功:", sellTrue); boolean sellFalse = fishMarketManage.sellFish(new Fish(), 2); Assert.assertTrue("卖鱼失败:", sellFalse); } } |
卖鱼接口测试讲解
1、在测试类中声明需要测试的类,注意,这里可以直接声明一个实现类对象
@Tested private FishMarketManageImpl fishMarketManage; |
JMockit会通过反射自动实例化。
2、声明需要被mock的接口,并注解@Injectable
假设实际测试中dao连接数据库极不稳定,正常测试100次接口才能成功一次,这种情况下,为了提升效率,我们会将外部接口进行mock。
@Injectable
private FishMarketDAO fishMarketDAO;
3、编写测试类,并注解@Test
@Test public void testSellFish() { } |
4、在测试类中写上期望mock的接口及对应方法的返回值
通过JMockit的Expectations可以实现该功能。这里演示mock最简单的public方法。
new Expectations() { { fishMarketDAO.sellFish((Fish) any, 1); result = true; fishMarketDAO.sellFish((Fish) any, 2); result = false; } }; |
这里的(Fish) any表示允许传入的是任何fish,后面的1、2表示:当传入1时,即卖一条鱼,对应的result=true,即卖鱼成功,也即对应业务层接口中的成功支路。
if (sellResult) { System.out.println("卖鱼成功"); } |
当传入2时,即fishMarketDAO.sellFish((Fish) any, 2);表示卖出两条鱼,对应的result=false,即卖鱼失败,也即对应业务层接口中的失败支路。
else { System.out.println("卖鱼失败"); } |
这样就可以充分测试方法中的每一条支路。
备注:JMockit的mockit.Invocations抽象类中提供了几个任意值,如同上面的(Fish)any,你也可以将第二个参数置为anyInt,表示不管卖多少条鱼,都返回true或false
NonStrictExpectations/Expectations方式
@Test public void testSellFish2() { //录制预期模拟行为 new NonStrictExpectations() { { fishMarketDAO.sellFish((Fish) any, anyInt); result = true; //卖任何数量的任何鱼都是true } }; boolean sell1 = fishMarketManage.sellFish(new Fish(), 1); Assert.assertTrue("卖鱼1条:", sell1); //sell1=true boolean sell2 = fishMarketManage.sellFish(new Fish(), 2); Assert.assertTrue("卖鱼2条:", sell2); //sell2=true //以下代码为了验证你的预期是否有被调用,也可以不写 new Verifications() { { fishMarketDAO.sellFish((Fish) any, 1); times=1; //预期调用“卖一条任何鱼”一次 ==》true fishMarketDAO.sellFish((Fish) any, 2); times=1; //预期调用“卖两条任何鱼”一次 ==》true fishMarketDAO.sellFish((Fish) any, 3); times=1; //预期调用“卖三条任何鱼”一次 ==》false } }; } |
以上测试的堆栈如下:
开始卖鱼,卖了1条
卖鱼成功
开始卖鱼,卖了2条
卖鱼成功
mockit.internal.MissingInvocation: Missing 1 invocation to: com.mybank.bkpartner.mciservicewindowtest.FishMarketDAO#sellFish(com.mybank.bkpartner.mciservicewindowtest.Fish, Integer) with arguments: any com.mybank.bkpartner.mciservicewindowtest.Fish, 3 on mock instance: com.mybank.bkpartner.mciservicewindowtest.$Impl_FishMarketDAO@5470be88 at com.mybank.bkpartner.mciservicewindowtest.FishTest$3.<init>(FishTest.java:63) at com.mybank.bkpartner.mciservicewindowtest.FishTest.testSellFish2(FishTest.java:56) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at java.lang.reflect.Method.invoke(Method.java:597) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at java.lang.reflect.Method.invoke(Method.java:597) at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68) at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:51) at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:237) at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70) Caused by: Missing invocations at com.mybank.bkpartner.mciservicewindowtest.FishTest$3.<init>(FishTest.java:62) ... 9 more |
可以看出,卖出1条和2条鱼是可以成功的,当执行到预期调用“卖三条任何鱼”一次时,就报错了Missing 1 invocation,可以看到
mock静态方法
1.业务层中新增静态方法sayHello与catchFish抓鱼方法
public static String sayHello() { return "基本的hello"; } /** * 抓鱼 */ @Override public boolean catchFish(Fish fish, Integer amount) { System.out.println("要打招呼:" + sayHello()); System.out.println("开始抓鱼,抓了" + amount + "条"); boolean catchResult = fishMarketDAO.catchFish(fish, amount); if (catchResult) { System.out.println("抓鱼成功"); } else { System.out.println("抓鱼失败"); } return catchResult; } |
2.测试方法
@Test public void testCatchFish() { new Expectations(FishMarketManageImpl.class) { { FishMarketManageImpl.sayHello(); result = "我是mock的hello语句"; fishMarketDAO.catchFish((Fish) any, 1); result = true; } }; boolean catchResult = fishMarketManage.catchFish(new Fish(), 1); Assert.assertTrue("抓鱼成功:", catchResult); } |
3.输出堆栈
Connected to the target VM, address: '127.0.0.1: ', transport: 'socket' 要打招呼:我是mock的hello语句 开始抓鱼,抓了1条 抓鱼成功 Disconnected from the target VM, address: '127.0.0.1: ', transport: 'socket' Process finished with exit code 0 |
mock私有方法
1.业务层中新增私有方法getPrivateHello,并在catchFish方法中调用
private String getPrivateHello() { return "基本的私有hello"; } /** * 抓鱼 */ @Override public boolean catchFish(Fish fish, Integer amount) { System.out.println("要打招呼:" + sayHello()); System.out.println("有私聊:" + getPrivateHello()); System.out.println("开始抓鱼,抓了" + amount + "条"); boolean catchResult = fishMarketDAO.catchFish(fish, amount); if (catchResult) { System.out.println("抓鱼成功"); } else { System.out.println("抓鱼失败"); } return catchResult; } |
2.测试方法
@Test public void testCatchFish() { new Expectations(FishMarketManageImpl.class) { { FishMarketManageImpl.sayHello(); result = "我是mock的hello语句"; Deencapsulation.invoke(fishMarketManage, "getPrivateHello"); result = "我是mock的私聊hello语句"; fishMarketDAO.catchFish((Fish) any, 1); result = true; } }; boolean catchResult = fishMarketManage.catchFish(new Fish(), 1); Assert.assertTrue("抓鱼成功:", catchResult); } |
3.输出堆栈
Connected to the target VM, address: '127.0.0.1: ', transport: 'socket' 要打招呼:我是mock的hello语句 有私聊:我是mock的私聊hello语句 开始抓鱼,抓了1条 抓鱼成功 Disconnected from the target VM, address: '127.0.0.1: ', transport: 'socket' Process finished with exit code 0 |