0


PowerMock测试框架详解

摘要

本文介绍了Mock背景,常见的单元测试场景以及对应的测试方法,最后简单介绍了powermockit测试框架。理解本文内容,会带你入门java单元测试,其中第一章、第二章需要重点理解。

一、背景

Mock是什么?

Mock(模拟的)是一种隔离测试类功能的方法。例如:mock测试不需要真实连接数据库,或读取配置文件,或连接服务器。mock对象模拟真实服务,mock对象会返回与传递给它的某个虚拟输入相对应的虚拟数据。

为什么要使用Mock?

单元测试重点在于验证代码逻辑以及结果是否正确,但是在测试过程中经常会遇到如下痛点问题:

  • 接口之间的相互依赖
  • 第三方接口调用,例如连接数据库,连接https等

使用Mock框架可以模拟出外部依赖,只注重测试代码逻辑、验证代码结果,满足测试真实目的。

Mock框架使用流程

  1. 创建 外部依赖Mock 对象, 然后将Mock 对象注入到 测试类 中;
  2. 执行 测试代码
  3. 校验 测试代码 是否执行正确。

本文所需的maven依赖

<scope>test</scope>表示仅作用于测试目录。默认作用于范围compile表示被依赖项目需要参与当前项目的编译,包含测试目录。

**mvn -Dmaven.test.skip clean install ** 方式可以不编译测试用例

<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.13</version>
    <scope>test</scope>
</dependency>

<dependency>
    <groupId>org.powermock</groupId>
    <artifactId>powermock-module-junit4</artifactId>
    <version>2.0.2</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.powermock</groupId>
    <artifactId>powermock-api-mockito2</artifactId>
    <version>2.0.2</version>
    <scope>test</scope>
</dependency>

二、单元测试待测内容有哪些分类?

2.1 无框架java代码

使用Junit单元测试包进行测试。

1.被测试类中方法无其他依赖

1)普通无依赖静态方法

静态方法无其他依赖类,可以直接调用方法计算结果。只需要构造期望数据,计算实际数据,最后比对数据。

代码示例:

被测试类:

public class MyMath {
    public static int add(int num1,int num2) {
        return num1 + num2;
    }
}

测试代码:

//测试静态方法
    @Test
    public void testStaticAdd() {
        int num1 = 1;
        int num2 = 1;
        int expect = 2;
        int real = MyMath.add(num1, num2);
        Assert.assertEquals(expect, real);
    }

2)非静态无依赖方法

代码示例:

被测试类:

public class MyMath {
    public int multi(int num1,int num2) {
        return num1 * num2;
    }
}

测试代码:

@Test
    public void testMulti() {
        int num1 = 2;
        int num2 = 3;
        int expect = 6;
        int real = new MyMath().multi(num1, num2);
        Assert.assertEquals(expect, real);
    }

2.被测试类中方法存在其他可实现依赖

存在其他可实现依赖时,和2.1测试思路一致。

代码示例:

被测试类:

public class MyRectangle {
    int height;
    int width;
    MyMath myMath = new MyMath();
    
    public void setHeight(int height) {
        this.height = height;
    }

    public void setWidth(int width) {
        this.width = width;
    }

    public int getArea() {
        return myMath.multi(width, height);
    }
    
    public int getPerimeter() {
        return myMath.multi(2, MyMath.add(width, height));
    }
}

测试代码:

    @Test
    public void testRectangleGetArea() {
        int width = 2;
        int height = 3;
        MyRectangle rectangle = new MyRectangle();
        rectangle.setWidth(width);
        rectangle.setHeight(height);
        
        int expectedArea = 6;
        int realArea = rectangle.getArea();
        Assert.assertEquals(expectedArea, realArea);
    }

3..被测试类存在难以实现的其他依赖

使用Mock框架,构造出虚拟的依赖,如有需要,可对虚拟依赖进行打桩以满足测试要求。

打桩:用来代替依赖的代码,或未实现的代码。对构造出的虚拟对象,定制其行为,以满足测试逻辑要求。

测试service层代码举例:

service层代码:需要依赖dao层类从数据库获取数据。

public class RectangleService {
    RectangleDao rectangleDao = new RectangleDao();//rectangleDao未开发完成
    
    public int getRectangleAreaById(String id) {
        MyRectangle myRectangle = rectangleDao.getRectangleById(id);
        return myRectangle.getArea();
    }
}

Dao层代码:dao层代码未开发或需要连接数据库,不好操作。

public class RectangleDao {
    public MyRectangle getRectangleById(String id) {
        //代码未开发
        return new MyRectangle();
    }
}

测试用例代码:mock dao层对象,并通过打桩方式定制其行为。

方式1:将mock的rectangleDao对象,通过java反射方式设置到rectangleService对象中


