JMockit+Junit 基于行为 mock学习

发表于:2017-9-20 16:39

字体: | 上一篇 | 下一篇 | 我要投稿

 作者:木子李_af14    来源:51Testing软件测试网采编

  最近有一个需求涉及到的外部系统别较多,只是一个小小的方法有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
《2023软件测试行业现状调查报告》独家发布~

关注51Testing

联系我们

快捷面板 站点地图 联系我们 广告服务 关于我们 站长统计 发展历程

法律顾问:上海兰迪律师事务所 项棋律师
版权所有 上海博为峰软件技术股份有限公司 Copyright©51testing.com 2003-2024
投诉及意见反馈:webmaster@51testing.com; 业务联系:service@51testing.com 021-64471599-8017

沪ICP备05003035号

沪公网安备 31010102002173号