版权声明:本文为博主「SJMP1974」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 编辑:SJMP1974 原文出处链接:https://editor.csdn.net/md/?not_checkout=1 参考:https://developer.aliyun.com/ebook/7895?spm=a2c6h.13066369.question.5.e953296fRPnbNA

文章目录

测试框架简介编写测试用例引入依赖单元测试案例***流程详细介绍定义被测对象模拟依赖对象注入依赖对象模拟依赖对象调用被测方法验证依赖方法验证数据对象验证依赖对象

测试框架简介

Mockito 是一个单元测试模拟框架,可以让你写出优雅、简洁的单元测试代码。 Mockito 采用了模拟技术,模拟了一些在应用中依赖的复杂对象,从而把测试对象 和依赖对象隔离开来。

编写测试用例

引入依赖

为了引入Mockito 和PowerMock 包,需要在maven 项目的pom.xml 文件中加入 以下包依赖:

其中,powermock.version 为2.0.9,为当前的最新版本,可根据实际情况修改。在 PowerMock包中,已经包含了对应的Mockito和JUnit包,所以无需单独引入Mockito 和JUnit 包。

单元测试案例***

一个有依赖的单元测试

定义对象:定义测试对象,模拟依赖对象、注入依赖对象;模拟方法:模拟参数或返回值、模拟依赖方法;调用方法:传入参数对象、调用测试方法、验证返回值或异常;验证方法:验证依赖方法、验证方法参数、验证依赖对象。

业务代码(可以先大致看下):

/**

* 用户服务类

*/

@Service

public class UserService {

/** 定义依赖对象 */

/** 用户DAO */

@Autowired

private UserDAO userDAO;

/** 标识生成器 */

@Autowired

private IdGenerator idGenerator;

/** 定义依赖参数 */

/** 可以修改 */

@Value("${userService.canModify}")

private Boolean canModify;

/**

* 保存用户

*

* @param userSave 用户保存

* @return 用户标识

*/

public Long saveUser(UserVO userSave) {

// 获取用户标识

Long userId = userDAO.getIdByName(userSave.getName());

// 根据存在处理

// 根据存在处理: 不存在则创建

if (Objects.isNull(userId)) {

userId = idGenerator.next();

UserDO userCreate = new UserDO();

userCreate.setId(userId);

userCreate.setName(userSave.getName());

userCreate.setDescription(userSave.getDescription());

userDAO.create(userCreate);

}

// 根据存在处理: 已存在可修改

else if (Boolean.TRUE.equals(canModify)) {

UserDO userModify = new UserDO();

userModify.setId(userId);

userModify.setName(userSave.getName());

userModify.setDescription(userSave.getDescription());

userDAO.modify(userModify);

}

// 根据存在处理: 已存在禁修改

else {

throw new UnsupportedOperationException("不支持修改");

}

// 返回用户标识

return userId;

}

}

对应的单元测试用例: 代码中的第一个单元测试方法重点看!!!

/**

* 用户服务测试类

*

*/

@RunWith(MockitoJUnitRunner.class)