@RunWith(PowerMockRunner.class)
public class RectangleServiceTest {
    /**
     * 此处仅测试RectangleService类代码逻辑,因此该类的依赖需要mock出来,并打桩(自定义对象的行为)
     */
    @Test
    public void testRectangleService() throws Exception{
        //构造service内部依赖的rectangleDao对象,
        RectangleDao rectangleDao = PowerMockito.mock(RectangleDao.class);
        PowerMockito.when(rectangleDao.getRectangleById("1")).thenReturn(new MyRectangle(2,3));
        
        //通过反射的方式,将mock出来的rectangleDao配置到rectangleService中
        RectangleService rectangleService = new RectangleService();
        Field field = rectangleService.getClass().getDeclaredField("rectangleDao");
        field.setAccessible(true);
        field.set(rectangleService, rectangleDao);
        
        //构造期望数据,计算实际数据,比对两者
        MyRectangle myRectangle = new MyRectangle(2,3);
        int expectedArea = myRectangle.getArea();
        int actualArea = rectangleService.getRectangleAreaById("1");
        Assert.assertEquals(expectedArea, actualArea);
    }
}

方式2:将mock的rectangleDao对象,通过注解的方式设置到rectangleService对象中

Note:

  • @Mock注解:创建一个Mock对象。
  • @InjectMocks注解:创建一个实例对象,将其他用@Mock、@Spy注解创建的对象注入到用该实例中。
  • @Before注解:junit中的注解,表示每一个@Test注解测试用例执行前,都会执行一遍。
 /**
* 此处仅测试RectangleService类代码逻辑,因此该类的依赖需要mock出来,并打桩(自定义对象的行为)
*/
@RunWith(PowerMockRunner.class)
public class RectangleServiceTest {
    @InjectMocks  //将其他用@Mock(或@Spy)注解创建的对象设置到下面对象中
    RectangleService rectangleService;//创建bean(类似new RectangleService)
    @Mock
    RectangleDao rectangleDao;
    
    @Before
    public void setUp() {
        MockitoAnnotations.initMocks(this);//初始化上面@Mock和@InjectMocks标注对象
    }
    
    @Test
    public void testRectangleService() throws Exception{
        //打桩
        PowerMockito.when(rectangleDao.getRectangleById("1")).thenReturn(new MyRectangle(2,3));
        //构造期望数据,计算实际数据,比对两者
        MyRectangle myRectangle = new MyRectangle(2,3);
        int expectedArea = myRectangle.getArea();
        //调用实际数据并对比
        int actualArea = rectangleService.getRectangleAreaById("1");
        Assert.assertEquals(expectedArea, actualArea);
    }
}

显然,第2种方式更加方便,尤其是被测试类中依赖许多其他对象时,注解方式更加高效。

2.2 依赖框架的java代码

  • spring-boot框架中,类的依赖通过@Autowired注入,而非new创建,但两者本质一样。在springboot框架中,同样可以使用2.3中的方式进行单元测试。
  • 此外,在springboot框架中还可以使用spring-boot-test包进行单元测试。

1.被测试类不存在难以实现的其他依赖

可直接使用junit测试包对代码逻辑进行测试,参考2.1章节。

2.被测试类存在难以实现的其他依赖

方式1:类似2.1.3,直接使用powermock框架对代码进行测试

代码示例:

service层:依赖dao层方法。

和无框架代码区别在于,springboot框架内开发的bean都交给spring IOC容器管理,使用时直接注入而非new对象,但两者本质一样。

@Service
public class RectangleService {
    @Autowired
    RectangleDao rectangleDao;//rectangleDao未开发完成
    
    public int getRectangleAreaById(String id) {
        MyRectangle myRectangle = rectangleDao.getRectangleById(id);
        return myRectangle.getArea();
    }
}

dao层:和数据库交互。

@Component
public class RectangleDao {
    public MyRectangle getRectangleById(String id) {
        //代码未开发
        return new MyRectangle();
    }
}

测试用例:


@RunWith(PowerMockRunner.class)
public class RectangleServiceTest {
    @InjectMocks  //将其他用@Mock(或@Spy)注解创建的对象设置到下面对象中
    RectangleService rectangleService;//创建bean(类似new RectangleService)
    @Mock
    RectangleDao rectangleDao;
    
    @Before
    public void setUp() {
        MockitoAnnotations.initMocks(this);//初始化上面@Mock和@InjectMocks标注对象
    }
    
    @Test
    public void testRectangleService() throws Exception{
        //打桩
        PowerMockito.when(rectangleDao.getRectangleById("1")).thenReturn(new MyRectangle(2,3));
        //构造期望数据,计算实际数据,比对两者
        MyRectangle myRectangle = new MyRectangle(2,3);
        int expectedArea = myRectangle.getArea();
        //调用实际数据并对比
        int actualArea = rectangleService.getRectangleAreaById("1");
        Assert.assertEquals(expectedArea, actualArea);
    }
    
}

方式2:使用spring-boot-test框架对代码进行测试

代码示例:

使用springboot-test的注解。

@RunWith(SpringRunner.class)
@SpringBootTest
public class RectangleServiceTest2 {
    @Autowired  //注入spring IOC容器管理的bean
    RectangleService rectangleService;//创建bean(类似new RectangleService)
    @MockBean  //mock对象
    RectangleDao rectangleDao;
    
