写点什么

提高单元测试质量的低代码思路

作者:赫杰辉
  • 2023-03-29
    上海
  • 本文字数:3854 字

    阅读完需:约 13 分钟

提高单元测试质量的低代码思路

摘要

单元测试是保障代码质量的一个非常有效的手段。但在单元测试实践中,往往会出现研发人员不愿意写单元测试,或者即使写了单元测试,但是其代码质量和覆盖率不高的问题。本文会尝试分析单元测试推广难,质量差的原因,并给出一个解决之道。

单测推广难点

研发人员为什么还是不愿意写单测呢?常见的主要有以下几个原因:

  1. 虽然这代码是我写的,但是质量不好,很难测

  2. 因为这代码是我接手的,所以质量不好,很难测

  3. 我只改了 1 行

  4. 写单测要花很多时间,甚至比写业务代码的时间还长,值吗?

  5. 业务代码经常变,之前写好的单测总不过,时间紧迫,只能先注释掉 assert,牺牲单测质量

  6. 项目排期很紧张,在既要有要还要的情况下,只能不写单测,后期也没时间补


上面 1,2,3,4 归根结底是代码质量问题,既代码的可测试性不好,很难解决。5,6 是项目管理问题,如果管理的质量意识到位,能给足研发进行单测的时间,是可以解决的。反之如果管理层面不愿意留出单测时间,那其实说明质量问题不是大问题。

常见解决办法

要体现单测的质量保障效果,需要从单测质量和数量两方面入手,那么如何提升单测的质量和数量?


常见的提高质量的做法就是培训,比如通过培训分享单测写的好的人的优秀经验。我就在公司内部给大家分享过几次单元测试 100%覆盖技巧。


而要提高单测数量,除了研发自己写单测外,还有一种做法是将一部分单测开发工作移交给测试。但白盒测试一般都比较少,为了加大白盒测试人员的比例,也有通过培训黑盒测试成为白盒测试的做法。


当然相应的辅助管理手段也是必要的,比如定期对单元测试代码进行抽查,对质量高的做表彰,对质量低的做提醒等等。


但其实我对通过测试人员解决单测问题是有所保留的。如果专业的程序员都不想写也写不好单测,测试人员就可以吗?

万万没想到我也要学编程

其实不管是单侧的质量还是数量,提高的关键都是代码的质量,特别是可测试性。不解决代码的可测试性,再怎么折腾都没啥用。


如何提升代码质量?去学习 OOAD,设计模式,各种编程设计原则,层出不穷的方法论吗?扪心自问,我们真的学会并能灵活运用这些知识,写出高质量代码吗?如果回答是的,我们今天干嘛还在讨论这个问题。这些手段就其传授的知识而言本身没有问题,但这些手段的效率很低,周期很长,无法短期见效。学会这些你能成为一个高手,但无论从管理还是成本角度考虑,你能想象一个全是高手的团队吗。


既然做法都不是可以快速复制的工业化解决方案,我们必须另辟蹊径。从研发工程实践来讲,需要能够快速提升批量人员代码质量的手段。

低代码解决方案


想要快速提升代码质量,本质上是如何低成本产出高质量设计。只有这样才能解决代码可测试性差的问题,才有可能提升单测质量,此外还可以思考如何减少必要代码量。提高单测数量的本质是希望覆盖更多的被测代码。如果能减少必要代码量,则单测工作量也会相应减少,同等人力投入下就可以覆盖更多的被测代码。


解决问题的办法在问题发生的层面往往是找不到的,要实现上面的目标,眼光要超越代码层面。近年来兴起的低代码无代码技术就是一个可行的方案。但该技术争议也比较大,我认为主要原因是大多数低代码工具更倾向于低门槛使用者,而忽略了专业研发的使用场景,这篇文章中专门做了分析:低代码工具选项难题浅析


X-Series 是为后端研发打造的一套开源低代码框架,可以实现上面的目标。下面先简要介绍 X-Series,再分析为什么 X-Series 可以低成本产出高质量设计和减少必要代码量。

X-Series 简介

X-Series 是面向后端研发人员的低代码工具,用于高效构建可理解,可维护的后台应用。它包含 3 个独立工具,分别对应流程处理,逻辑判断和状态管理。


xUnit利用流程图抽象后台流程处理。开发时根据需求对应的处理流程先创建流程图,这一般只花 1 分钟时间。流程图画好了,设计工作基本上也就结束了。研发既可以拿着这个流程图去和需求方 review,也可以用于和团队成员进行沟通,还可以在测试阶段协助白盒测试人员设计测试案例。

流程图设计完毕后,接下来的只需要为每个流程节点提供代码实现。不同的节点类型需要实现不同的预定义接口。一般常见的接口为以下 4 种:

  1. Processor:对传入参数进行处理,无返还结果

  2. Converter:对传入参数进行转换,返回一个结果

  3. Validator:对传入参数进行判断,返回 true/false

  4. Locator:对传入参数进行判断,返回一个 key

所有的传入参数类型均为Context类型。在系统中调用流程图也很方便


xUnit 非常注重使用体验,流程图的设计和代码的开发都可以在同一个 IDE 中完成。利用集成在 IDE 中的可视化编辑器,研发人员可以随时从节点跳转到代码,有了 xUnit 相当于有了一张系统蓝图,而不会迷失在代码海洋中。


xDecision 使用决策树模型来表达复杂逻辑判断。用户首先在模型中定义用于判断的变量和决策,需要的话还可以定义类型以方便和代码匹配。其次在图上添加节点,节点可以包含决策,也可以包含表达式用于进一步的判断。最后将节点连接起来并指定表达式,即可实现判断逻辑。

