您现在的位置是:首页 >技术杂谈 >Java单元测试实战(一)基础网站首页技术杂谈

Java单元测试实战(一)基础

SJMP1974 2023-05-25 08:00:02
简介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 依赖:
image.png

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

单元测试Hello World :Mock

先感受下,后续展开介绍
image.png

声明:T PowerMockito.mock(Class clazz);

用途:可以用于模拟指定类的对象实例。

当模拟final 类、final 方法和静态方法时,必须使用 @RunWith 和 @PrepareForTest 注解。

@RunWith(PowerMockRunner.class)
@PrepareForTest({TargetClass.class})

如何模拟静态方法?

声明:PowerMockito.mockStatic(Class clazz);
image.png

spy 语句

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

spy 类

模拟类的部分方法

声明:PowerMockito.spy(Class clazz);
举例:
image.png

spy 对象

模拟对象的部分方法

声明:T PowerMockito.spy(T object);
image.png

when 语句

  1. 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();
  • 返回期望值

image.png

  • 返回期望异常

image.png

  • 返回期望应答

image.png

  • 调用真实方法

image.png

  1. 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);
  • 返回期望值

image.png

  • 返回期望异常

image.png

  • 返回期望应答

image.png

  • 模拟无返回值

image.png

  • 调用真实方法

image.png

小结:when.thenReturn 和 doReturn.when 在 mock 实例下使用基本无差别,但在 spy 实例下使用,前者会执行原方法,后者不会。

参数匹配器

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

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

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

verify 语句

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

格式:

Mockito.verify(mockObject[,times(int)]).someMethod(somgArgs);
  1. 验证调用方法

ListTest 方法是否已和 mockList.clear 方法进行了交互。
image.png

  1. 验证调用次数

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

  1. 验证调用次数

image.png

  1. 验证调用参数

image.png

  1. 确保验证完毕

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

image.png

说明:这里 mockedList.isEmpty 被调用,因此,应验证该方法是否与 testVerifyNoMoreInteractions 发生交互。

  1. 验证静态方法

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

image.png

私有属性

  1. ReflectionTestUtils.setField 方法

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

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

  1. Whitebox.setInternalState 方法

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

image.png

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

私有方法

  1. 通过 when 实现

image.png

  1. 通过 stub

通过模拟方法stub(存根),也可以实现模拟私有方法。但是,只能模拟整个方法
的返回值,而不能模拟指定参数的返回值。

image.png

  1. 测试私有方法

image.png

  1. 验证私有方法

image.png

主要注解

  1. @RunWith 注解

@RunWith(PowerMockRunner.class)
指定JUnit 使用PowerMock 框架中的单元测试运行器。

  1. @PrepareForTest 注解

@PrepareForTest({ TargetClass.class })
当需要模拟final 类、final 方法或静态方法时,需要添加@PrepareForTest 注解,
并指定方法所在的类。如果需要指定多个类,在{}中添加多个类并用逗号隔开即可。

  1. @Mock 注解

@Mock注解创建了一个全部Mock的实例,所有属性和方法全被置空(0或者null)。

  1. @Spy 注解

@Spy 注解创建了一个没有Mock 的实例,所有成员方法都会按照原方法的逻辑执
行,直到被Mock 返回某个具体的值为止。

  1. @InjectMocks 注解

@InjectMocks 注解创建一个实例,这个实例可以调用真实代码的方法,其余用
@Mock或@Spy 注解创建的实例将被注入到用该实例中。

  1. @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());
	}
}
风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。