1.针对方法打桩

1.1 打桩类的public static方法

测试用例中如果需要对public静态方法的打桩,针对测试类增加注解@RunWith(PowerMockRunner.class)同时针对静态方法所在的类增加注解@PrepareForTest({StaticMethod.class}),接着在测试用例调用方法之前增加

PowerMockito.mockStatic(StaticMethod.class);
PowerMockito.when(StaticMethod.getJavaVersion()).thenReturn(“1.8.0_92”); 或者用写法
PowerMockito.doReturn(“1.8.0_92”).when(StaticMethod.class, “getJavaVersion”);
另外对PowerMockito的调用最好在产品类ProductClass对象new之前进行,这样保障一切模拟数据就绪后再进行产品类的测试,保证测试用例一次性成功编写的几率。

import junit.framework.TestCase;
import org.junit.runner.RunWith;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;

@RunWith(PowerMockRunner.class)
@PrepareForTest({StaticMethod.class})
public class ProductClassTest extends TestCase {
    public void test_getCustomNameByVersion() {
        PowerMockito.mockStatic(StaticMethod.class);
        PowerMockito.when(StaticMethod.getJavaVersion()).thenReturn("1.8.0_92");
        ProductClass product = new ProductClass();
        assertEquals("18VersionSeries", product.getCustomNameByVersion());
    }
}

ProductClassTest.java
public class ProductClass {
    public String getCustomNameByVersion() {
        String javaVersion = StaticMethod.getJavaVersion();
        if(javaVersion.split("_")[0].contains("1.8")) {
            return "18VersionSeries";
        }
        else {
            return "OTHER";
        }
    }
}

ProductClass.java产品代码调用其他类的静态方法
public class StaticMethod {
    public static String getJavaVersion() {
        throw new RuntimeException();
    }
}

1.2 打桩类的private static方法

针对StaticMethod类中的private static方法打桩的时候,外部调用StaticMethod类的public方法仍然保持实际代码的调用,因此在模拟private static方法之前,增加一行

PowerMockito.spy(StaticMethod.class);或者

PowerMockito.when(StaticMethod.getJavaVersion()).thenCallRealMethod();
以此保证除了具体的某个方法打桩,其他的方法保持照旧。另外,针对private方法,因为正常是无法直接访问的,因此需要使用

下面的形式进行调用。这里要注意一个事情,doReturn when 和 when thenReturn的差异,后者是虽然做了打桩但是实际的代码还是会走一遍。

PowerMockito.doReturn(true).when(StaticMethod.class, “isProduct”);

import junit.framework.TestCase;
import org.junit.runner.RunWith;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;

@RunWith(PowerMockRunner.class)
@PrepareForTest({StaticMethod.class})
public class ProductClassTest extends TestCase {
    
    public void test_getCustomNameByVersion_product() throws Exception {
        PowerMockito.mockStatic(StaticMethod.class);
        PowerMockito.spy(StaticMethod.class);
        PowerMockito.doReturn(true).when(StaticMethod.class, "isProduct");

        ProductClass product = new ProductClass();
        assertEquals("18VersionSeries", product.getCustomNameByVersion());
    }
}

ProductClassTest.java测试用例
public class StaticMethod {
    public static String getJavaVersion() {
        if (isProduct()) {
            System.out.println("getJavaVersion()::product");
            return System.getProperty("java.version");
        }
        System.out.println("getJavaVersion()::test");
        throw new RuntimeException();
    }

    private static boolean isProduct() {
        if (System.getenv().containsKey("isProduct=false")) {
            System.out.println("isProduct()::false");
            return false;
        } else {
            System.out.println("isProduct()::true");
            return true;
        }
    }
}

StaticMethod.java
public class ProductClass {
    public String getCustomNameByVersion() {
        String javaVersion = StaticMethod.getJavaVersion();
        if(javaVersion.split("_")[0].contains("1.8")) {
            return "18VersionSeries";
        }
        else {
            return "OTHER";
        }
    }
}

ProductClass.java

1.3 打桩类的public方法实现部分中使用的new对象

如果要对 StudentHandler.java的new对象进行mock,那么需要把StudentHandler对象所在的类StudentMng放到 @PrepareForTest({StudentMng.class})

需要特别注意,在PrepareForTest中放的类在统计代码覆盖率的时候,覆盖率是0。如果要针对该类进行代码测试覆盖,那需要以其他的方式进行模拟测试。

1 package training;
 2 
 3 import junit.framework.TestCase;
 4 import org.junit.runner.RunWith;
 5 import org.powermock.api.mockito.PowerMockito;
 6 import org.powermock.core.classloader.annotations.PrepareForTest;
 7 import org.powermock.modules.junit4.PowerMockRunner;
 8 
 9 