使用决策树仅需要创建模型实例并传入参数,不需要单独生成代码。对决策树的创建维护完全在模型中,如果事实变量和决策集合没有改变,则模型变更时无需改动主程序。xDecision 还支持生成测试代码,用于快速检验模型的正确性。


xState 使用状态机模型来构建业务对象的状态模型,支持状态变迁校验和事件处理调用。无论业务模型的状态有多复杂,都可以用状态图清晰的表达。同 xDecision 类似,如果事件和状态集合没有变化,修改模型后无需改变调用程序


X-Series 已经开源多年,在多个公司都有应用。其中 xUnit 和 xState 已经被我们公司多个研发部门采用,用户反馈能够很显著的提升系统可理解性,提高组件复用性和研发效率。

提升可测试性的原理

在做进一步阐述之前,我们先回顾一下什么是设计。所谓设计,本质上是一种系统分解的手段,既如何将一个系统分解为一系列相关的更小的系统,持续这一分解的过程,直到达到能够直接编码实现的粒度。此粒度上的代码要能满足高内聚,低耦合的质量要求。


为什么说 X-Series 能够提高代码的可测试性?因为它能够同时满足低成本产出高质量设计和减少必要代码量两个要求。


以 xUnit 为例,流程图是研发人员最熟悉的工具,人人都懂,利用流程图可以将系统以清晰的方式展现出来,并且将工作分解到节点级别。因为流程图是基础知识,不需要熟练掌握 OOAD,设计模式或其他任何设计理念就可以使用,所以利用流程图就满足了低成本的要求。如果流程图的每个节点如果还是比较抽象复杂,则可以通过子图的方式继续分解。这不但保障了设计手段的一致性,而且由于不需要引入额外的设计手段,也就避免了相关培训成本。


流程图本身具备的清晰易懂的特性满足了高质量的要求。当流程图分解到节点的工作职责已经非常单一具体,可以利用代码直接实现时,则很容易满足代码的高内聚要求。在代码层面,xUnit 预先定义了各类型节点要实现的接口。这些接口职责高度单一,研发人员只需要为接口提供实现即可。在减轻用户负担的同时还避免了设计引入缺陷。


流程图节点间的调度协调不需要单独代码实现,xUnit 引擎会直接利用流程图模型本身来完成。如果说 xUnit 可以大幅减少粘合代码量的话,xDecision 和 xState 则更进一步通过模型直接消灭了代码实现的必要性,因为它们生成的模型可以在应用中直接调用,不需要生成任何中间代码。


虽然使用 X-Series 可以低成本产出高质量设计,大幅减少必要代码量,并从设计角度保障代码层面的高内聚。但作为一个完整的方法论我们还需要在代码层面为实现低耦合提供方案,否则还是无法彻底满足可测试性要求。

降低耦合度的技巧

在讨论技巧前我们先明确为什么低耦合是提高代码可测试性的关键?


例如 A 类的 a 方法依赖 B 类的 b 方法完成自身功能,b 方法可能是一次外部调用,一次数据库请求,或者某种复杂运算。当 A 类和 B 类耦合度高的时候,例如 A 类在自己代码内部创建 B 类,则要为 A 类提供单元测试时,必须间接的满足 B 类的运行条件。这就是为什么耦合度高的代码可测试性差的原因。反之如果 A 类可以通过某种方式注入 B 类,则可以在单元测试时使用 B 类的 mock 来代替实际的 B 类完成对 A 类的测试工作。因为 mock 可以由我们提供,创建和使用过程都是受控的,因此耦合度低的代码可测试性就好。


由上面的例子可知,要降低耦合度,核心就是将所有外部依赖参数化。具体做法分两种情况:

  1. 如果 B 类实例在 A 类实例的生命周期内都不需要改变,那么只需将 B 类实例通过 A 类的构造函数注入。如果没有构造函数,可以创建一个包含 B 类实例的构造函数。

  2. 如果 B 类实例在 A 类实例的生命周期中会发生改变,那么只需要为 A 类创建一个 B 类实例的 setter 方法即可。


完成以上改造后,即可降低代码耦合度,因此针对 A 类 a 方法的单元测试的全部工作是:

  1. 把 B 类通过构造函数或 setter 从内部属性变为外部依赖

  2. 对 B 类做 mock,重载 b 方法使其能返回特定测试所需要的值

  3. 通过构造函数将 B 的 mock 注入到 A 类中

  4. 基于重载的 b 方法来实现 a 方法的单元测试


一个合格的单元测试应该要做到所有逻辑路径 100%覆盖,除非是特别简单的代码,例如标准的 getter/setter 外。有了完备的单元测试,我们就能快速检测被测代码本身行为是否符合预期,对系统其它部分是否造成影响,或者被其他部分的修改影响到。

结论

如果做一件事情特别费力,往往是方法不对。通过提升个人能力或运用强制手段提高单测质量效果并不好。而借助低代码,不但能低成本产出高质量设计,还能大幅降低代码量,再结合提升可测试性的技巧,三管齐下就能有效提高单测覆盖率和质量。


参考资料:

影响研发效能的因素和提升办法:提升研发效能的低代码思路

低代码能大幅减少必要代码量:低代码与传统研发模式案例对比


用户头像

赫杰辉

关注

开源可视化系统构建工具x-series作者 2020-06-09 加入

还未添加个人简介

评论

发布
暂无评论
提高单元测试质量的低代码思路_赫杰辉_InfoQ写作社区