PowerMocker&Jacoco单元测试全解

PowerMocker&Jacoco单元测试全解

第一章 pom文件的引入与各种坑

1. pom文件

<!-- 测试包 -->
<dependency>
   <groupId>junit</groupId>
   <artifactId>junit</artifactId>
   <version>4.12</version>
   <scope>test</scope>
</dependency>
<!-- 主要是PowerMocker的工具包 -->
<dependency>
    <groupId>org.powermock</groupId>
    <artifactId>powermock-api-mockito2</artifactId>
    <version>2.0.2</version>
    <scope>test</scope>
</dependency>
<!-- jacocomaven插件包 -->
<dependency>
    <groupId>org.jacoco</groupId>
    <artifactId>jacoco-maven-plugin</artifactId>
    <version>0.8.2</version>
</dependency>
<!-- powermock -->
<dependency>
    <groupId>org.powermock</groupId>
    <artifactId>powermock-module-junit4</artifactId>
    <version>2.0.2</version>
    <scope>test</scope>
</dependency>

2. 插件

<build>
    <plugins>
        <!-- 主要的插件,我这个用的是离线模式,一定要用离线模式,
							因为@PrepareForTest 会和传统模式冲突-->
        <plugin>
            <groupId>org.jacoco</groupId>
            <artifactId>jacoco-maven-plugin</artifactId>
            <version>0.8.2</version>
            <executions>
                <execution>
                    <id>default-instrument</id>
                    <goals>
                        <goal>instrument</goal>
                    </goals>
                </execution>
                <execution>
                    <id>default-restore-instrumented-classes</id>
                    <goals>
                        <goal>restore-instrumented-classes</goal>
                    </goals>
                </execution>
                <execution>
                    <id>report</id>
                    <phase>prepare-package</phase>
                    <goals>
                        <goal>report</goal>
                    </goals>
                    <configuration>
                        <dataFile>${project.build.directory}/jacoco.exec</dataFile>
                    </configuration>
                </execution>
            </executions>
        </plugin>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-surefire-plugin</artifactId>
            <version>2.22.2</version>
            <configuration>
                <systemPropertyVariables>
                    <!-- 生成的jacoco.exec文件就是统计覆盖率的文件-->
                    <jacoco-agent.destfile>
                        ${project.build.directory}/jacoco.exec
                    </jacoco-agent.destfile>
                </systemPropertyVariables>
            </configuration>
        </plugin>
        <!-- 会生成大量的Test测试文件,但是不建议使用,后面会介绍插件使用-->
        <plugin>
            <groupId>com.github.houbb</groupId>
            <artifactId>gen-test-plugin</artifactId>
            <version>0.0.1</version>
            <configuration>
                <encoding>UTF-8</encoding>
                <isOverwriteWhenExists>false</isOverwriteWhenExists>
                <junitVersion>4</junitVersion>
            </configuration>
        </plugin>
    </plugins>
</build>
######################################################################################## 
## 
## Available variables: 
##         $entryList.methodList - List of method composites 
##         $entryList.privateMethodList - List of private method composites 
##         $entryList.fieldList - ArrayList of class scope field names 
##         $entryList.className - class name 
##         $entryList.packageName - package name 
##         $today - Todays date in yyyy-MM-dd format 
## 
##            MethodComposite variables: 
##                $method.name - Method Name 
##                $method.signature - Full method signature in String form 
##                $method.reflectionCode - list of strings representing commented out reflection code to access method (Private Methods) 
##                $method.paramNames - List of Strings representing the method's parameters' names 
##                $method.paramClasses - List of Strings representing the method's parameters' classes 
## 
## You can configure the output class name using "testClass" variable below. 
## Here are some examples: 
## Test${entry.ClassName} - will produce TestSomeClass 
## ${entry.className}Test - will produce SomeClassTest 
## 
######################################################################################## 
## 
#macro (cap $strIn)$strIn.valueOf($strIn.charAt(0)).toUpperCase()$strIn.substring(1)#end 
## Iterate through the list and generate testcase for every entry. 
#foreach ($entry in $entryList) 
#set( $testClass="${entry.className}Test") 
## 
package $entry.packageName; 

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.modules.junit4.PowerMockRunner;

import java.util.Date;

import java.util.List;
import java.util.Map;

/**
 * APPConstant Tester.
 *
 * @author shihaoyan
 * @version 1.0
 * @since ${today}
 */
@RunWith(PowerMockRunner.class)
public class $testClass { 