10 import java.util.ArrayList;
11 import java.util.List;
12 @RunWith(PowerMockRunner.class)
13 @PrepareForTest({StudentMng.class})
14 public class StudentMngTest extends TestCase {
15 
16 
17         public void test_getSpecifiedStudents() throws Exception {
18             StudentHandler handler = PowerMockito.mock(StudentHandler.class);
19             PowerMockito.when(handler.getAllStudents()).thenReturn(mockStudents());
20             PowerMockito.whenNew(StudentHandler.class).withNoArguments().thenReturn(handler);
21             StudentMng mng = new StudentMng();
22             assertEquals(1, mng.getSpecifiedStudents(10).size());
23     }
24 
25     private List<Student> mockStudents() {
26         List<Student> students = new ArrayList<>();
27         Student stu = new Student();
28         stu.setScore(new Score(80, 11));
29         students.add(stu);
30         return students;
31     }
32 }

StudentMngTest.java
1 package training;
 2 
 3 import java.util.List;
 4 
 5 public class StudentHandler {
 6     public StudentHandler() {
 7         System.out.println("invoke StudentHandler()");
 8         throw new RuntimeException();
 9     }
10     public List<Student> getAllStudents() {
11         System.out.println("invoke getAllStudents");
12         throw new RuntimeException();
13     }
14 }

StudentHandler.java 是一个不方便直接new的类
1 package training;
 2 
 3 public class Student {
 4     private String sno;
 5     private String name;
 6     private Score score = new Score(100, 1000);
 7 
 8     public String getName() {
 9         System.out.println("invoke getName");
10         return name;
11     }
12 
13     public void setName(String name) {
14         System.out.println("invoke setName");
15         this.name = name;
16     }
17 
18     public String getSno() {
19         System.out.println("invoke getSno");
20         return sno;
21     }
22 
23     public void setSno(String sno) {
24         System.out.println("invoke setSno");
25         this.sno = sno;
26     }
27 
28     public void setScore(Score score) {
29         System.out.println("invoke setScore");
30         this.score = score;
31     }
32 
33     public Score getScore() {
34         System.out.println("invoke getScore");
35         return score;
36     }
37 
38     public String getScoreRecord(int delta) {
39         System.out.println("invoke getScoreRecord");
40         return String.format("sno:%s,name:%s,value:%d,level:%d", sno, name, score.getValue() + delta, score.getLevel());
41     }
42 }

Student.java
1 package training;
 2 
 3 public class Score {
 4     private int value;
 5     private int level;
 6 
 7     public Score(int value, int level) {
 8         this.value = value;
 9         this.level =  level;
10     }
11 
12     public int getLevel() {
13         System.out.println("invoke getLevel");
14         return level;
15     }
16 
17     public void setLevel(int level) {
18         System.out.println("invoke setLevel");
19         this.level = level;
20     }
21 
22     public int getValue() {
23         System.out.println("invoke getValue");
24         return value;
25     }
26 
27     public void setValue(int value) {
28         System.out.println("invoke setValue");
29         this.value = value;
30     }
31 }

Score.java

1.4打桩类的public方法

2种方式,方式一:PowerMockito.mock方式,对应StudentTest.java中的test_mock_public_method_powermock()测试用例
方式二:函数复写override方式,对应StudentTest.java中的test_mock_public_method_override()测试用例

两种方式比较,方式一代码看起来简洁。 方式二 测试用例运行时间效率很高。

1 package training;
 2 
 3 import junit.framework.TestCase;
 4 import org.powermock.api.mockito.PowerMockito;
 5 
 6 public class StudentTest extends TestCase {
 7     public void test_mock_public_method_powermock()
 8     {
 9         Student stu = PowerMockito.mock(Student.class);
10         PowerMockito.when(stu.getName()).thenReturn("test_name");
11         PowerMockito.when(stu.getNewName()).thenCallRealMethod();
12         assertEquals("new_test_name", stu.getNewName());
13     }
14 
15     public void test_mock_public_method_override()
16     {
17         Student stu = new Student() {
18             @Override
19             public String getName() {
20                 return "test_name";
21             }
22         };
23         assertEquals("new_test_name", stu.getNewName());
24     }
25 }

StudentTest.java
1 package training;
 2 
 3 public class Student {
 4     private String name;
 5     
 6 
 7     public String getName() {
 8         System.out.println("invoke getName");
 9         throw new RuntimeException();
10 //        return name;
11     }
12 
13     public String getNewName() {
14         System.out.println("invoke getNewName");
15         return "new_" + getName();
16     }
17     
18 }

