单元测试之更强大的powermock

前面一篇说到了Mockito的各种功能,可以帮助我们在编写测试用例的时候模拟对象的各种行为,但是Mockito对于一些场景还是无法满足,比方说静态方法,私有方法(不过一般正常的单元测试很少去mock私有方法),构造方法等

github上关于mockito不支持的地方给了如下说明:

Do not mock types you don’t own
Don’t mock value objects
Don’t mock everything
Show some love with your tests

在FAQ中,也写了mockito的其他一些问题

Mockito 2.x specific limitations

  • Requires Java 6+
  • Cannot mock static methods
  • Cannot mock constructors
  • Cannot mock equals(), hashCode(). Firstly, you should not mock those methods. Secondly, Mockito defines and depends upon a specific implementation of these methods. Redefining them might break Mockito.
  • Mocking is only possible on VMs that are supported by Objenesis. Don’t worry, most VMs should work just fine.
  • Spying on real methods where real implementation references outer Class via OuterClass.this is impossible. Don’t * worry, this is extremely rare case.

Can I mock static methods?
No. Mockito prefers object orientation and dependency injection over static, procedural code that is hard to understand & change. If you deal with scary legacy code you can use JMockit or Powermock to mock static methods.

Can I mock private methods?
No. From the standpoint of testing… private methods don’t exist. More about private methods here.

Can I verify toString()?
No. You can stub it, though. Verification of toString() is not implemented mainly because:

When debugging, IDE calls toString() on objects to print local variables and their content, etc. After debugging, the verification of toString() will most likely fail.
toString() is used for logging or during string concatenation. Those invocations are usually irrelevant but they will change the outcome of verification.

对于上述Mockito不能实现的功能,PowerMock可以满足我们的需求。PowerMock 也是一个单元测试模拟框架,它是在其它单元测试模拟框架的基础上做出的扩展。通过提供定制的类加载器以及一些字节码篡改技巧的应用,PowerMock 实现了对静态方法、构造方法、私有方法以及 Final 方法的Mock支持等强大的功能。目前,PowerMock 仅支持 EasyMock 和 Mockito。

1.依赖jar包

maven项目需要引入如下jar:

<dependency>
    <groupId>org.powermock</groupId>
    <artifactId>powermock-module-junit4</artifactId>
    <version>1.7.4</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.powermock</groupId>
    <artifactId>powermock-api-mockito</artifactId>
    <version>1.7.4</version>
    <scope>test</scope>
</dependency>
2.mock静态方法
public class CommonUtils {

    public static String getUUID() {
        String uuid = UUID.randomUUID().toString();
        uuid = uuid.replace("-", "");
        return uuid;
    }
}
public class UUIDTest {

    public String getUUId(){
        return CommonUtils.getUUID();
    }
}

两个类,一个是静态工具类,一个是调用工具类的测试类
mock静态方法的时候就需要使用PowerMockRunner并且添加@PrepareForTest注解,还需要调用对应的mockStatic方法mock静态方法所在的类

RunWith(PowerMockRunner.class)
@PrepareForTest({CommonUtils.class})
public class CommonUtilsTest {

    @Test
    public void test() throws ParseException {
        PowerMockito.mockStatic(CommonUtils.class);
        PowerMockito.when(CommonUtils.getUUID()).thenReturn("12345678910111111");
        UUIDTest test = new UUIDTest();
        Assert.assertEquals("12345678910111111",test.getUUId());
    }
}
3.mock私有及final方法
public class TargetClass {
    
    public String mockPrivateFunc(int i) {
        return privateFunc(i + "");
    }

    private final String privateFunc(String i) {
        return "0";
    }
}
@RunWith(PowerMockRunner.class)
@PrepareForTest({TargetClass.class})
public class MockTest {

    @Test
    public void testMockPrivateFunc() throws Exception {
        TargetClass targetClass = PowerMockito.spy(new TargetClass());
        PowerMockito.when(targetClass,"privateFunc",anyString()).thenReturn("test");
        String realResult = targetClass.mockPrivateFunc(1);
        Assert.assertEquals("test", realResult);
    }
}

4.mock构造方法
public class User {

    private String username;
    private String password;

    public User(String username, String password) {
        this.username = username;
        this.password = password;
    }

    @Override
    public String toString() {
        return username+","+password;
    }
}
@RunWith(PowerMockRunner.class)
//注意下这里添加的是调用构造方法的类而不是User类
@PrepareForTest({UserService.class})
public class UserServiceTest {

    @Test
    public void saveUser() throws Exception {
        String username = "mock姓名";
        String password = "aaa";
        User user = new User(username, password);
        UserService userService = new UserService();
        PowerMockito.whenNew(User.class).withAnyArguments().thenReturn(user);
        userService.saveUser("真实姓名","123");
    }

总结下之里的规律:
如果需要使用PowerMock来mock构造方法,私有方法,final方法和静态方法,那么都需要使用PowerMockRunner和
@PrepareForTest注解

  • 当使用mock构造方法时,注解@PrepareForTest里写的类是需要mock的新对象生成的代码所在的类。
  • 当需要mock系统类的静态方法的时候,注解里写的类是需要调用系统方法所在的类
  • 当需要mock final方法,静态方法,私有方法的时候,注解@PrepareForTest里写的类是对应方法所在的类
5.Field

@InjectMocks和@Mock注解配合使用可以帮我们做自动注入,在编写单元测试时也可以使用SpringJUnit4ClassRunner来帮助我们做一些属性的注入和自动装配。
但是这样在单机环境下由于spring容器在启动的时候会自动完成很多初始化工作,一来比较耗时,二来会去连接一些其他中间件比方说配置中心等,单机下就会出现异常

那么我们就需要PowerMock的field方法来帮助我们做一些装配的工作