    private ${entry.className} entry = new ${entry.className}();
################################## 属性 #############################
##    #foreach($field in $entry.fieldList) 
##    @Mock
##    private $field.valueOf($field.charAt(0)).toUpperCase()$field.substring(1) $field;
##    #end
###################controller层,如果需要注入request、respose进行创建#####
    #set($a=0)
    #foreach($method in $entry.methodList)
    #foreach($classes in $method.paramClasses)
    #if($classes.contains('HttpServletRequest')&&$a==0)
    private MockHttpServletRequest request = new MockHttpServletRequest();       
    #set($a=1)
    #end
    #end
    #end
    #set($a=0)
    #foreach($method in $entry.methodList)
    #foreach($classes in $method.paramClasses)
    #if($classes.contains('HttpServletResponse')&&$a==0)
    private MockHttpServletResponse response = new MockHttpServletResponse();       
    #set($a=1)
    #end
    #end
    #end
#################### end ###########################################
   

    @Before
    public void setUp() {
        try {
            ${entry.className} entry1 = new ${entry.className}();
            entry = PowerMockito.spy(entry);
            entry.hashCode();
            entry.equals(entry1);
####################################################################
            #foreach($field in $entry.fieldList)
            PowerMockito.field(${entry.className}.class,"$field").set(entry,$field);
            EasyTestUtils.initMock($field);
            #end
####################################################################
        } catch (Exception e) {

        }
    }


#foreach($method in $entry.methodList) 
    #set($k=0)
    #set($j=0)
    #foreach($classes in $method.paramClasses)
        #set($j=$j+1)
    #end
    /** 
    * 
    * Method: $method.signature 
    * 
    */ 
    @Test
    public void test#cap(${method.name})() { 
        try {
            entry.${method.name}(#foreach($classes in $method.paramClasses)#set($k=$k+1)#if($classes.contains('String')&&!$classes.contains('<')&&!$classes.contains('>')&&!$classes.contains('['))"abcd"#elseif($classes.contains('List'))null#elseif($classes.contains('HttpServletRequest'))request#elseif($classes.contains('HttpServletResponse'))response#elseif($classes.contains('long'))1000L#elseif($classes.contains('Date'))new Date()#elseif($classes.contains('int')&&!$classes.contains('['))1#elseif($classes.contains('int')&&$classes.contains('['))new int[]{1}#elseif($classes.contains('short'))(short)1#elseif($classes.contains('boolean'))true#elseif($classes.contains('byte[]'))"abcd".getBytes()#elseif($classes.contains('String')&&$classes.contains('['))new String[]{"abcd"}#elseif($classes.contains('byte')&&!$classes.contains('['))(byte)1#else null#end#if($k<$j),#end#end);
        } catch (Exception e) {
        }
    } 

#end 
#foreach($method in $entry.privateMethodList) 
    #set($i=0)
    #set($length=0)
    #foreach($classes in $method.paramClasses)
        #set($length=$length+1)
    #end
    #set($n=0)
   /** 
    * 
    * Method: $method.signature 
    *
    */
    @Test
    public void test#cap(${method.name})() {  
        try {
            PowerMockito.method(${entry.className}.class, "${method.name}"#foreach($classes in $method.paramClasses)#set($i=$i+1)#if($i==1),#end#if($classes.contains('String')&&!$classes.contains('<')&&!$classes.contains('>'))${classes.substring($classes.indexOf('String'))}.class#elseif($classes.contains('List'))List.class#elseif($classes.contains('Set'))Set.class#elseif($classes.contains('HttpServletRequest'))HttpServletRequest.class#elseif($classes.contains('HttpServletRespone'))HttpServletRespone.class#elseif($classes.contains('Map'))Map.class#else${classes}.class#end#if($i<$length),#end#end)
                    .invoke(entry #foreach($classes in $method.paramClasses)#set($n=$n+1)#if($n==1),#end#if($classes.contains('String')&&!$classes.contains('<')&&!$classes.contains('>'))"abcd"#elseif($classes.contains('List'))null#elseif($classes.contains('int')&&!$classes.contains('['))1#elseif($classes.contains('short'))(short)1#elseif($classes.contains('boolean'))true#elseif($classes.contains('byte[]'))"abcd".getBytes()#elseif($classes.contains('byte')&&!$classes.contains('[')) (byte)1 #else null #end#if($n<$length),#end#end);
        } catch (Exception e) {
        }
    } 
#end 
} 
#end

第二章 关于插件的使用

第一种:gen-test-plugin

这个是git提供的一个自动插件,能根据模板生成所有Test文件,但是灵活性比较差。推荐使用下面介绍的一款

第二种:JUnitGeneratorV2.0插件

  1. 安装方式:直接在idea设置里面安装就行了。
  2. 配置:在这里插入图片描述

**注意:**Output Path: S O U R C E P A T H / . . / . . / t e s t / j a v a / {SOURCEPATH}/../../test/java/ SOURCEPATH/../../test/java/{PACKAGE}/${FILENAME}

这个一定改掉

  1. 模板配置

    ​ 放在上面了。研究一下就能看懂,直接中就行。

  2. 使用方式:alt+insert
    在这里插入图片描述

  3. 注意事项