Student.java

1.5 打桩类的private方法

Student.java类中getName方法为private的,因此需要使用PowerMockito.when(stu, “getName”).thenReturn(“test_name”); 方式进行mock。

被模拟的Student类需要在测试用例test类的开头增加以下两行。

@RunWith(PowerMockRunner.class)
@PrepareForTest({Student.class})

1 package training;
 2 
 3 import junit.framework.TestCase;
 4 import org.junit.runner.RunWith;
 5 import org.powermock.api.mockito.PowerMockito;
 6 import org.powermock.core.classloader.annotations.PrepareForTest;
 7 import org.powermock.modules.junit4.PowerMockRunner;
 8 
 9 @RunWith(PowerMockRunner.class)
10 @PrepareForTest({Student.class})
11 public class StudentTest extends TestCase {
12     public void test_mock_public_method_powermock()
13     {
14         Student stu = PowerMockito.mock(Student.class);
15         try {
16             PowerMockito.when(stu, "getName").thenReturn("test_name");
17         } catch (Exception e) {
18             e.printStackTrace();
19         }
20         PowerMockito.when(stu.getNewName()).thenCallRealMethod();
21         assertEquals("new_test_name", stu.getNewName());
22     }
23 
24 }

StudentTest.java
1 package training;
 2 
 3 public class Student {
 4     private String sno;
 5     private String name;
 6     private Score score = new Score(100, 1000);
 7 
 8     private String getName() {
 9         System.out.println("invoke getName");
10         throw new RuntimeException();
11 //        return name;
12     }
13 
14     public String getNewName() {
15         System.out.println("invoke getNewName");
16         return "new_" + getName();
17     }
18 
19     public void setName(String name) {
20         System.out.println("invoke setName");
21         this.name = name;
22     }
23 
24     public String getSno() {
25         System.out.println("invoke getSno");
26         return sno;
27     }
28 
29     public void setSno(String sno) {
30         System.out.println("invoke setSno");
31         this.sno = sno;
32     }
33 
34     public void setScore(Score score) {
35         System.out.println("invoke setScore");
36         this.score = score;
37     }
38 
39     public Score getScore() {
40         System.out.println("invoke getScore");
41         return score;
42     }
43 
44     public String getScoreRecord(int delta) {
45         System.out.println("invoke getScoreRecord");
46         return String.format("sno:%s,name:%s,value:%d,level:%d", sno, name, score.getValue() + delta, score.getLevel());
47     }
48 }

Student.java

2.针对变量打桩

2.1 打桩类的private成员变量

方法一: Whitebox

来源StudentMngTest.java,关键模拟代码如下,Whitebox.setInternalState(mng, “handler”, handler);打桩设置对象的私有成员变量。

PowerMockito.when(mng.getSpecifiedStudents(10)).thenCallRealMethod();控制调用实际的方法。

StudentMng mng = PowerMockito.mock(StudentMng.class); 控制StudentMng对象的初始化过程,保证在对象初始化的时候不去做StudentHandler成员变量的初始化。

public void test_getSpecifiedStudents() {
StudentHandler handler = PowerMockito.mock(StudentHandler.class);
PowerMockito.when(handler.getAllStudents()).thenReturn(mockStudents());
StudentMng mng = PowerMockito.mock(StudentMng.class);
Whitebox.setInternalState(mng, “handler”, handler);
PowerMockito.when(mng.getSpecifiedStudents(10)).thenCallRealMethod();
assertEquals(1, mng.getSpecifiedStudents(10).size());
}

package training;

import junit.framework.TestCase;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.reflect.Whitebox;

import java.util.ArrayList;
import java.util.List;

public class StudentMngTest extends TestCase {
    public void test_getSpecifiedStudents() {
        StudentHandler handler = PowerMockito.mock(StudentHandler.class);
        PowerMockito.when(handler.getAllStudents()).thenReturn(mockStudents());
        StudentMng mng = PowerMockito.mock(StudentMng.class);
        Whitebox.setInternalState(mng, "handler", handler);
        PowerMockito.when(mng.getSpecifiedStudents(10)).thenCallRealMethod();
        assertEquals(1, mng.getSpecifiedStudents(10).size());
    }

    private List<Student> mockStudents() {
        List<Student> students = new ArrayList<>();
        Student stu = new Student();
        stu.setScore(new Score(80, 11));
        students.add(stu);
        return students;
    }
}

StudentMngTest.java 打桩后 连初始化StudentMng对象的时候都模拟完成。
package training;