        OperateButtonService operateButtonService = new OperateButtonService();
        Map<String, AbstractGetOperateButton> operateButtonMap = new HashMap<>();
        operateButtonMap.put("CALL_HOTEL", callHotel); 
        operateButtonMap.put("COMMENT", comment);
        operateButtonMap.put("RESERVE_AGAIN", reserveAgain);
        PowerMockito.field(OperateButtonService.class, "operateButtonMap").set(operateButtonService, operateButtonMap);

对operateButtonService的operateButtonMap字段进行赋值,底层原理也是反射,不过powermock帮我们封装了下,更加容易使用,对于私有变量和静态变量都可以进行赋值(常量不行)

6.mock静态void方法 (2020/5/15补充)

使用PowerMock mock静态void方法,并且通过VerifyStatic验证调用次数

public class CommonUtils {
    public static void log(){
        System.out.println("日志记录");
    }
}
@RunWith(PowerMockRunner.class)
@PrepareForTest({CommonUtils.class})
public class CommonUtilsTest {

    @Test
    public void testStaticVoidMethod() throws ParseException {
        PowerMockito.mockStatic(CommonUtils.class);
        PowerMockito.doNothing().when(CommonUtils.class);
        //这里不是对静态方法的调用,而是指定了Stub的void方法
        CommonUtils.log();

        //这里才是对静态方法的调用
        CommonUtils.log();
        CommonUtils.log();

        //验证的时候和上面同理
        PowerMockito.verifyStatic(CommonUtils.class, Mockito.times(2));
        CommonUtils.log();

    }
}

参考链接:
https://stackoverflow.com/questions/18466198/how-to-verify-static-void-method-has-been-called-with-power-mockito

7.其他注解的功能

@PowerMockRunnerDelegate

如果在使用了PowerMockRunner之后还想使用Spring容器的功能,那么我们就需要spring的runner

@PowerMockRunnerDelegate(SpringJUnit4ClassRunner.class)

powermock使用了自定义的classloader来解决mock静态方法与私有方法的问题,因此其会为加了PrepareForTest注解的类生成对应的classloader来加载用到的类,这样就可能会导致其与系统的classloader加载了相同的类,导致类型转换失败,我们可以使用@PowerMockIgnore注解告诉powermock放弃加载指定的这些类

@PowerMockIgnore({"javax.management.*", "javax.net.ssl.*"})

这也是powermock2.0.0与1.x版本重大不一样的地方

@SuppressStaticInitializationFor

在单机环境下(无法使用任何外部的资源),因为代码中我们可能会在static静态块或者常量里做一些初始化的操作,比方说提前生成Redis操作的管理类等,此时我们就需要避免这些初始化操作,因为尝试调用其他服务资源都会失败

@SuppressStaticInitializationFor(“com.chenpp.RedisManager”)
忽略指定类的静态初始化, 包括static{}静态代码块和static变量的初始化

PowerMock简单原理
@RunWith(PowerMockRunner.class)
public class UserServiceTest {

    @Test
    @PrepareForTest({UserService.class})
    public void testSaveUser() throws Exception {
        String username = "mock姓名";
        String password = "aaa";
        User user = new User(username, password);
        Long current = System.currentTimeMillis();
        UserService userService = new UserService();
        PowerMockito.whenNew(User.class).withAnyArguments().thenReturn(user);
        userService.saveUser("真实姓名", "123");
        System.out.println("testSaveUser:" + userService.getClass().getClassLoader());
        System.out.println("testSaveUser:" + user.getClass().getClassLoader());
        System.out.println("testSaveUser:" + System.class.getClassLoader());
    }

    @Test
    public void testUser() throws Exception {
        UserService userService = Mockito.mock(UserService.class, RETURNS_DEEP_STUBS);
        Mockito.when(userService.getUser().show()).thenReturn("mock test");
        Assert.assertEquals("mock test", userService.getUser().show());
        System.out.println("testUser:" + userService.getClass().getClassLoader());
        System.out.println("testUser:" + User.class.getClassLoader());
    }
}

在这里插入图片描述
在这里插入图片描述

@RunWith(MockitoJUnitRunner.class)
public class UserServiceTest {
    @Test
    public void testUser() throws Exception {
        UserService userService = Mockito.mock(UserService.class, RETURNS_DEEP_STUBS);
        Mockito.when(userService.getUser().show()).thenReturn("mock test");
        Assert.assertEquals("mock test", userService.getUser().show());
        System.out.println("testUser:" + userService.getClass().getClassLoader());
        System.out.println("testUser:" + User.class.getClassLoader());
    }
}

在这里插入图片描述
当在某个测试类上使用PowerMockRunner,那么在运行测试用例时,会创建一个新的org.powermock.core.classloader.MockClassLoader类加载器,然后使用该类加载器加载测试用例使用到的类(系统类除外)

PowerMock会根据你的mock要求,去修改写在注解@PrepareForTest里的class文件(当前测试类会自动加入注解中),以满足特殊的mock需求。例如:去除final方法的final标识,在静态方法的最前面加入自己的虚拟实现等。

如果需要mock的是系统类的final方法和静态方法,PowerMock不会直接修改系统类的class文件,而是修改调用系统类的class文件,以满足mock需求。

  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值