    • 如何使用了lomback 会出现错误,需要把源码中的lomback删掉然后手动生成getter/setter,在使用。
    • 如果是service/controller这种需要注入服务对象的类需要把有一段代码开开,会自动生成Field并进行自动Mock
    • 其他的直接用就行,但是会出现错误,但是错误不会很多,我们生成之后 简单调一下就行。

第三章 模板语言 Velocity 语法规则

  1. 定义变量: #set(i=1)
  2. 想要让变量+1:#set(i=i+1)
  3. 引用变量 $i
  4. 循环:#foreach($method in $entry.methodList) … #end
  5. 分支:#if() … #else() … #end
  6. 多分支:#if()… #elseif() … #end
  7. Java中的字符串处理函数都能,所以能玩很多东西。
  8. 学习网址:http://www.51gjie.com/javaweb/900.html

第四章 PowerMocker详解

1. 注解详解

  1. @RunWith(PowerMockRunner.class):每个测试类必须添加
  2. @PowerMockIgnore({“javax.management.*”}):不标记这个注解会出现一个红色的错误,虽然没有任何影响,但是真的非常丑。
  3. @PrepareForTest({}):这个注解非常重要,我们需要测试某些静态方法,或者私有方法,需要XXX.class写在上面。建议先把当前测试类 直接写进去。
  4. Mock:最重要的注解,我们当测试一个类的时候,肯定会出现调用其他的对象,比如说XxxService类,但是这个类可能就是一个接口,也可能太复杂了,不能直接使用,甚至有的还是spring注入的,那就没办法了。我们需要通过声明一个需要的对象,然后添加一个Mock注解,这样就不会出现空指针了,然后交给PowerMock进行管理。

2. 常用方法详解

  1. PowerMockito.when(xxxAAA.getId()).thenReturn(“123”); 这个方法是最重要的,先说明,当我们在一个方法中调用了另一个对象的方法,我们可以直接通过这个方法去模拟返回值,这个是必须的,因为我们调用任何方法都是为了得到一个结果。所以他是必须要会的。
  2. PowerMockito.doNothing().when(对象实例,“方法”,参数);这个可以模拟void方法
  3. Mockito.any()模拟一个任何参数
  4. Mockito.anyString()模拟String类型的参数
  5. Mockito.isNull()模拟null
  6. Mockito.anyXxx()什么都能模拟
  7. 注意:如果要模拟一个方法,出现一个模拟参数,那必须全部都用模拟参数,模拟的方法需要的参数需要注意是不是为null,需要一一对应的不然模拟不到。
  8. PowerMockito.mock(Xxxxx.class) 这个是模拟一个对象类似于@Mock
  9. PowerMockito.mockStatic(Xxxx.class) 如果需要模拟一个静态方法,需要先进行mockstatic,注意需要在@PrepareForTest中添加这个静态类。
  10. PowerMockito.spy(Xxx.class); 其实相当于我们能模拟这个实体类,也会走真正的方法。mock的对象不会走真正的方法的。
  11. PowerMockito.doReturn(返回值).when(对象实例, “方法”,参数列表); 这个方法表示不执行的模拟的方法体,就是调用这个方法 不走原先的方法体直接给你个值。
  12. PowerMockito.field(Xxxxxxxxx.class, “属性名称”).set(对象实例, 注入的值); 能够对方法中的某个属性进行注入。

3. 模拟各种方法

1. 模拟普通:PowerMockito.when(userInfo.getId()).thenReturn("123");
2. 模拟void方法:PowerMockito.doNothing().when(UserInfo.class,"setId","123"));
3. 模拟static方法(模拟静态方法)PowerMockito.when(UserInfo.getId()).thenReturn("123");
4. 模拟static void方法(模拟静态无返回值方法)和模拟void方法一样的。
5. 如果模拟一个普通方法,但是这个方法有异常,我们就需要不走真实的方法,直接走模拟:PowerMockito.doReturn(返回值).when(对象实例, "方法",参数列表);
6. 所有的模拟方法返回参数都可以设置多个,挺有意思的,因为这样可以根据第几次调用某个方法,得到不同的结果。

第五章 各种问题踩坑整理

PowerMocker和jacoco统计覆盖率为零的问题

这个问题其实很多是因为@PrepareForTest注解和jacoco出冲突了。因为jacoco是通过字节码插桩的方式进行统计的,所以有冲突,解决办法就是直接使用离线模式,就是前面介绍的方式。

模拟方法失效的问题

  1. 可能是因为参数不对,就是可能有的参数是null,这个需要单独模拟的。
  2. 可能模拟方式不对,需要变换一个模拟方式 比如所doReturn 或者 thenReturn啊

静态方法模拟失败

可能是没有在注解上声明@PrepareForTest

其他问题

如果碰到其他问题可以私信联系我,一般都能解决。

  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值