Java单元测试技巧之PowerMock
阿里技术Alitech
2021年03月15日 17:30

前言

高德的技术大佬向老师在谈论方法论时说到:“复杂的问题要简单化,简单的问题要深入化。” 这句话让我感触颇深,这何尝不是一套编写代码的方法——把一个复杂逻辑拆分为许多简单逻辑,然后把每一个简单逻辑进行深入实现,最后把这些简单逻辑整合为复杂逻辑,总结为八字真言即是“化繁为简,由简入繁”。 编写Java单元测试用例,其实就是把“复杂的问题要简单化”——即把一段复杂的代码拆解成一系列简单的单元测试用例;写好Java单元测试用例,其实就是把“简单的问题要深入化”——即学习一套方法、总结一套模式并应用到实践中。这里,作者根据日常的工作经验,总结了一些Java单元测试技巧,以供大家交流和学习。

1. 准备环境

PowerMock是一个扩展了其它如EasyMock等mock框架的、功能更加强大的框架。PowerMock使用一个自定义类加载器和字节码操作来模拟静态方法、构造方法、final类和方法、私有方法、去除静态初始化器等等。

1.1. 引入PowerMock包

为了引入PowerMock包,需要在pom.xml文件中加入下列maven依赖:

1.2. 集成SpringMVC项目

在SpringMVC项目中,需要在pom.xml文件中加入JUnit的maven依赖:

1.3. 集成SpringBoot项目

在SpringBoot项目中,需要在pom.xml文件中加入JUnit的maven依赖:

1.4. 一个简单的测试用例

这里,用List举例,模拟一个不存在的列表,但是返回的列表大小为100。

2. mock语句

2.1. mock方法

声明: T PowerMockito.mock(Class clazz); 用途: 可以用于模拟指定类的对象实例。 当模拟非final类(接口、普通类、虚基类)的非final方法时,不必使用@RunWith和@PrepareForTest注解。当模拟final类或final方法时,必须使用@RunWith和@PrepareForTest注解。注解形如: @RunWith(PowerMockRunner.class) @PrepareForTest({TargetClass.class})

2.1.1. 模拟非final类普通方法

2.1.2. 模拟final类或final方法

2.2. mockStatic方法

声明: PowerMockito.mockStatic(Class clazz); 用途: 可以用于模拟类的静态方法,必须使用“@RunWith”和“@PrepareForTest”注解。

3. spy语句

如果一个对象,我们只希望模拟它的部分方法,而希望其它方法跟原来一样,可以使用PowerMockito.spy方法代替PowerMockito.mock方法。于是,通过when语句设置过的方法,调用的是模拟方法;而没有通过when语句设置的方法,调用的是原有方法。

3.1. spy类

声明: PowerMockito.spy(Class clazz); 用途: 用于模拟类的部分方法。 案例:

3.2. spy对象

声明: T PowerMockito.spy(T object); 用途: 用于模拟对象的部分方法。 案例:

4. when语句

4.1. when().thenReturn()模式

声明:

PowerMockito.when(mockObject.someMethod(someArgs)).thenReturn(expectedValue); PowerMockito.when(mockObject.someMethod(someArgs)).thenThrow(expectedThrowable); PowerMockito.when(mockObject.someMethod(someArgs)).thenAnswer(expectedAnswer); PowerMockito.when(mockObject.someMethod(someArgs)).thenCallRealMethod(); 用途: 用于模拟对象方法,先执行原始方法,再返回期望的值、异常、应答,或调用真实的方法。

4.1.1. 返回期望值

4.1.2. 返回期望异常

4.1.3. 返回期望应答

4.1.4. 调用真实方法

4.2. doReturn().when()模式

声明: PowerMockito.doReturn(expectedValue).when(mockObject).someMethod(someArgs); PowerMockito.doThrow(expectedThrowable).when(mockObject).someMethod(someArgs); PowerMockito.doAnswer(expectedAnswer).when(mockObject).someMethod(someArgs); PowerMockito.doNothing().when(mockObject).someMethod(someArgs); PowerMockito.doCallRealMethod().when(mockObject).someMethod(someArgs); 用途: 用于模拟对象方法,直接返回期望的值、异常、应答,或调用真实的方法,无需执行原始方法。 注意: 千万不要使用以下语法: PowerMockito.doReturn(expectedValue).when(mockObject.someMethod(someArgs)); PowerMockito.doThrow(expectedThrowable).when(mockObject.someMethod(someArgs)); PowerMockito.doAnswer(expectedAnswer).when(mockObject.someMethod(someArgs)); PowerMockito.doNothing().when(mockObject.someMethod(someArgs)); PowerMockito.doCallRealMethod().when(mockObject.someMethod(someArgs)); 虽然不会出现编译错误,但是在执行时会抛出UnfinishedStubbingException异常。

