-
Mockito
上篇文章介绍了 Spring Boot 单元测试的一些案例场景,其中我们先回想下关于 Service层 模拟对象注入的测试场景,在单元测试过程中,对那些不容易构建对象的采用一个虚拟对象来代替测试的方法称为
Mock测试
。在Spring Boot中内置了 Mockito 测试工具 [常用的Mock测试工具还有:JMock、EasyMock等],Mockito 可以模拟任何
类
和接口
,模拟方法调用的返回值
、抛出异常
。同时记录调用这些模拟方法的输入/输出和顺序,从而校验这些模拟对象时候被正确顺序调用,以及按照期望的属性被调用。上篇文章提到的积分案例场景中,Service层采用了
@MockBean
注解模拟注入了 CreditSystemService接口实现对象。这里对模拟的对象进行下测试:
@Test public void test() throws IOException { int userId = 10; // 创建mock对象 CreditSystemService creditService = mock(CreditSystemService.class); // 模拟mock对象调用 when(creditService.getUserCredit(anyInt())).thenReturn(100); // 实际调用 int ret = creditService.getUserCredit(userId); // 比较期望值和返回值 assertEquals(100, ret); }
Debug结果截图:
补充:Mockito模拟测试方法有 mock、when、thenReturn等,详情可查看相关API文档。
模拟方法参数: Mockito 提供
any
方法模拟方法的任何参数,如上述例子中:when(creditService.getUserCredit(anyInt())).thenReturn(100);
注意:在单元测试中,大多数情况下不推荐使用
any
方法,因为模拟的对象需要提供更明确的输入/输出才能更好的完成单元测试,因此最好采用具体的方法参数来代替any
方法。int userId = 10; // 创建mock对象 CreditSystemService creditService = mock(CreditSystemService.class); // 模拟mock对象调用 when(creditService.getUserCredit(eq(userId))).thenReturn(100);
上述代码传入方法参数为为
userId
,若被测试代码没有参照传入参数 userId 为 10,并且输出结果为 100 时,该单元测试会报错。这也很好的限制了单元测试方法的输入/输出。[eq 表示参数相等]Mockito 不仅模拟参数调用的输入/输出,也可以记录模拟对象是如何调用的。像模拟调用并未实际被调用,Mockito 也会报错,通过
verify
方法来精确的校验模拟低下是否被调用。针对调用
两次
的业务场景进行测试:int userId = 10; // 创建mock对象 CreditSystemService creditService = mock(CreditSystemService.class); // 模拟mock对象调用 when(creditService.getUserCredit(eq(userId))).thenReturn(100); // 实际调用 [第一次] int ret = creditService.getUserCredit(userId); //注释如下行,单元测试会失败 [第二次] creditService.getUserCredit(userId); // 比较期望值和返回值 assertEquals(100, ret); // 期望调用次数校验 verify(creditService, times(2)).getUserCredit(eq(userId));
verify
方法包含了模拟的对象和期望的调用次数,使用times
来构造期望调用的次数。如果在测试方法中只发生了一次 getUserCredit() 调用,那么 Mockito 在单元测试中会抛出异常(未按照期望调用次数执行单元测试方法)。除了上面讲的,Mockito 还可以验证
方法调用的顺序
,使用inOrder
方法:// Mock对象调用 when(creditService.getUserCredit(eq(userId))).thenReturn(100); when(creditService.addCredit(eq(userId), anyInt())).thenReturn(true); // 实际调用 int ret = creditService.getUserCredit(userId); creditService.addCredit(userId, ret + 10); // 验证调用顺序 InOrder inOrder = InOrder(creditService); inOrder.verify(creditService).getUserCredit(userId); inOrder.verify(creditService).addCredit(userId, ret + 10);
上述代码采用 inOrder 对象的 verify 方法校验测试方法执行顺序,确保模拟对象先调用 getUserCredit 方法,再调用 addCredit 方法进行积分增加操作。
模拟返回值:
-
thenReturn
模拟返回结果when(creditService.getUserCredit(eq(userId))).thenReturn(100);
-
thenThrow
模拟抛出异常 [lt(0) 表示参数小于0 的情况]when(creditService.getUserCredit(lt(0))). thenThrow(new IllegalArgumentException("userId不能小于0"));
-
doThrow
模拟无返回值情况下,抛出异常List list = mock(List.class); doThrow(new UnsupportedOperationException("不支持clear方法调用")) .when(list).clear(); // 实际调用,将抛出异常 try { list.clear(); } catch (UnsupportedOperationException x) { return ; } Assert.fail();
-
-
面向数据库应用的单元测试
看到标题,面向数据库。首先想到的就是一系列
CURD
操作了,在单元测试中我们可以通过 Mockito 模拟注入 Dao层 对象进行CURD
操作。但是针对未开发完全或者正在运营的数据库应用产品线项目,数据库方面的测试就限制在建立在不影响正常使用的情况下进行了。关于数据库应用的单元测试问题,首先我们需要一个
测试数据库
。上篇文章提到了@sql
可以信息数据库脚本初始化。-
@Sql
在进行单元测试前,需要先准备新的测试数据库,这个测试数据库通常不包含任何数据,或者只包含一些必要的字典类型的数据。
@Sql
注解可以引入一系列 SQL 脚本来进一步模拟测试前的数据库数据,案例如下:@RunWith(SpringRunner.class) @SpringBootTest @ActiveProfiles("test") @Transactional public class UserDbTest { @Test @Sql({"classpath:test/db/user.sql"}) //初始化一条主键为1的用户数据 public void test(){ // 测试代码 } }
这里需要说明两点:
-
@ActiveProfiles("test")
因为需要一个专用的测试数据库,该注解是激活application-test.properties 配置文件。 -
@Sql({"classpath:test/db/user.sql"})
单元测试方法前初始化测试数据库内容。如果 SQL脚本没有以/
开头,则默认在测试类所在包下。否则,从根目录搜索。INSERT INTO `user` (id,`name`, `department_id`) VALUES (1,'Jerry', '5106');
-
-
Spring Boot 2.0 读书笔记_14:单元测试【白盒测试】下
最新推荐文章于 2024-04-25 14:59:11 发布