暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

单元测试-PoweMockito使用

菜猿快跑 2019-06-22
935

近期,公司为了提高代码开发质量,减少线上bug出现概率 ,要求开发人员引入单元测试以及sonrQube检测,对代码质量进行检测与测试。为了响应公司的要求,遂开始了学习单元测试之旅。


初识单元测试


1.什么叫做单元测试

  • 单元测试是对呈现代码单元进行函数级别测试,是面向最小软件设计单元的验证工作。

  • 他是软件最小组成单位的测试,是软件开发过程中的最基本的测试。

  • 它处于软件开发过程中实施的最低级别的测试活动,即检查单元程序模块有无错误

  • 它是在编码完成后必须进行的测试工作,也可称之为模块测试


2.为什么要做单元测试? 

单元测试的目的,是将应用程序的所有源代码,隔离成最小的可测试的单元,保证每个单元的正确性。理想情况下,如果每个单元都能保证正确,就能保证应用程序整体相当程度的正确性。

另一方面,单元测试也是一种特殊类型的文档,相对于书面的文档,测试脚本本身往往就是对被测试代码的实际的使用代码,对于帮助开发人员理解被测试单元的使用是相当有帮助。


3.单元测试原则

  • 需要验证的方法、函数体都可以写单元测试

  • 单元测试只验证测试方法的逻辑结果是否能否满足期望返回值

  • 单元测试一般测试当前方法的逻辑,不测试被依赖的类方法、属性、字段或自己的私有方法

4.单元测试框架选取

Java相关的单元测试框架还是很多的,常见的有junit、mockito、Jmockito、easyMock、powerMockito等,这些框架在mock方面都具有比较强大的功能与比较广泛的使用量。本文采用powerMockito框架进行单元测试

powerMockito是一个扩展了其它如EasyMock等mock框架的、功能更加强大的框架,PowerMock旨在用少量的方法和注解扩展现有的API来实现额外的功能。目前PowerMock支持EasyMock和Mockito。


实战


了解单元测试和powerMockito之后,我们开始着手搭建单元测试(PowerMockito)相关环境

单元测试框架:Junit+Mockito+PowerMockito搭配使用


1.引入依赖包

<!-- https://mvnrepository.com/artifact/org.powermock/powermock-api-mockito -->
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-api-mockito</artifactId>
<version>1.4.9</version>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.powermock/powermock-module-junit4 -->
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-module-junit4</artifactId>
<version>1.4.9</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>1.8.5</version>
<scope>test</scope>
</dependency>


2.安装Junit插件

   本文采用开发IDE为IntelliJ IDEA,通过File ->settings(或者使用快捷键Ctrl + Alt + S)打开配置界面,找到Plugins插件管理,搜索junit后,勾选安装junit,如下图所示:

安装完成后,重新进入settings界面,选择junit配置,选择“junit4”,配置junit生成的test文件路径:${SOURCEPATH}/../../test/java/${PACKAGE}/${FILENAME}