    @Test
    public void testRectangleService() throws Exception{
        //打桩
        Mockito.when(rectangleDao.getRectangleById("1")).thenReturn(new MyRectangle(2,3));
        //构造期望数据,计算实际数据,比对两者
        MyRectangle myRectangle = new MyRectangle(2,3);
        int expectedArea = myRectangle.getArea();
        //调用实际数据并对比
        int actualArea = rectangleService.getRectangleAreaById("1");
        Assert.assertEquals(expectedArea, actualArea);
    }
    
}

三、PowerMock框架核心方法

3.1 PowerMock创建mock对象

T PowerMock.mock(Class<T> type);//创建模拟对象,支持final和native方法

public static <T> void spy(Class<T> type);//创建真实对象

代码示例:

//方式1:注解
@Mock
RectangleDao rectangleDao;
//方式2:创建
RectangleDao rectangleDao = PowerMockito.mock(RectangleDao.class);

//方式1:注解
@Spy
RectangleDao rectangleDao;
//方式2:创建
RectangleDao rectangleDao = PowerMockito.spy(new RectangleDao);

3.2 对mock对象方法进行打桩

  • 当使用PowerMockito.whenNew方法时,必须加注解@PrepareForTest和@RunWith。注解@PrepareForTest里写的类是需要mock的new对象代码所在的类。
  • 当需要mock final方法的时候,必须加注解@PrepareForTest和@RunWith。注解@PrepareForTest里写的类是final方法所在的类。
  • 当需要mock静态方法的时候,必须加注解@PrepareForTest和@RunWith。注解@PrepareForTest里写的类是静态方法所在的类。
  • 当需要mock私有方法的时候, 只是需要加注解@PrepareForTest,注解里写的类是私有方法所在的类
  • 当需要mock系统类的静态方法的时候,必须加注解@PrepareForTest和@RunWith。注解里写的类是需要调用系统方法所在的类

(1)mock非final类(接口、普通类、虚基类)的非final方法:

先mock对象,然后对mock的对象进行打桩顶起方法行为,最后比对结果。

public class RectangleTest {
    @Test
    public void testObjectNormalMethod(){
        MyRectangle myRectangle = PowerMockito.mock(MyRectangle.class);
        PowerMockito.when(myRectangle.getArea()).thenReturn(6);

        int expectArea = 6;
        int actualArea = myRectangle.getArea();
        Assert.assertEquals(expectArea,actualArea);
    }
}

:其中when(...).thenReturn(...)表示,当对象

rectangle

调用

getArea(0)

方法,并且参数为

0

时,返回结果为

0

,这相当于定制了

mock

对象的行为结果.

(2)模拟final类或final方法:

final方法所在的类,需要通过@PrepareForTest注解设置,然后mock对象,再然后打桩(指定方法行为),最后对比。

    @Test
    @PrepareForTest(MyRectangle.class)
    public void testObjectFinalMethod(){
        MyRectangle myRectangle = PowerMockito.mock(MyRectangle.class);
        PowerMockito.when(myRectangle.getFinalArea()).thenReturn(6);

        int expectArea = 6;
        int actualArea = myRectangle.getFinalArea();
        Assert.assertEquals(expectArea,actualArea);
    }

(3)mockStatic方法

static方法所在的类,需要通过@PrepareForTest注解设置,mock静态方法所在的类,再然后打桩(指定方法行为),最后对比。

public static void mockStatic(Class<?> classToMock);//模拟类的静态方法

    @Test
    @PrepareForTest(MyRectangle.class)
    public void testObjectStaticMethod(){
        PowerMockito.mockStatic(AreaUtils.class);
        PowerMockito.when(AreaUtils.getStaticArea(new MyRectangle(2,3))).thenReturn(6);

        int expectArea = 6;
        int actualArea = AreaUtils.getStaticArea(new MyRectangle(2,3));
        Assert.assertEquals(expectArea,actualArea);
    }

(4)mock方法的参数

mock不好实际调用的类,然后打桩mock的参数对象的方法行为,最后对比结果。

AreaUtils类:

public class AreaUtils {
    public static int getStaticArea(MyRectangle rectangle){
        return rectangle.height * rectangle.width;
    }
    public boolean callArgumentInstance(File file) {
        return file.exists();
    }
}

测试代码:

    @Test
    public void testMockObject() {
        File file = PowerMockito.mock(File.class);//假设file对象不好实现,这里构造一个file对象
        AreaUtils areaUtils = new AreaUtils();

        PowerMockito.when(file.exists()).thenReturn(true);//定制file对象的方法行为:file.exists方法时,设置其返回值为true
        Assert.assertTrue(areaUtils.callArgumentInstance(file));//传入构造好的file对象。由于造的file对象的file.exists()方法返回值为true,因此调用demo.call方法返回的就是true
    }

打桩方法总结:

when().thenReturn()

when().thenThrow()

when().thenCallRealMethod()

when(file.exists()).thenThrow(Exception.class);

whenNew(File.class).withArguments("bbb").thenReturn(file);

标签: 单元测试

本文转载自: https://blog.csdn.net/jainszhang/article/details/124924284
版权归原作者 Mr. 阿柴 所有, 如有侵权,请联系我们删除。

“PowerMock测试框架详解”的评论:

还没有评论