public class UserServiceTest {

/** 1.1 定义测试对象 */

/** 1.3 InjectMocks 和 Mock 配合可以将 userDAO 注入 userService

用户服务 */

@InjectMocks

private UserService userService;

/** 1.2 模拟依赖对象 */

/** 用户DAO */

@Mock

private UserDAO userDAO;

/** 定义静态常量 */

/** 资源路径 */

private static final String RESOURCE_PATH ="testUserService/";

/** 标识生成器 */

@Mock

private IdGenerator idGenerator;

/**

* 在测试之前

*/

@Before

public void beforeTest() {

Whitebox.setInternalState(userService, "canModify",

Boolean.TRUE);

}

/**

* 测试: 保存用户-创建

*/

@Test

public void testSaveUserWithCreate() {

// 2.1 模拟依赖方法

// 模拟依赖方法: userDAO.getIdByName

Mockito.doReturn(null).when(userDAO).getIdByName(Mockito.anyStri

ng());

// 2.1 模拟依赖方法: idGenerator.next

Long userId = 123L;

Mockito.doReturn(userId).when(idGenerator).next();

// 3.1 调用测试方法

String path = RESOURCE_PATH + "testSaveUserWithCreate/";

String text =

ResourceHelper.getResourceAsString(getClass(), path +

"userSave.json");

UserSaveVO userSave = JSON.parseObject(text,

UserSaveVO.class);

// 3.2 验证返回值或异常

Assert.assertEquals("用户标识不一致", userId,

userService.saveUser(userSave));

// 4.1 验证依赖方法

// 4.1 验证依赖方法: userDAO.getIdByName

Mockito.verify(userDAO).getIdByName(userSave.getName());

// 4.1 验证依赖方法: idGenerator.next

Mockito.verify(idGenerator).next();

// 4.2 验证方法参数: userDAO.create

ArgumentCaptor userCreateCaptor =

ArgumentCaptor.forClass(UserDO.class);

Mockito.verify(userDAO).create(userCreateCaptor.capture());

text = ResourceHelper.getResourceAsString(getClass(),

path + "userCreate.json");

Assert.assertEquals("用户创建不一致", text,

JSON.toJSONString(userCreateCaptor.getValue()));

// 4.3 验证依赖对象,确保所有验证均已覆盖

Mockito.verifyNoMoreInteractions(userDAO, idGenerator);

}

/**

* 测试: 保存用户-修改

*/

@Test

public void testSaveUserWithModify() {

// 模拟依赖方法

// 模拟依赖方法: userDAO.getIdByName

Long userId = 123L;

Mockito.doReturn(userId).when(userDAO).getIdByName(Mockito.anySt

ring());

// 调用测试方法

String path = RESOURCE_PATH + "testSaveUserWithModify/";

String text =

ResourceHelper.getResourceAsString(getClass(), path +

"userSave.json");

UserSaveVO userSave = JSON.parseObject(text,

UserSaveVO.class);

Assert.assertEquals("用户标识不一致", userId,

userService.saveUser(userSave));

Assert.assertEquals("异常消息不一致", "不支持修改",

exception.getMessage());

// 验证依赖方法

// 验证依赖方法: userDAO.getIdByName

Mockito.verify(userDAO).getIdByName(userSave.getName());

// 验证依赖对象

Mockito.verifyNoMoreInteractions(userDAO, idGenerator);

}

}

其中,加载的JSON 资源文件内容如下: userSave.json:

userCreate.json:

userModify.json:

通过执行以上测试用例,可以看到对源代码进行了100%的行覆盖。

小结: 编写测试用例流程如下: 如上图所示,第1、3 步适用于大多数单元测试,而第2、4 步只适用于有外部依赖的单元测试。

下面将对各个细节进行拆解:

流程详细介绍

定义被测对象

直接构建对象,简单直接

利用Mockito.spy 方法

Mockito 提供一个spy 功能,用于拦截那些尚未实现或不期望被真实调用的方法, 默认所有方法都是真实方法,除非主动去模拟对应方法。所以,利用spy 功能来定 义被测对象,适合于需要模拟被测类自身方法的情况,适用于普通类、接口和虚基 类。

利用 @Spy 注解

@Spy注解跟Mockito.spy 方法一样,可以用来定义被测对象,适合于需要模拟被测类自身方法的情况,适用于普通类、接口和虚基类。@Spy注解需要配合@RunWith注解使用。

利用@InjectMocks 注解

@InjectMocks 注解用来创建一个实例,并将其它对象(@Mock、@Spy或直接定义的对象)注入到该实例中。所以,@InjectMocks 注解本身就可以用来定义被测对象。@InjectMocks 注解需要配合@RunWith 注解使用。

模拟依赖对象

在编写单元测试用例时,需要模拟各种依赖对象——类成员、方法参数和方法返回值。

直接构建对象

如果需要构建一个对象,最简单直接的方法就是——定义对象并赋值。

反序列化对象

如果对象字段或层级非常庞大,采用直接构建对象方法,可能会编写大量构建程序代码。这种情况,可以考虑反序列化对象,将会大大减少程序代码。由于JSON 字符串可读性高,这里就以JSON 为例,介绍反序列化对象。

反序列化模型对象:

反序列化模型对象:

反序列化映射对象:

利用 Mockito.mock 方法 Mockito

Mockito 默认所有方法都已被模拟——方法为空并返回默认值(null 或0),除非主动执行doCallRealMethod 或thenCallRealMethod 操作,才能够调用真实的方法。

利用@Mock 注解

@Mock 注解跟 Mockito.mock 方法一样,可以用来模拟依赖对象,适用于普通类、接口和虚基类。@Mock 注解需要配合 @RunWith 注解使用。

利用Mockito.spy 方法

利用@Spy 注解