3.准备测试代码

    public class GetRollBackReasonType extends ServiceBase {


    private List<Map<String,Object>> list;


    protected String getsql() {
    String sql = "";
    if (CommUtil.isCommMySqlType()) {
    sql = "call proc_queryRollBackReasonType";
    } else {
    sql = "EXEC proc_queryRollBackReasonType";
    }
    log.info("获取退单原因SQL:"+sql);
    return sql;
    }



    @Override
    protected boolean handle() {


    try {
    list = jdbcTemplate.queryForList(getsql());
    addOutput("data",list);
    return true;
    }
    catch (Exception ex){
    log.info("查询数据库失败," + ex);
    }
    return false;


    }
    }


    4.生成Test代码文件

    在被测试代码类中,使用快捷键(Alt+Insert)出现提示框,快速添加对应的Test文件。

    5.单元测试模拟解析

      /** 
      * GetRollBackReasonType Tester.
      *
      * @author <Authors name>
      * @since <pre>六月 20, 2019</pre>
      * @version 1.0
      */


      // powerMockito特殊注解,使用必须添加
      @RunWith(PowerMockRunner.class) 
      //powerMockito特殊注解 当你需要使用PowerMock强大功能(Mock静态、final、私有方法等)的时候,就需要加注解@PrepareForTest。
      @PrepareForTest({CommUtil.class,GetRollBackReasonType.class}) 
      //忽略打桩时产生异常
      @PowerMockIgnore("javax.management.*")
      public class GetRollBackReasonTypeTest 
          private GetRollBackReasonType rollBackReasonType;
      //得到JdbcTemplae对象
          private JdbcTemplate jdbcTemplate;
      @Before
      public void before() throws Exception {
      rollBackReasonType = new GetRollBackReasonType();


      }


      @After
      public void after() throws Exception {
      }




      /**
      *
      * Method: getsql()
      *
      */
      @Test
      public void testGetsql() throws Exception {


          //powerMockito 模拟静态类CommUtil
      PowerMockito.mockStatic(CommUtil.class);
          //模拟CommUtil.isCommMySqlType()返回为true
      PowerMockito.when(CommUtil.isCommMySqlType()).thenReturn(true);
          //模拟PowerMockito.spy(rollBackReasonType) 实例化rollBackReasonType对象,这是实际存在的对象
      GetRollBackReasonType temp = PowerMockito.spy(rollBackReasonType);
          //Assert断言  用于判断代码方法返回值是否与预期相同
      Assert.assertEquals("call proc_queryRollBackReasonType",temp.getsql());
          //验证静态方法调用次数
      PowerMockito.verifyStatic(Mockito.times(1));


      }


      /**
      *
      * Method: output()
      *
      */
      @Test
      public void testHandle() throws Exception {
          //powerMockito 模拟 JdbcTemplate对象
      JdbcTemplate jdbcTemplate = PowerMockito.mock(JdbcTemplate.class);
      //调用jdbcTemplate.queryForList时模拟返回空的集合
      PowerMockito.when(jdbcTemplate.queryForList(Matchers.anyString())).thenReturn(new ArrayList<>());
          //实例化GetRollBackReasonType的一个对象
      GetRollBackReasonType temp = PowerMockito.spy(rollBackReasonType);
          //模拟GetRollBackReasonType私有变量jdbcTemplate的值
      PowerMockito.field(temp.getClass(),"jdbcTemplate").set(temp,jdbcTemplate);
      //模拟GetRollBackReasonType父类私有变量outputMap的值
      PowerMockito.field(temp.getClass(),"outputMap").set(temp,new HashMap<>());
          //模拟GetRollBackReasonType私有方法addOutput的结果
      PowerMockito.method(temp.getClass(),"addOutput").invoke(temp,Matchers.anyString(),Matchers.anyObject());
          //断言返回结果是否成功
      Assert.assertTrue(temp.handle());




      }




      @Test


      public void testHandle1(){


      try {
               //手动加载spring bean 配置文件
      ApplicationContext context = new ClassPathXmlApplicationContext("spring-servlet.xml");
               //实例化JdbcTemplate对象
      jdbcTemplate = (JdbcTemplate) context.getBean("jdbcTemplate");
      //调用jdbcTemplate.queryForList时模拟返回空的集合
      PowerMockito.when(jdbcTemplate.queryForList(Matchers.anyString())).thenReturn(new ArrayList<>());
      //实例化GetRollBackReasonType的一个对象
      GetRollBackReasonType temp = PowerMockito.spy(rollBackReasonType);
      //模拟GetRollBackReasonType私有变量jdbcTemplate的值
      PowerMockito.field(temp.getClass(),"jdbcTemplate").set(temp,jdbcTemplate);
      //模拟GetRollBackReasonType父类私有变量outputMap的值
      PowerMockito.field(temp.getClass(),"outputMap").set(temp,new HashMap<>());
      //模拟GetRollBackReasonType私有方法addOutput的结果
      PowerMockito.method(temp.getClass(),"addOutput").invoke(temp,Matchers.anyString(),Matchers.anyObject());
      //断言返回结果是否成功
      Assert.assertTrue(temp.handle());
      }
      catch (Exception ex){
      System.out.println("数据库执行出错..." + ex);
          }
      }

      以上代码运用了PowerMockito模拟普通对象、模拟静态类方法、模拟私有变量等手段来解决被测试方法的外部依赖,使得被测方法的逻辑不受外部影响,达到充分测试的效果。

      除了以上的使用的模拟情况,powerMockito还支持:

      • 模拟void方法

           

        @Test  
        public void testMockVoidMethod() {
        Hello hello = new Hello();
        Kitty kitty = PowerMockito.mock(Kitty.class);
        hello.setKitty(kitty);
        PowerMockito.doNothing().when(kitty).say3();
        hello.mockVoidMethod();
        }

        • 模拟普通private方法

          public class MockPrivateClass {
          private String privateFunc() {
          return "private func";
          }
          }


          @Test
          public void testPrivateFunc() throws Exception {
          MockPrivateClass privateClass = PowerMockito.spy(new MockPrivateClass());
              PowerMockito.when(privateClass, "privateFunc").thenReturn("test");
          }


          @Test
          public void testPrivateMethod() {
              MockPrivateClass mockPrivateClass = new  MockPrivateClass ();  
          PowerMockito.verifyPrivate(ockPrivateClass ).invoke("privateFunc");
          }

          • 模拟构造方法

            GetRollBackReasonType type= PowerMockito.mock(GetRollBackReasonType .class)  
            PowerMockito.whenNew(type.class).thenReturn(type);


            总结


            单元测试是每个开发人员都应该掌握的职业技能,它能使我们的代码质量显著提升,减少线上bug产生,提高产品质量。而使用PowerMockito作为单元测试框架,简单易懂语法,能够有效帮助开发人员使用

            文章转载自菜猿快跑,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

            评论