4.2.1. 返回期望值

4.2.2. 返回期望异常

4.2.3. 返回期望应答

4.2.4. 模拟无返回值

4.2.5. 调用真实方法

4.3. 两种模式的主要区别

两种模式都用于模拟对象方法,在mock实例下使用时,基本上是没有差别的。但是,在spy实例下使用时,when().thenReturn()模式会执行原方法,而doReturn().when()模式不会执行原方法。 测试服务类:

使用when().thenReturn()模式:

在测试过程中,将会打印出"调用获取用户数量方法&#​34;日志。

使用doReturn().when()模式:

在测试过程中,不会打印出"调用获取用户数量方法&#​34;日志。

4.4. whenNew模拟构造方法

声明:

PowerMockito.whenNew(MockClass.class).withNoArguments().thenReturn(expectedObject); PowerMockito.whenNew(MockClass.class).withArguments(someArgs).thenReturn(expectedObject); 用途: 用于模拟构造方法。 案例:

注意:需要加上注解@PrepareForTest({FileUtils.class}),否则模拟方法不生效。

5. 参数匹配器

在执行单元测试时,有时候并不关心传入的参数的值,可以使用参数匹配器。

5.1. 参数匹配器(any)

Mockito提供Mockito.anyInt()、Mockito.anyString、Mockito.any(Class clazz)等来表示任意值。

5.2. 参数匹配器(eq)

当我们使用参数匹配器时,所有参数都应使用匹配器。 如果要为某一参数指定特定值时,就需要使用Mockito.eq()方法。

5.3. 附加匹配器

Mockito的AdditionalMatchers类提供了一些很少使用的参数匹配器,我们可以进行参数大于(gt)、小于(lt)、大于等于(geq)、小于等于(leq)等比较操作,也可以进行参数与(and)、或(or)、非(not)等逻辑计算等。

6. verify语句

验证是确认在模拟过程中,被测试方法是否已按预期方式与其任何依赖方法进行了交互。

格式:

Mockito.verify(mockObject[,times(int)]).someMethod(somgArgs);

用途:

用于模拟对象方法,直接返回期望的值、异常、应答,或调用真实的方法,无需执行原始方法。

案例:

6.1. 验证调用方法

6.2. 验证调用次数

除times外,Mockito还支持atLeastOnce、atLeast、only、atMostOnce、atMost等次数验证器。

6.3. 验证调用顺序

6.4. 验证调用参数

6.5. 确保验证完毕

Mockito提供Mockito.verifyNoMoreInteractions方法,在所有验证方法之后可以使用此方法,以确保所有调用都得到验证。如果模拟对象上存在任何未验证的调用,将会抛出NoInteractionsWanted异常。

备注:Mockito.verifyZeroInteractions方法与Mockito.verifyNoMoreInteractions方法相同,但是目前已经被废弃。

6.6. 验证静态方法

Mockito没有静态方法的验证方法,但是PowerMock提供这方面的支持。

7. 私有属性

7.1. ReflectionTestUtils.setField方法

在用原生JUnit进行单元测试时,我们一般采用ReflectionTestUtils.setField方法设置私有属性值。

注意:在测试类中,UserService实例是通过@Autowired注解加载的,如果该实例已经被动态代理,ReflectionTestUtils.setField方法设置的是代理实例,从而导致设置不生效。

7.2. Whitebox.setInternalState方法

现在使用PowerMock进行单元测试时,可以采用Whitebox.setInternalState方法设置私有属性值。

注意:需要加上注解@RunWith(PowerMockRunner.class)。

完整版:https://developer.aliyun.com/article/782591?spm=5176.12901015.0.i12901015.32ce525cAH2q1C&groupCode=alitech


添加阿里妹官微(alimei6)备注【阿里技术】,即可领取Python、Java、数据库、运维等等阿里独家学习资料和福利!票圈还有更多阿里校招社招资讯、免费训练营和大佬干货直播等等等分享!不断更新中~阿里妹这里等你!