cover_image

单元测试实践总结:如何保证在频繁修改优化代码之后,不影响其原本的功能

问陌 蚂蚁研发效能
2022年09月20日 09:30

图片

代码设计要遵循一个重要的原则-易于修改,并且要实现这个目标就应该经常修改它,而不是等到大量积累了臃肿代码之后再进行深度重构。而如何保证在频繁修改优化代码之后,不影响其原本的功能呢?那就是要编写一套完善的测试用例,能覆盖到所有的链路场景,通过测试用例的执行结果来确保代码修改的正确性。

01
为什么要写单测



软件要易于修改

最近在看一本书《代码整洁之道:程序员的职业素养》,这本书里面说到,代码设计要遵循一个重要的原则-易于修改,并且要实现这个目标就应该经常修改它,而不是等到大量积累了臃肿代码之后再进行深度重构。



完善的测试用例是修改代码的安全保障

而如何保证在频繁修改优化代码之后,不影响其原本的功能呢?那就是要编写一套完善的测试用例,能覆盖到所有的链路场景,通过测试用例的执行结果来确保代码修改的正确性。

特别是我们做金融场景业务的开发,尤其要注意代码的正确性、安全性,那么写好单测是不可或缺的。


02
写单测前要做的准备



了解你的业务流程

写单测的基本前提是,你得足够熟悉你要测试的链路流程,正常执行的流程,以及异常及相应处理。

另外,还需要清楚,什么样的结果能代表代码流程按预期执行了,比如对方法返回结果的断言,对数据库生成记录的校验等。



梳理外部交互及数据变动点

如:

  • 对外RPC调用

  • MQ消息的发送、消费

  • 对数据库的插入、修改、删除


03
使用Mockito进行Mock



Mockito注解

Mockito提供了几个注解,包括@Mock、@Spy、@MockBean、@SpyBean,用于声明式地构造待Mock对象,只有被Mockito代理的对象才能进行后续的方法行为mock。


@Spy 和@SpyBean的区别、@Mock 和 @MockBean的区别

图片

@Spy 和@Mock 生成的对象不受Spring管理,而 @SpyBean 和 @MockBean 会生成SpringBean,可进行自动注入

@Spy 调用真实方法时,其依赖的其它bean是无法通过Spring自动注入的,要使用注入,要使用 @SpyBean

@MockBean 和 @SpyBean 的区别

图片

默认行为的不同:对于未指定mock的方法,spy默认会调用真实的方法,有返回值的返回真实的返回值,而mock默认不执行,有返回值的,默认返回null

对于某些待测类,如果我们只想Mock其某一个方法,而其他方法希望按原来正常调用,那么就应该使用@Spy或@SpyBean,而对于想要完全屏蔽原本行为的类,则使用@Mock或@MockBean来声明。


注意:使用 @MockBean 时,通过 Mockito.when().thenReturn()来模拟方法返回,但是使用@SpyBean时,这样写会调到真实的方法,需要Mockito.doReturn().when().xxx()来声明模拟。

像我们目前在SpringBoot框架环境下开发的话,一般使用@MockBean和@SpyBean即可。



Mockito对象行为

  • Mock方法返回值

/ 声明userService在传入任意参数调用get()方法时,返回userMockito.when(userService.get(Mockito.any())).thenReturn(user);Mockito.doReturn(user).when(userService).get(Mockito.any());    // Spy必须用这种
  • Mock异常抛出

// 声明userService在传入任意参数调用get()方法时,抛出指定异常Mockito.when(userService.get(Mockito.any())).thenThrow(new RuntimeException("mock error"));Mockito.doThrow(new RuntimeException("mock error")).when(userService).get(Mockito.any()); // Spy必须用这种



04
写单测的注意点


用例聚焦内部逻辑,不关注外部及上下游

  • 比如转账接口的单测,我应该主要关注同步接口中的受理过程,受理流程完成后的异步过程,最好不要在当前流程中提现,而应该另外再对异步实际调用方法写Case。

  • 消息消费和定时任务最好是针对最后实际调用的Service方法进行测试。



单测不能影响外部应用和数据

比如风控Accept和风控Pending后的流程截然不同,那么就要分别创建Case,并Mock不同结果。

图片

关闭Dubbo服务注册

图片

视情况关闭定时任务

测试参数和数据库准备数据一定不要对实际数据产生影响,在一些关键属性,不如id、requestNo等唯一标示要能明确区分是测试数据,并且要易于清理。但注意要符合实际情况,能代表真实场景。

05
Acts2.0 简单实践


| 此部分仅做基础示例,基于新加坡建站技术栈:Doggy(SpringCloudAlibaba) + Acts (AntCoreTestSuite)2.0


Acts介绍

ACTS 源于蚂蚁集团多年金融级分布式架构工程的测试实践积累与沉淀,为企业提供高效、精细化的接口自动化测试。

ACTS是一款基于数据驱动测试执行的蚂蚁内部自研测试框架,适配 SOFA、SOFABoot 的测试环境。它的用例数据以静态文件为载体,在此基础上构建用例模型驱动测试执行,并依赖框架内定义的一套标准化测试流程自动编排用例整个生命周期,同时实现了可视化编辑、高效化用例管理、精细化结果校验等,可以极大压缩测试代码体积。


简单实践

测试用例目录结构

图片

编写测试脚本

图片

数据准备

方法入参及结果断言:

注意,对抛出异常的校验可以在Result内声明具体异常,跟正常结果对象一样声明即可。

图片

DB初始准备数据(PrepareDBData目录)

图片

这里columnName、dataType、comment、primaryKey、nullable这几列都是根据实际表结构生成的。
而flag是标识特定行为:
后面的value列即要插入的数据,如果要插入多行记录,则将value列复制多行,如图中插入了两条记录。

DB校验数据(CheckeDBData目录)

图片

结构与上面准备数据类似,但flag标识在这里的含义有些许不同:
注意!
添加清理标识(C)时,一定要确保通过此字段value进行where查询只会清理测试数据,而不会删掉其他数据。
本地查看覆盖率
以Coverage模式执行
图片

图片

图片

往期推荐:
提升代码沟通性,需做好这三个方面
高效率 js 代码指南(For Duktape引擎)
论如何提升自己的代码质量:这两方面,是重中之重!

热文阅读 · 目录
上一篇蚂蚁 Golang 领域驱动设计(DDD)极简最佳实践下一篇DDD之业务系统搭建的设计思考
继续滑动看下一个
蚂蚁研发效能
向上滑动看下一个