import java.util.List;

public class StudentHandler {
    public StudentHandler() {
        System.out.println("invoke StudentHandler()");
//        throw new RuntimeException();
    }
    public List<Student> getAllStudents() {
        System.out.println("invoke getAllStudents");
        throw new RuntimeException();
    }
}

StudentHandler.java
package training;

public class Student {
    private String sno;
    private String name;
    private Score score = new Score(100, 1000);

    public String getName() {
        System.out.println("invoke getName");
        return name;
    }

    public void setName(String name) {
        System.out.println("invoke setName");
        this.name = name;
    }

    public String getSno() {
        System.out.println("invoke getSno");
        return sno;
    }

    public void setSno(String sno) {
        System.out.println("invoke setSno");
        this.sno = sno;
    }

    public void setScore(Score score) {
        System.out.println("invoke setScore");
        this.score = score;
    }

    public Score getScore() {
        System.out.println("invoke getScore");
        return score;
    }

    public String getScoreRecord(int delta) {
        System.out.println("invoke getScoreRecord");
        return String.format("sno:%s,name:%s,value:%d,level:%d", sno, name, score.getValue() + delta, score.getLevel());
    }
}

Student.java
package training;

public class Score {
    private int value;
    private int level;

    public Score(int value, int level) {
        this.value = value;
        this.level =  level;
    }

    public int getLevel() {
        System.out.println("invoke getLevel");
        return level;
    }

    public void setLevel(int level) {
        System.out.println("invoke setLevel");
        this.level = level;
    }

    public int getValue() {
        System.out.println("invoke getValue");
        return value;
    }

    public void setValue(int value) {
        System.out.println("invoke setValue");
        this.value = value;
    }
}

Score.java

方法二(更加通用):用以下方式修改属性的访问权限,另外 @PrepareForTest({StudentMng.class}) 和 PowerMockito.whenNew(StudentHandler.class).withNoArguments().thenReturn(handler); 同时来达到mock new对象的目的。

Field handlerField = mng.getClass().getDeclaredField(“handler”);
handlerField.setAccessible(true);
handlerField.set(mng, handler);

1 package training;
 2 
 3 import junit.framework.TestCase;
 4 import org.junit.runner.RunWith;
 5 import org.powermock.api.mockito.PowerMockito;
 6 import org.powermock.core.classloader.annotations.PrepareForTest;
 7 import org.powermock.modules.junit4.PowerMockRunner;
 8 
 9 import java.lang.reflect.Field;
10 import java.util.ArrayList;
11 import java.util.List;
12 
13 @RunWith(PowerMockRunner.class)
14 @PrepareForTest({StudentMng.class})
15 public class StudentMngTest extends TestCase {
16     public void test_getSpecifiedStudents() throws Exception {
17         StudentHandler handler = PowerMockito.mock(StudentHandler.class);
18         PowerMockito.when(handler.getAllStudents()).thenReturn(mockStudents());
19         PowerMockito.whenNew(StudentHandler.class).withNoArguments().thenReturn(handler);
20         StudentMng mng = new StudentMng();
21         Field handlerField = mng.getClass().getDeclaredField("handler");
22         handlerField.setAccessible(true);
23         handlerField.set(mng, handler);
24         assertEquals(1, mng.getSpecifiedStudents(10).size());
25     }
26 
27     private List<Student> mockStudents() {
28         List<Student> students = new ArrayList<>();
29         Student stu = new Student();
30         stu.setScore(new Score(80, 11));
31         students.add(stu);
32         return students;
33     }
34 }

StudentMngTest.java

2.2 打桩类的public static变量或者private static变量

针对此类情况,建议对static变量赋值的部分进行mock模拟,以此来达到模拟变量值得目的。

3.测试用例执行效率简单说明

override方式模拟打桩方式的执行测试用例时间消耗

< 不带@RunWith(PowerMockRunner.class)和@PrepareForTest({StudentMng.class}) 方式的执行测试用例时间消耗

< 只带@RunWith(PowerMockRunner.class)方式的的执行测试用例时间消耗

< 都带@RunWith(PowerMockRunner.class)和@PrepareForTest({StudentMng.class}) 方式的的执行测试用例时间消耗

关于代码覆盖率:在PrepareForTest中放的类在统计代码覆盖率的时候,覆盖率是0。如果要针对该类进行代码测试覆盖,

那需要以其他的方式进行模拟测试。

关于编写产品代码:建议在编写产品代码的时候,尽量按照对象注入方式,同时减少静态方法的实现方式,

以此也能增加代码的灵活性,同时在给编写测试用例也能带来较大的便利。

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