单元测试是代码开发和DevOps质量内建无法绕开的话题,无论是橄榄球型还是金字塔型的的测试分层方案都需要对单元测试做充分的设计。
一、问题描述
在项目实际开发过程中,由于需要对存在大量数据增删操作的DAO层代码一并测试,单元测试有时会采用直接写库的方式来验证 sql、表字段对应关系及键约束。其中使用 @MockBean 注解所模拟创建的对象,会在 Spring 的 ApplicationContext 中进行管理。若系统有多个单元测试类中使用该注解,执行至流水线的单元测试步骤时,单元测试框架会为每个使用@MockBean注解的单元测试类重新执行一次应用的初始化加载。
如上的单元测试执行策略,会有很大的时间消耗。如:共20个单元测试类,其中有10个单元测试类都使用了@MockBean注解。则执行完20个单元测试类,Spring 应用上下文需要启动共11次,其中10个使用@MockBean注解的单元测试类需要各自启动一次,其他10个单元测试类在一次启动过程中完成测试执行,这无疑会增加执行时间,降低执行效率;同时,在上面描述的多次启动的基础上,如果每次启动都申请创建10个数据库连接资源,则启动11次的话,某一时刻会同时占用110个数据库连接资源。即在某一个单元测试类执行完成后,并不会立即释放当前单元测试类申请的连接资源(10个),而是等待所有单元测试类都执行完成后才进行释放,这种连接真实数据库实例进行单元测试的情况下项目,每次启动都会占用一定数量的数据库连接直到整个测试完成后才释放,导致资源浪费。
二、优化方案:
由于该情况为框架自有机制,多次启动是因为需要对各单元测试类进行隔离,避免各个类使用相同的 ApplicationContext,从而满足各个单元测试类 Mock 不同场景需求。经过分析,Mock 的对象仅作用于当前测试方法,且支持依赖注入的能力,利用这一特性对 Mock 方法进行改造,自行管理Mock对象的依赖关系,在每个类的单元测试方法执行完成后使用原对象对 Mock 对象进行替换,从而达到只启动一次的效果。
在单元测试方法执行开始前,进行 mock 对象的创建及依赖注入,并对原对象自行进行管理。在单元测试方法执行结束后,将原对象替换掉 mock 对象。
三、优点总结
通过对 @MockBean 注解多次加载 ApplicationContext 上下文机制的优化,改变原先由框架自行管理 Mock 对象的机制。既利用了 Mock 解除测试对象对外部模块的依赖使测试用例可以独立运行的特性,又规避了多次加载 ApplicationContext 上下文耗费资源和时间的不足。
保证应用中所有单元测试类执行完成,只启动一次应用上下文环境。且各单元测试类中需要用到的bean对象(不管是否需要Mock操作),不互相影响。经过改造,单元测试执行效率得到大幅度提升。在项目组现有 2200 个测试用例的基础上,平均执行时间缩减为原先的30%,有效提升了项目组的研发效率。
2020年11月27日,由云计算开源联盟指导,由高效运维社区和开放运维联盟联合主办的 2020 GOPS 全球运维大会 · 上海站上,隆重发布了 DevOps 标准持续交付部分第九批评估结果,中国农业银行手机存款贷款模块顺利通过由中国信通院开展的《研发运营一体化(DevOps)能力成熟度持续交付3级评估。DevOps 标准共分 5 级,持续交付部分如果能达到 3 级已经是国内领先水准,这代表着中国农业银行在参评项目的持续交付能力达到国内领先水平。这是中国农业银行在 6 月 19 日同时 5 个项目通过持续交付标准评估之后通过的第 6 个项目。
中国农业银行通过 DevOps 标准持续交付部分的 3 级评估的项目,分别是:
- 信贷中台项目
- 个人网银项目
- 分布式应用互联平台(AIR)项目
- 增值税进项税管理项目
- 金融小店项目
- 手机银行存款贷款业务