您现在的位置是:首页 >技术杂谈 >Java单元测试实战(一)基础网站首页技术杂谈
Java单元测试实战(一)基础
版权声明:本文为博主「SJMP1974」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
编辑:SJMP1974
原文出处链接:https://editor.csdn.net/md?not_checkout=1&articleId=130234294
参考:https://developer.aliyun.com/ebook/7895?spm=a2c6h.13066369.question.5.e953296fRPnbNA
文章目录
单元测试概述
什么是单元测试?
一般地,单元测试是针对一个类的一个公有方法的测试。
为什么要写单元测试?
- 验证代码逻辑,代码运行后是否能得到预期结果;
- 减少代码缺陷,只有“零件”没有问题,才能进一步地保障“机器运行的质量”;
- 便于多人协作,依赖的接口不一定被其他同学完成了,此时,通过Mock的方法可以完成自己的测试;
- 便于回归,每当需求完成后,可能影响其他模块逻辑,此时,单测可在一定程度上回归验证。
基础知识
Java 单元测试技巧之 PowerMock
什么是 PowerMock?
PowerMock 是一个扩展了其它如EasyMock 等mock 框架的、功能更加强大的框架。PowerMock 使用一个自定义类加载器和字节码操作来模拟静态方法、构造方法、final 类和方法、私有方法、去除静态初始化器等等。
如何使用 PowerMock?
引入依赖
引入 PowerMock 包,加入 pom 依赖:
在 SpringBoot 项目中,需要在 pom.xml 文件中加入JUnit 的 Maven 依赖:
单元测试Hello World :Mock
先感受下,后续展开介绍
声明:T PowerMockito.mock(Class clazz);
用途:可以用于模拟指定类的对象实例。
当模拟final 类、final 方法和静态方法时,必须使用 @RunWith 和 @PrepareForTest 注解。
@RunWith(PowerMockRunner.class)
@PrepareForTest({TargetClass.class})
如何模拟静态方法?
声明:PowerMockito.mockStatic(Class clazz);
spy 语句
如果只希望模拟对象的部分方法,而希望其它方法跟原来一样,可以使用 PowerMockito.spy 方法代替 PowerMockito.mock 方法。于是,通过 when 语句设置过的方法,调用的是模拟方法;而没有通过 when 语句设置的方法,调用的是原有方法。
spy 类
模拟类的部分方法
声明:PowerMockito.spy(Class clazz);
举例:
spy 对象
模拟对象的部分方法
声明:T PowerMockito.spy(T object);
when 语句
- when().thenReturn()模式
用于模拟对象方法,先执行原始方法,返回期望的值、异常、应答,或调用真实的方法。
PowerMockito.when(mockObject.someMethod(someArgs)).thenRetu
rn(expectedValue);
PowerMockito.when(mockObject.someMethod(someArgs)).thenThro
w(expectedThrowable);
PowerMockito.when(mockObject.someMethod(someArgs)).thenAnsw
er(expectedAnswer);
PowerMockito.when(mockObject.someMethod(someArgs)).thenCall
RealMethod();
- 返回期望值
- 返回期望异常
- 返回期望应答
- 调用真实方法
- doReturn().when()模式
用用于模拟对象方法,直接返回期望的值、异常、应答,或调用真实的方法,
无需执行原始方法。
PowerMockito.doReturn(expectedValue).when(mockObject).someM
ethod(someArgs);
PowerMockito.doThrow(expectedThrowable).when(mockObject).so
meMethod(someArgs);
PowerMockito.doAnswer(expectedAnswer).when(mockObject).some
Method(someArgs);
PowerMockito.doNothing().when(mockObject).someMethod(someAr
gs);
PowerMockito.doCallRealMethod().when(mockObject).someMethod
(someArgs);
- 返回期望值
- 返回期望异常
- 返回期望应答
- 模拟无返回值
- 调用真实方法
小结:when.thenReturn 和 doReturn.when 在 mock 实例下使用基本无差别,但在 spy 实例下使用,前者会执行原方法,后者不会。
参数匹配器
在执行单元测试时,有时候并不关心传入的参数的值,可以使用参数匹配器。
Mockito 提供Mockito.anyInt() 、Mockito.anyString 、Mockito.any(Class clazz)等来表示任意值。
当我们使用参数匹配器时,所有参数都应使用匹配器。 如果要为某一参数指定特定
值时,就需要使用Mockito.eq()方法。.
verify 语句
验证是确认在模拟过程中,被测试方法是否已按预期方式与其任何依赖方法进行了
交互。
格式:
Mockito.verify(mockObject[,times(int)]).someMethod(somgArgs);
- 验证调用方法
ListTest 方法是否已和 mockList.clear 方法进行了交互。
- 验证调用次数
除 times 外,Mockito 还支持 atLeastOnce、atLeast、only、atMostOnce、atMost 等次数验证器。
- 验证调用次数
- 验证调用参数
- 确保验证完毕
Mockito 提供 Mockito.verifyNoMoreInteractions 方法,在所有验证方法之后
可以使用此方法,以确保所有调用都得到验证。如果模拟对象上存在任何未验证的
调用,将会抛出 NoInteractionsWanted 异常。
说明:这里 mockedList.isEmpty 被调用,因此,应验证该方法是否与 testVerifyNoMoreInteractions 发生交互。
- 验证静态方法
Mockito 没有静态方法的验证方法,但是PowerMock 提供这方面的支持。
私有属性
- ReflectionTestUtils.setField 方法
在用原生JUnit进行单元测试时,我们一般采用ReflectionTestUtils.setField
方法设置私有属性值。
注意:在测试类中,UserService 实例是通过@Autowired 注解加载的,如果该实例已经被动态代理,ReflectionTestUtils.setField 方法设置的是代理实例,从而导致设置不生效。
- Whitebox.setInternalState 方法
现在使用PowerMock 进行单元测试时,可以采用Whitebox.setInternalState方法设置私有属性值。
注意:需要加上注解@RunWith(PowerMockRunner.class)。
私有方法
- 通过 when 实现
- 通过 stub
通过模拟方法stub(存根),也可以实现模拟私有方法。但是,只能模拟整个方法
的返回值,而不能模拟指定参数的返回值。
- 测试私有方法
- 验证私有方法
主要注解
- @RunWith 注解
@RunWith(PowerMockRunner.class)
指定JUnit 使用PowerMock 框架中的单元测试运行器。
- @PrepareForTest 注解
@PrepareForTest({ TargetClass.class })
当需要模拟final 类、final 方法或静态方法时,需要添加@PrepareForTest 注解,
并指定方法所在的类。如果需要指定多个类,在{}中添加多个类并用逗号隔开即可。
- @Mock 注解
@Mock注解创建了一个全部Mock的实例,所有属性和方法全被置空(0或者null)。
- @Spy 注解
@Spy 注解创建了一个没有Mock 的实例,所有成员方法都会按照原方法的逻辑执
行,直到被Mock 返回某个具体的值为止。
- @InjectMocks 注解
@InjectMocks 注解创建一个实例,这个实例可以调用真实代码的方法,其余用
@Mock或@Spy 注解创建的实例将被注入到用该实例中。
- @Captor 注解
@Captor 注解在字段级别创建参数捕获器。但是,在测试方法启动前,必须调用
MockitoAnnotations.openMocks(this)进行初始化。
@Service
public class UserService {
@Autowired
private UserDAO userDAO;
public void modifyUser(UserVO userVO) {
UserDO userDO = new UserDO();
BeanUtils.copyProperties(userVO, userDO);
userDAO.modify(userDO);
}
}
@RunWith(PowerMockRunner.class)
public class UserServiceTest {
@Mock
private UserDAO userDAO;
@InjectMocks
private UserService userService;
@Captor
private ArgumentCaptor<UserDO> argumentCaptor;
@Before
public void beforeTest() {
MockitoAnnotations.openMocks(this);
}
@Test
public void testCreateUser() {
UserVO userVO = new UserVO();
userVO.setId(1L);
userVO.setName("changyi");
userVO.setDesc("test user");
userService.modifyUser(userVO);
Mockito.verify(userDAO).modify(argumentCaptor.capture());
UserDO userDO = argumentCaptor.getValue();
Assert.assertNotNull("用户实例为空", userDO);
Assert.assertEquals("用户标识不相等", userVO.getId(),
userDO.getId());
Assert.assertEquals("用户名称不相等", userVO.getName(),
userDO.getName());
Assert.assertEquals("用户描述不相等", userVO.getDesc(),
userDO.getDesc());
}
}