为什么做测试?

Martin Fowler在Self Testing Code中有段话,解释了我们为什么要做测试:

But the biggest benefit isn’t about merely avoiding production bugs, it’s about the confidence that you get to make changes to the system.

测试最大的好处不仅仅是避免生产错误, 它能够提高你修改系统代码的自信。

在项目实践中,往往有两种误区: 一种认为写测试会增加开发成本,影响项目进度,因此”有时间再写测试”,最终的结果是测试覆盖率低甚至没有测试。 另一种正好相反,把测试覆盖率当作项目质量的指标,动辄要求覆盖率90%以上。开发为了完成任务,getter,setter都不放过。

这两种显然都不是Martin提出的测试的真正目的。我们写测试还是为了编写和维护”正确的代码”,测试不是目的,而是我们的工具。维护过遗系统代码的开发一般会特别体会, 没有测试的遗留代码不要说修改逻辑,想读懂代码逻辑都很难。这里最能体现Martin提到的测试的目的:”它能够提高你修改系统代码的自信”!(你可能会说”我维护的是一个 新系统”。且不说新系统也是逐渐演进,再新的系统再过两年也就成了遗留系统。即使未来不是”你”来维护,也不要给后人挖坑才好)。

测试除了有利于系统代码的修改,还有一个非常重要的作用 – 驱动代码的实现!即测试驱动开发TDD。TDD是敏捷开发中的一项核心技术实践,一种方法论。

为什么TDD?

TDD在后端开发中已经并不陌生,虽然它还并没有像前后端分离这类生产实践一样普及,但各样的参考文章和项目实践已经非常成熟。但在前端开发中,虽然前端框架和相应的 测试框架百花齐放,TDD却在项目中很难推广。

之所以出现这种现象,并不能全部归于TDD在前端发展中不成熟。TDD只是一种方法论,它并没有依托于某种复杂的技术或者框架。切换成前端语言和前台运行环境去做测试依然 具备可实践性。

但很多前端开发(在项目中观察到),认为TDD相对于手动测试(可称作DDT开发驱动测试)并不直观,很多人喜欢”所见即所得”,代码实现后刷新页面便能看到结果,又何必花时间 写测试或者测试驱动出来代码呢?另外,对模板和样式的测试成本太高,而脱离了它们只测业务逻辑又脱离了页面呈现,很难直观验证逻辑的正确性。

针对于这种困惑,可以对TDD和DDT这两种实践方式做一下比较:

TDD和DDT的比较

  1. TDD是由业务需求驱动出来实现代码,而DDT却是实现代码驱动业务需求(往往是对页面呈现的手动测试和判断)

    业务需求当由测试代码呈现并维护后,最大程度上保证了实现代码的正确性,而且也”能够提高你修改系统代码的自信”。而DDT驱动出页面呈现的业务需求,不具有测试代码的”纯粹性”, 页面呈现是组件(样式,模板,业务逻辑)和其他组件相互作用呈现的结果。而且页面呈现并不能像测试代码一样”维护起来”,它是不断变化的。

  2. TDD更好的支持重构,DDT需要手动重复

    TDD伴随着测试自动化,让实现代码的修改和重构更加安全和有效。而手动重复无疑是浪费,也不具有可靠性。

测试的一些理论

在实践TDD之前,需要了解测试的一些基本理论。

测试金字塔

前端测试金字塔

这里就不展开讨论测试金字塔的理论了,可以参考一下Martin的The Practical Test Pyramid。虽然是 文章里以后端测试进行讨论,但前面也说过TDD仅仅是一种方法论,它在前后端都适用。

在项目实践中,真正让开发困惑的是在前端框架和测试工具越来越多样的情况下,该选择哪些测试?对于一个前端项目而言,Javascript单元测试,Dom测试,快照测试,E2E测试, 契约测试,UI测试…而每种测试又有各种测试框架。

测试的选择要依据项目的实际情况和需求。依据个人经验,对于单元测试级别的Javascript单元测试和Dom测试,Javascript单元测试用于测试业务逻辑,而Dom测试注重组件作为完整单元的 渲染。对于UI变化频繁的场景,可选择后者。快照测试用于测试组件UI的变化,它不应该作为单元测试级别,而更像回归测试。对于契约经常变化的场景,契约测试已经是和API交互的保障,这里 也要将契约测试和单元测试区分开来。E2E测试和UI测试成本属于更高级别高成本的测试,依据项目开发成本考虑集成测试的力度是选择的标准。单元测试是基础,本文介绍的TDD是基于单元测试进行的。

TDD周期(红-绿-重构)

TDD周期

TDD周期遵循红-绿-重构的开发方法,在有集成测试的项目中,可参考下图的TDD-BDD周期。

TDD-BDD周期

结束

在下篇中,会议快递查询举例,详细介绍前端通过测试驱动开发的方法。