@Spy注解跟Mockito.spy 方法一样,可以用来模拟依赖对象,适用于普通类、接口和虚基类。@Spy 注解需要配合@RunWith 注解使用。

注入依赖对象

利用Setter 方法注入利用ReflectionTestUtils.setField 方法注入

利用Whitebox.setInternalState 方法注入

利用@InjectMocks 注解注入

@InjectMocks 注解用来创建一个实例,并将其它对象(@Mock、@Spy或直接定义的对象)注入到该实例中。@InjectMocks 注解需要配合@RunWith 注解使用。

设置静态常量字段值

Whitebox.setInternalState 方法和@InjectMocks 注解并不支持设置静态常量,需要自己实现一个设置静态常量的方法: 具体使用方法如下:

注意:经过测试,该方法对于int、Integer 等基础类型并不生效,应该是编译器常量优化导致。

模拟依赖对象

根据返回模拟方法

模拟方法单个返回值

模拟方法定制返回值

模拟方法抛出单个异常

直接调用真实方法

其他省略… 编写时,未知的细节可以查阅手册

调用被测方法

调用无权限访问的普通方法

调用无访问权限的普通方法,可以使用PowerMock提供的Whitebox.invokeMethod方法。

调用无权限访问的静态方法

验证依赖方法

在单元测试中,验证是确认模拟的依赖方法是否按照预期被调用或未调用的过程。

验证无参数方法调用

验证方法默认调用1次

验证指定参数方法调用

验证任意参数方法调用

在验证依赖方法时,有时候并不关心传入参数的具体值,可以使用Mockito 参数匹配器的any 方法。Mockito 提供了anyInt、anyLong、anyString、anyList、anySet、anyMap、any(Class clazz)等方法来表示任意值。

验证必空参数方法调用

同样,如果要匹配null 对象,可以使用isNull 方法,或使用eq(null)。

验证方法调用n次

验证方法调用至少1次

使用ArgumentCaptor.forClass方法定义参数捕获器

使用@Captor 注解定义参数捕获器

验证数据对象

通过JUnit 提供的Assert.assertNull 方法验证数据对象为空。

验证数据对象值

JUnit 提供Assert.assertEquals、Assert.assertNotEquals、Assert.assertArrayEquals方法组,可以用来验证数据对象值是否相等。

验证复杂数组或集合对象

对于复杂的JavaBean 数组和集合对象,需要先展开数组和集合对象中每一个JavaBean 数据对象,然后验证JavaBean 数据对象的每一个属性字段。

通过序列化验证数据对象

如上一节例子所示,当数据对象过于复杂时,如果采用Assert.assertEquals 依次验证每个JavaBean 对象、验证每一个属性字段,测试用例的代码量将会非常庞大。这里,推荐使用序列化手段简化数据对象的验证,比如利用JSON.toJSONString 方法把复杂的数据对象转化为字符串,然后再使用Assert.assertEquals 方法进行验证字符串。但是,序列化值必须具备有序性、一致性和可读性。

通常使用JSON.toJSONString 方法把Map 对象转化为字符串,其中key-value 的顺序具有不确定性,无法用于验证两个对象是否一致。这里,JSON 提供序列化选项SerializerFeature.MapSortField(映射排序字段),可以用于保证序列化后的keyvalue的有序性。

验证数据对象私有属性字段

有时候,单元测试用例需要对复杂对象的私有属性字段进行验证。而PowerMockito提供的Whitebox.getInternalState 方法,获取轻松地获取到私有属性字段值。

验证依赖对象

验证模拟对象没有任何方法调用

Mockito 提供了verifyNoInteractions 方法,可以验证模拟对象在被测试方法中没有任何调用。

验证模拟对象没有更多方法调用

Mockito 提供了verifyNoMoreInteractions 方法,在验证模拟对象所有方法调用后使用,可以验证模拟对象所有方法调用是否都得到验证。如果模拟对象存在任何未验证的方法调用,就会抛出NoInteractionsWanted 异常。

清除模拟对象所有方法调用标记

在编写单元测试用例时,为了减少单元测试用例数和代码量,可以把多组参数定义在同一个单元测试用例中,然后用for 循环依次执行每一组参数的被测方法调用。为了避免上一次测试的方法调用影响下一次测试的方法调用验证,最好使用Mockito 提供clearInvocations 方法清除上一次的方法调用。

精彩内容

评论可见,请评论后查看内容,谢谢!!!
 您阅读本篇文章共花了: