Skip to main content

浅谈 TDD(测试驱动开发)

· 9 min read
keqing

v2-45d641f2191559d4eff581d0607efd61_1440w

如果你的项目要长期使用并维护的话,那么代码自动测试就非常有必要使用。因为没人能保证在修改代码后,不会引发其他额外 bug(功能失效,渲染失败),而在修改完代码后,跑一遍测试就能很大程度让开发者发现自己所修改的代码是否存在问题,是否会导致原有功能失效。

尤其是在其他人接手这个项目时,诱发 bug 的概率自然也就更高(因为他有很大的可能不知道这部分代码的上下文的功能用途),所以这也就是为什么很多开源项目与大型企业的公司都会使用自动化测试,以及要求一定的代码覆盖率。

当然如果项目不是长期维护的,那么完全没必要编写测试代码,这么做无疑是在浪费开发者的时间。

适合引入自动化测试的场景

提前简单总结下适合引入自动化测试的场景(优点)

  • 中长期项目迭代/重构(需要频繁的修改代码)

  • 准确定位代码问题,提高代码质量

  • 引用了不可控的第三方依赖,极易发生 bug(例:beta 版相关的包)

测试的目的在于,及时发现错误,提高代码质量和开发效率,避免存在 BUG 的代码发布上线造成损失

自动化测试要注意的点

  • 并不是所有项目都适合引入自动化测试,反而会增加一定代码成本

  • 如果项目开发阶段还不稳定,那么手动测试效率会比自动化测试更好

  • 有些代码可能这辈子都不会在碰第二次,就没有编写自动化测试的意义

在代码编写阶段,建议只对重点功能进行测试,没必要一定追求过高的测试覆盖率。注意,是编写阶段

测试思想

TDD:Test-Driven Development(测试驱动开发)

  • TDD:Test-Driven Development(测试驱动开发):TDD 则要求在编写某个功能的代码之前先编写测试代码,然后只编写使测试通过的功能代码,通过测试来推动整个开发的进行

BDD:Behavior-Driven Development(行为驱动开发)

  • BDD:Behavior-Driven Development(行为驱动开发):BDD 可以让项目成员(甚至是不懂编程的)使用自然语言来描述系统功能和业务逻辑,从而根据这些描述步骤进行系统自动化的测试

自动化测试类型

测试类型有以下几种:

  • 单元测试(Unit Testing)

    代码中多个组件共用的工具类库、多个组件共用的子组件等。通常情况下,在公共函数/组件中一定要有单元测试来保证代码能够正常工作。单元测试也应该是项目中数量最多、覆盖率最高的。

  • 集成测试(Integration Testing)

    测试经过单元测试后的各个模块组合在一起是否能正常工作。会对组合之后的代码整体暴露在外接口进行测试,查看组合后的代码工作是否符合预期。集成测试是安全感较高的测试,能很大程度提升开发者的信心,集成测试用例设计合理且测试都通过能够很大程度保证产品符合预期。

  • UI 测试 (UI Testing)

    对于前端的测试,是脱离真实后端环境的,仅仅只是将前端放在真实环境中运行,而后端和数据都应该使用 Mock 的。

  • 端对端测试(End-to-End Testing)

    将整个应用放到真实的环境中运行,包括数据在内也是需要使用真实的。

关于测试框架,我主要使用 VitestCypress。这两个作为测试框架都相对比较新,并且性能与开发上会比 JestPuppeteer 来的好。本文的一些测试示例也是基于这两类框架之上。

note

其实还有个接口测试,不过这就不是前端要关心的内容了,所以就没列举在这上面。

自动化测试的误区

自动化测试和普通说的测试是有些不大一样的,有很多测试,其实都不能归类为前端自动化测试。这里我会举个例子来说明一下。

在自动化测试来说有个要求:自动化测试要的不是某次测试执行的是否通过,而是每次执行都必须通过。

怎么理解这句话呢:比方说我要测试获取博客列表的函数,假设实际的接口失效了,那么就会导致结果与预期不一致,就会导致代码测试不通过。既然不通过,那我就要去查看为什么不通过。当我点击这个单元测试的时,发现原来是后端接口失效了。可万一哪天这个接口突然好了,又或者发现刚刚原来没插网线导致的请求失败导致测试不通过。像这些 不稳定因素 在前端自动化测试中就会使用 mock 的方式,强制返回一定格式的数据给测试框架。到这里你可能会好奇,为什么要这么做?

想想看,如果因为接口失效导致测试失败,是因为测试代码的问题吗?那跟测试代码有毛关系,明显是后端或者服务器的问题。我们要测试的是获取博客列表的函数,而不是在测试接口(接口自动化测试)。测试接口不应该是前端要做的事情。确保后端返回正确的响应结果,前端能够对这些数据进行处理渲染,这才是我们要做的。

每次测试都存在不可控的因素,就会导致每次测试结果都有可能不同,这就违背测试的意义了。 所以这也就是为什么要数据 mock 的原因了。

给测试输入的值,在经过测试后,要保证输出的值与我们预期想要结果的值相同。

自动化测试到底在测试什么?

其实目前前端有个尴尬的点,目前绝大部分实际业务项目里,前端的单元测试都没啥鸟用,UI 自动测试又太难搞。

这就导致很多开发者不清楚到底要测试什么,导致对测试特别不重视,包括我一开始也是如此。看到很多文章都是在演示测试 1+1 =2,介绍测试框架,很少从实际项目中出发进行测试。不过原因无非就是实际项目写的少,就别说测试代码了。再不然就是写过的代码都不怎么维护(重构,阅读),自然的就不会去写测试了。

不过确实没什么好举例的,因为太多东西可以写成单元测试了,比方说formatTime.test.ts, param2Obj.test.ts,validate.test.ts,从文件名就知道在测试什么了,就看开发者想不想写的问题了。

可以到 vitest-dev/vitest / facebook/jest 等测试框架中的 example 中查看测试案例。

关于 UI 测试和 e2e 测试,我非常推荐看看 cypress 的Todo 示例,测试的特别清楚,这里放张官方测试结果供参考。

这里补充一句,vitest 是能做 UI 测试的,可以通过 vuejs/test-utils 库来实现,但是 vitest 的运行环境是 nodejs,通过 jsdom 等库来模拟浏览器环境,而 cypress 是实实在在的运行在浏览器上,而且有可视化页面操作。这两者的区别也就是运行时环境的区别,有些实际场景对真实环境是有需求的,所以针对 UI 测试更多会选择像 cypress 这种直接运行在浏览器的测试框架。

为何我开始重视起测试?

在之前我根本不会在意测试,就连已有的测试代码我都不会尝试运行。就在前段时间我正重构我的一个项目时,但当我写了一大部分的代码后,我尝试运行发现有些功能失效了。于是我进一步的排查,终于找到我修改代码并还原成我原来的代码。

假设一开始有份完整的测试代码,当我修改一部分代码后,跑一遍测试查看测试情况。发现没问题后,再开始下一步的代码工作,反复测试,直到最终重构完毕。与其浪费代码的时间,不如将这些时间去用来完善测试代码。不仅自己后续使用需要,到时候项目交付给别人的时,别人也不至于修改你的代码时兢兢业业。

究其原因是为了保证代码质量

当然,虽说重视,但我也不会立马为已有的项目增加测试.耗时且费力不讨好。更多时候只会在准备重构的项目,或者是新项目上去增加测试代码。

编写这篇文章主要解惑我自己对往常对测试的看法,也借此机会养成 TDD 模式的开发的习惯。

参考文章

试试前端自动化测试!(基础篇) - 掘金 (juejin.cn)