阅读 646

如何说服前端写单测

写在前面

互联网上半场,2C 业务占据了市场的绝对大头,其业务特点是「短平快」,快速试错,快速迭代,出来的快,死的也快。在这种环境下,功能迭代的速度关乎生死,没有比生死更重要的事了。单测?等这个(下个,下下个……)迭代完成之后再补吧。

随着互联网发展进入了下半场,2B 业务开始抬头,业界也开始出现了很多优秀的工具。这就意味着业务质量和稳定的权重大大提升了。这时候「稳定」成了关乎生死的事情。试想一个 2B 的业务因为新功能上线,出现了 1h 的不可使用状态,这将是灾难性的。游戏规则已经发生了变化,玩法自然也要跟着变。

网上写单测的文章太多了,要么是介绍工具(jest、mocha、chai 等)的,要么是照本宣科介绍什么是单测的。看过之后总是觉得无法指导实际开发,不够接地气,于是尝试着总结了此篇文档,希望能够补上这块缺失。所以本篇文章里不会出现单测的基本概念与单测工具的介绍,有的只是指导实践的一些方法论。

为什么要求单测覆盖率?

首先,「覆盖率」不是目的,只是达到目的的手段,我们希望:

  • 通过「单测」唤起同学们对代码质量的意识;
  • 通过「单侧覆盖率」这个指标来保证和推进方案的落地;
  • 通过「写单测」来提高同学们的代码质量(为什么能提高?请看下面「重新认识单测」部分);

所以,「覆盖率」绝不是目的,虽然覆盖率不能简单的等同于代码质量,但是如果想持续提高覆盖率,并把覆盖率维持在一个较高水平,是一定会触及到更深层次的改变的。 至于如何制定覆盖率的标准,这个建议由项目自己制定一个长期计划,此计划务必做到持续提高维持高水平,可参考下文「怎么写好单测」部分。

什么不是单测?

比起什么是单测,让我们先来看看什么不是单测,也许会给我们更多启发:

需要访问数据库的测试不是单元测

需要访问网络的测试不是单元测试

需要访问文件系统的测试不是单元测试

——修改代码的艺术

纯函数,副作用,幂等,函数式编程……如果你的脑海里出现了这些词汇,恭喜你,已经上道了。

写单测有什么好处?

  1. 安全放心:这是显而易见的,相信你一定有过不少上线时心里没谱甚至胆战心惊的经历吧。
  2. 大胆重构:就算这个系统是你从零搭建的,你重构的时候也绝对会犯同样的错误。
  3. 解释性:测试用例就是你最好的说明文档,甚至相当于 demo。

以上是直观的,大家都懂的,下面说些容易被忽视的

  1. 提升设计能力:如果你认真对待单测,那么它会强迫你写出可维护性更好的代码。如果你发现代码不容易甚至不能写单测,那么八成是代码设计的问题。当你思考并重构了这部分代码时,相信你会觉得付出的精力和时间都是值得的,你再写出「可测试性不好」的代码的可能性就越来越小了。另外,据说 85% 的缺陷都在代码设计阶段产生,而发现 bug 的阶段越靠后,耗费成本就越高,指数级别的增高。
  2. 提升全面思考能力:单测经常会写边界 case,久而久之这种思路会形成惯性,以后在写代码时思路会非常清晰,思维缜密、面面俱到也会成为习惯。
  3. 提升代码质量:这步已经是水到渠成的了,是上面几条所结的果,无需赘述。
  4. 节省时间:别喷我,我只是个搬运工,论据请看从头到脚说单测——谈有效的单元测试里提到的《单元测试的艺术》那个例子

什么是好的单测?

  1. 正确、清晰、简洁,实践中单元测试不光测试代码的正确性,还能够帮助其他开发者理解代码逻辑,理解如何使⽤相关的类或者函数(可以当做接⼝或函数的使⽤⽰例了,省的写使⽤⽂档和 demo 了),所以要求单测写的清晰,简洁,有⾮常好的可读性。
  2. 完整性,也是必需的,单测应该有很⾼的覆盖率,把可能的输⼊输出场景都考虑到。
  3. 健壮性,是最容易被忽略的一项。当被测试的类或者函数被修改内部实现或者添加功能时,⼀个好的单测应该完全不需要被修改或者只有极少的修改。⽐如⼀个排序函数的单测实现是完全稳定的,它不应该跟着不同的排序算法⽽变化。
  4. 有个好名字,让⼈⼀看就知道是做什么测试,如果名字不能说明问题也要加上完整的注释。⽐如 testSortNumbers_withDuplicated, 意味 SortNumbers 函数的单元测试来验证有重复数字的情况。
  5. 逻辑简单,尽量避免使⽤命令式编程(Imperative Programming)引⼊条件判断,循环等复杂逻辑。否则很可能会给单元测试⾃⾝带来不少 bugs,这样就需要写单元测试的单元测试了……⼀句话单元测试不要引⼊复杂的逻辑,最好是不要引⼊逻辑。
  6. 完备⽽不重复。同样的测试场景,或者同类型的测试输⼊不要写多个单元测试,找⼀个有代表性的场景输⼊就可以了。

怎么写好单测?

以下只是一些理论,请结合实际进行改良

战略四步走

  1. 会写,项目里有工程,可以写单测,人人可写即可
  2. 写好,对代码是否可测有判断力,知道如何写出「可测试性好」的代码
  3. 重构,随着单测覆盖率的提升,一些代码自然被重构,开始出现良性循环
  4. 常态,写新代码时,已经想好了单测怎么写,二者同时进行,形成常态

战术四要素

  1. 核心代码优先、重点覆盖单测,如公共函数,公共组件等。此时要求「精」,处于战略 1、2 阶段,要注意修炼内功
  2. 设计好目录结构。如常量(无需测试)单独一个文件,并统一命名,方便 ignore;UI 组件与数据处理等逻辑分为不同文件,不要耦合;公共组件放在一个目录下,方便统计覆盖率等
  3. 函数式编程思维,尽最大可能降低函数的副作用,最典型的就是要把 window,localStorage 等全局变量作为参数传入,而不是在函数里直接访问并操作
  4. 把副作用控制在一定范围内,副作用是很难避免的,那么就把它们统一收敛起来,比如操作 localStorage,只允许通过一个二次封装的函数来触发,类似于 redux、vuex 等状态管理机制的思想

结语

单测是一个成熟程序员必备的技能,正如开头所说的,「游戏规则变了」,我们一定要有这个意识,未雨绸缪,跟上脚步。说实话,在整理这些方法论之前,我个人内心只是觉得「写单测很重要,应该要写单测,写单测可以不怕重构」,但是这些模糊的概念真的不足以说服我去写单测。直到主导部门「质量管理」项目后,才开始深入思考,从说服我自己开始,一个点一个点的研究,一个问题一个问题的解决,自认为自己对于编码的理解又多了一个维度,还是比较开心的。最后希望本文,能起到抛砖引玉的作用,给大家提供一个思路和引子,也算功德一件。

参考资料

文章分类
前端
文章标签