单元测试之Power Mock

一、简介

  • EasyMock、Mockito、jMock(单元测试模拟框架)
    在有这些模拟框架之前,程序员为了编写某一个函数的单元测试,必须进行十分繁琐的初始化工作,以确保调用的接口或编写的代码得到预期的结果。单元测试模拟框架极大的简化了单元测试的编写过程,在被测试代码需要调用某些接口的时候,直接模拟一个假的接口,并任意指定该接口的行为。这样就可以大大的提高单元测试的效率以及单元测试代码的可读性。
    **缺点:**都不可以实现对静态函数、构造函数、私有函数、Final函数以及系统函数的模拟,但是这些方法往往是我们在大型系统中需要的功能
  • Power Mock
    PowerMock 是在EasyMock以及Mockito基础上的扩展,通过定制类加载等技术,PowerMock实现了之前提到的所有模拟功能,使其成为大型系统上单元测试中的必备工具。
  • Mock
    Mock的意思是模拟,就是模拟接口返回的信息,用已有的信息替换它需要返回的信息,从而实现对上级模块的测试、

二、依赖

<properties>
        <powermock.version>1.6.6</powermock.version>
</properties>
<dependencies>
		<dependency>
			    <groupId>org.powermock</groupId>
			    <artifactId>powermock-module-junit4</artifactId>
			    <version>${powermock.version}</version>
			    <scope>test</scope>
		</dependency>
		<dependency>
			    <groupId>org.powermock</groupId>
			    <artifactId>powermock-api-mockito</artifactId>
			    <version>${powermock.version}</version>
			    <scope>test</scope>
		</dependency>
</dependencies>

三、相关注解

@RunWith(PowerMockRunner.class)
@PrepareForTest({UserController.class, CommonUtils.class})
@PowerMockIgnore("javax.management.*")
public class UserController {
    @Mock
    private UserService userService;
    @InjectMocks
    private  UserController userController = new UserController();
}
  • @RunWith(PowerMockRunner.class)
    表明用PowerMockerRunner来测试用例,否则无法使用PowerMock
  • @PrepareForTest({UserController.class, CommonUtils.class})
    所有需要测试的类,列在此处,以逗号分隔;在运行测试用例时,会创建一个新的org.powermock.core.classloader.MockClassLoader实例,然后加载该测试用例使用到的类
  • @PowerMockIgnore(“javax.management.*”)
    为了解决使用powermock后,提示classloader错误
  • @Mock
    注解修饰会mock出来一个userService对象
  • @InjectMocks
    注解会主动将已存在的mock对象注入到bean中,按名称注入,这个注解修饰在我们需要测试的类上。必须要手动new一个实例,不然单元测试会有问题。

四、使用(示例代码在文末)

  • PowerMockito.mock():
    指定需要mock的类型类(接口或者实现类),生成Mock类,其中所有的方法都不是真实的方法,访问对象方法不会执行具体逻辑,而且返回值都是NULL或者Empty。
    使用打桩返回给定数据:PowerMockito.when().thenReturn()方法
    若要执行具体逻辑:PowerMockito.when().thenCallRealMethod()方法
  • PowerMockito.spy():
    spy机制可以监视一个真实对象,对其进行方法调用会执行真实逻辑;
    spy也可以打桩指定的方法。
    spy中使用都Return…when打桩,不会执行具体逻辑
    spy中使用when…thenReturn打桩,会执行具体逻辑
  • mock有返回值的普通方法
@Test
 public void test_addUser(){
     User user = new User();
     PowerMockito.when(userService.addUser(user)).thenReturn(1);
     boolean result = userController.addUser(user);
     Assert.assertEquals(result, true);
 }

注:userService.addUser()和userController.addUser()所使用的参数值必须保持一致

  • mock无返回值的普通方法
    方法一:
 @Test
 public void test_initUser() throws Exception {
     User user = new User();
     user.setId(111);
     PowerMockito.doNothing().when(userService, "initUser", user);
     User result = userController.initUser(user);
     Assert.assertEquals(result.getId(), 111);
 }

方法二:

@Test
public void test_initUser1() throws Exception {
    User user = new User();
    user.setId(111);
    PowerMockito.doNothing().when(userService).initUser(user);
    User result = userController.initUser(user);
    Assert.assertEquals(result.getId(), 111);
}
  • mock抛异常
