Spring Boot 2.0 读书笔记_14:单元测试【白盒测试】下

  • 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');
        
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

编程小透明

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值