@Test
public void test_delUser() throws Exception {
    PowerMockito.when(userService.delUser(1)).thenThrow(new Exception("删除失败"));
    boolean result = userController.delUser(1);
    Assert.assertEquals(result, false);
}

注:thenThrow()中抛出delUser()方法抛出的异常或其子异常

  • mock静态方法
@Test
public void test_append() {
     PowerMockito.mockStatic(CommonUtils.class);
     PowerMockito.when(CommonUtils.isNotEmpty("World")).thenReturn(false);
     String result = userController.append("World");
     Assert.assertEquals(result, "Hello");
 }

注:需要在@PrepareForTest注解中加上CommonUtils.class

  • mock返回值为void的static方法
    方法一:
@Test
public void test_getFile() throws Exception {
   String filePath = "xiaofeng";
    PowerMockito.mockStatic(CommonUtils.class);
    PowerMockito.doNothing().when(CommonUtils.class, "validFilePath", filePath);
    File result = userController.getFile(filePath);
    Assert.assertEquals(result.getName(), filePath);
}

方法二:

@Test
public void test_getFile1() throws Exception {
    String filePath = "xiaofeng";
    PowerMockito.mockStatic(CommonUtils.class);
    PowerMockito.doNothing().when(CommonUtils.class);
    CommonUtils.validFilePath(filePath);
    File result = userController.getFile(filePath);
    Assert.assertEquals(result.getName(), filePath);
}
  • mock私有方法
    方法一:
@Test
public void test_modUser() throws Exception {
   User user = new User();
    PowerMockito.when(userService.modUser(user)).thenReturn(1);
    UserController controller = PowerMockito.mock(UserController.class);
    // 给没有set方法的私有属性赋值
    Whitebox.setInternalState(controller, "userService", userService);
    // 因为要测试的是modUser()方法,所以调用这个方法时,应调用真实方法,并非mock掉的方法
    PowerMockito.when(controller.modUser(user)).thenCallRealMethod();
    // 在modUser()方法中会调用verifyMod()这个私有方法,所以将其mock掉
    PowerMockito.when(controller, "verifyMod", 1).thenReturn(false);
    boolean result = controller.modUser(user);
    Assert.assertEquals(result, false);
}

注:此处的controller是mock出来的,不是UserControllerTest类中的成员变量userController(不会执行verifyMod方法,直接返回模拟值)
方法二:

@Test
public void test_modUser1() throws Exception {
    User user = new User();
    PowerMockito.when(userService.modUser(user)).thenReturn(1);
    // 对userController进行监视
    UserController controller = PowerMockito.spy(userController);
    // 当userController的verifyMod被执行时,将被mock掉
    PowerMockito.when(controller, "verifyMod", 1).thenReturn(false);
    boolean result = controller.modUser(user);
    Assert.assertEquals(result, false);
}

注:spy方法可以避免执行被测类中的成员函数,即mock掉不想被执行的私有方法(会执行verifyMod方法,但是会返回模拟值)

  • 测试私有方法(注意:是测试,不是mock)
    方法一:
@Test
public void test_verifyMod() throws Exception {
   // 获取method方法
    Method method = PowerMockito.method(UserController.class, "verifyMod", Integer.class);
    // 调用Method的invoke方法来执行
    boolean result = (boolean) method.invoke(userController, 1);
    Assert.assertEquals(result, true);
}

方法二:

@Test
public void test_verifyMod1() throws Exception {
   // 直接通过Whitebox来执行
    boolean result = Whitebox.invokeMethod(userController, "verifyMod", 1);
    Assert.assertEquals(result, true);
}
  • mock新建对象
@Test
public void test_getUser() throws Exception {
   User user = new User();
    user.setName("大风");
    PowerMockito.whenNew(User.class).withNoArguments().thenReturn(user);
    User result = userController.getUser();
    Assert.assertEquals(result.getName(), "大风");
}
  • mock同一方法,返回不同的值
@Test
public void test_getString() throws Exception {
    UserController controller = PowerMockito.spy(userController);
    PowerMockito.when(controller, "getFlag").thenReturn(true, false);
    String result = controller.getString("Hello");
    Assert.assertEquals(result, "Hello");
}
  • mock final方法
    final与普通方法一样mock,但是需要将其所在class添加到@PrepareForTest注解中
  • 参数的模糊匹配
    org.mockito.Matchers类中,提供了很多any*的方法,如anyObject()、anyString()…我们可以使用这些方法去避免构建那些难以模拟的输入参数
    注:如果对某一个参数使用了Matcher,那么这个方法的所有其它参数也必须使用Matcher,否则将会报错。

五、示例代码

package com.example.pattren.junitTest;

public class User {
    private int id;
    private String name;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
package com.example.pattren.junitTest;

public interface UserService {
    int addUser(User user);

    void initUser(User user);

    int delUser(int id) throws Exception;

    int modUser(User user);
}
package com.example.pattren.junitTest;

import java.io.File;
import java.io.IOException;
import java.net.URLDecoder;
import java.text.Normalizer;

public class CommonUtils {
    public static boolean isEmpty(CharSequence cs) {
        return cs == null || cs.length() == 0;
    }

    public static boolean isNotEmpty(CharSequence cs) {
        return !isEmpty(cs);
    }

    public static void validFilePath(String filePath) throws IOException {
        if (CommonUtils.isEmpty(filePath)) {
            throw new IOException("the file path is empty");
        }
        filePath = Normalizer.normalize(filePath, Normalizer.Form.NFKC);
        File file = new File(filePath);
        String decode = URLDecoder.decode(file.getCanonicalPath(), "UTF-8");
        file = new File(decode);
        if (!file.exists()) {
            throw new IOException("the file path is not exists");
        }
    }
}
package com.example.pattren.junitTest;

import java.io.File;
import java.io.IOException;

public class UserController {
    private UserService userService;

    public boolean addUser(User user) {
        int i = userService.addUser(user);
        if (i <= 0) {
            return false;
        } else {
            return true;
        }
    }

    public User initUser(User user) {
        userService.initUser(user);
        return user;
    }

    public boolean delUser(int id) {
        try {
            userService.delUser(id);
            return true;
        } catch (Exception e) {
            return false;
        }
    }

    public String append(String param) {
        String str = "Hello";
        if (CommonUtils.isNotEmpty(param)) {
            return str + param;
        } else {
            return str;
        }
    }

    public File getFile(String filePath) throws IOException {
        CommonUtils.validFilePath(filePath);
        return new File(filePath);
    }

    public boolean modUser(User user) {
        int flag = userService.modUser(user);
        return verifyMod(flag);
    }

    private boolean verifyMod(Integer flag) {
        if (flag > 0) {
            return true;
        }
        return false;
    }

    public User getUser() {
        User user = new User();
        return user;
    }

    public String getString(String str) {
        StringBuffer result = new StringBuffer();
        while (this.getFlag()) {
            result.append(str);
        }
        return result.toString();
    }

    private boolean getFlag() {
        return true;
    }
}
package com.example.pattren.junitTest;

import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PowerMockIgnore;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import org.powermock.reflect.Whitebox;

import java.io.File;
import java.lang.reflect.Method;

@RunWith(PowerMockRunner.class)
@PrepareForTest({UserController.class, CommonUtils.class})
@PowerMockIgnore("javax.management.*")
public class UserControllerTest {
    @Mock
    private UserService userService;
    @InjectMocks
    private UserController userController = new UserController();

    /**
     * mock有返回值的普通方法
     */
    @Test
    public void test_addUser() {
        User user = new User();
        PowerMockito.when(userService.addUser(user)).thenReturn(1);
        boolean result = userController.addUser(user);
        Assert.assertEquals(result, true);
    }

    /**
     * mock无返回值的普通方法    (方法一)
     */
    @Test
    public void test_initUser() throws Exception {
        User user = new User();
        user.setId(111);
        PowerMockito.doNothing().when(userService, "initUser", user);
        User result = userController.initUser(user);
        Assert.assertEquals(result.getId(), 111);
    }

    /**
     * mock有无返回值的普通方法    (方法二)
     */
    @Test
    public void test_initUser1() throws Exception {
        User user = new User();
        user.setId(111);
        PowerMockito.doNothing().when(userService).initUser(user);
        User result = userController.initUser(user);
        Assert.assertEquals(result.getId(), 111);
    }

    /**
     * mock抛异常
     */
    @Test
    public void test_delUser() throws Exception {
        PowerMockito.when(userService.delUser(1)).thenThrow(new Exception("删除失败"));
        boolean result = userController.delUser(1);
        Assert.assertEquals(result, false);
    }

    /**
     * mock静态方法
     */
    @Test
    public void test_append() {
        PowerMockito.mockStatic(CommonUtils.class);
        PowerMockito.when(CommonUtils.isNotEmpty("World")).thenReturn(false);
        String result = userController.append("World");
        Assert.assertEquals(result, "Hello");
    }

    /**
     * mock返回值为void的static方法     (方法一)
     */
    @Test
    public void test_getFile() throws Exception {
        String filePath = "xiaofeng";
        PowerMockito.mockStatic(CommonUtils.class);
        PowerMockito.doNothing().when(CommonUtils.class, "validFilePath", filePath);
        File result = userController.getFile(filePath);
        Assert.assertEquals(result.getName(), filePath);
    }

    /**
     * mock返回值为void的static方法     (方法二)
     */
    @Test
    public void test_getFile1() throws Exception {
        String filePath = "xiaofeng";
        PowerMockito.mockStatic(CommonUtils.class);
        PowerMockito.doNothing().when(CommonUtils.class);
        CommonUtils.validFilePath(filePath);
        File result = userController.getFile(filePath);
        Assert.assertEquals(result.getName(), filePath);
    }

    /**
     * mock私有方法     (方法一)
     */
    @Test
    public void test_modUser() throws Exception {
        User user = new User();
        PowerMockito.when(userService.modUser(user)).thenReturn(1);
        UserController controller = PowerMockito.mock(UserController.class);
        // 给没有set方法的私有属性赋值
        Whitebox.setInternalState(controller, "userService", userService);
        // 因为要测试的是modUser()方法,所以调用这个方法时,应调用真实方法,并非mock掉的方法
        PowerMockito.when(controller.modUser(user)).thenCallRealMethod();
        // 在modUser()方法中会调用verifyMod()这个私有方法,所以将其mock掉
        PowerMockito.when(controller, "verifyMod", 1).thenReturn(false);
        boolean result = controller.modUser(user);
        Assert.assertEquals(result, false);
    }

    /**
     * mock私有方法     (方法二)
     */
    @Test
    public void test_modUser1() throws Exception {
        User user = new User();
        PowerMockito.when(userService.modUser(user)).thenReturn(1);
        // 对userController进行监视
        UserController controller = PowerMockito.spy(userController);
        // 当userController的verifyMod被执行时,将被mock掉
        PowerMockito.when(controller, "verifyMod", 1).thenReturn(false);
        boolean result = controller.modUser(user);
        Assert.assertEquals(result, false);
    }

    /**
     * 测试私有方法(注意:是测试,不是mock)     (方法一)
     */
    @Test
    public void test_verifyMod() throws Exception {
        // 获取method方法
        Method method = PowerMockito.method(UserController.class, "verifyMod", Integer.class);
        // 调用Method的invoke方法来执行
        boolean result = (boolean) method.invoke(userController, 1);
        Assert.assertEquals(result, true);
    }

    /**
     * 测试私有方法(注意:是测试,不是mock)     (方法二)
     */
    @Test
    public void test_verifyMod1() throws Exception {
        // 直接通过Whitebox来执行
        boolean result = Whitebox.invokeMethod(userController, "verifyMod", 1);
        Assert.assertEquals(result, true);
    }

    /**
     * mock新建对象
     */
    @Test
    public void test_getUser() throws Exception {
        User user = new User();
        user.setName("大风");
        PowerMockito.whenNew(User.class).withNoArguments().thenReturn(user);
        User result = userController.getUser();
        Assert.assertEquals(result.getName(), "大风");
    }

    /**
     * mock同一方法,返回不同的值
     */
    @Test
    public void test_getString() throws Exception {
        UserController controller = PowerMockito.spy(userController);
        PowerMockito.when(controller, "getFlag").thenReturn(true, false);
        String result = controller.getString("Hello");
        Assert.assertEquals(result, "Hello");
    }
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值