对于前端测试的一点杂谈

之前本来在 12 月就应该写这一篇文章的,大致是同事需要做 SDK 开发,我要求必须要有对应测试,但对于怎么去设计测试比较迷茫,本来早在 19 年就写过一篇如何构造一些有意义的测试,但从某种角度来说这更偏后端一些,对于前端的测试来说有一些不同。
然后被裁了,本来不想写了,但之前帮做模拟面试以及被面试时其实都有提到一些内容,所以这里简单谈下我对前端测试的一些看法。
新读者注意:本人屁话较多,不喜勿喷,上角点叉。(我好脆弱啊哥哥.jpg)

什么时候我们需要测试

如果你的回答是:「当然是什么时候都需要测试」——那么恭喜你,你还没有接受过现实排期和业务的毒打(这里指的是国内互联网的情况)。

理想情况下,我们当然希望「都有测试,测试覆盖率达到 100%」,但是现实往往无法支撑起这个美好的愿景。

在这种情况下我们在什么时候选择编写测试代码呢?比较核心的几个点是:

  1. 核心、高风险业务内容,比如我 19 年写的文章就是针对支付、会员、订单这一块的后端服务进行的大规模覆盖
  2. 基础能力提供方,比如基础设施、通用基建,包括本文引子部分的 SDK
  3. 对于前端来说基础组件也是可以作为测试的项

其实从上面我们就可以看出,除了核心业务是为了止损外,大部分可以优先写测试的部分都是因为整体需求开发相对可控,不至于朝令夕改,同时作为基本盘,服务人数众多,牵一发而动全身,一个 Bug,全家完蛋。

当然实际上很多公司处于 0 测试的状态推进,包括之前在 B 站时我使用的一些内部工具,其实也是没有测试或者是测试覆盖率低的状态。因此也确实在其中埋了不少隐患。

出于大部分公司的迭代速率和需求的不稳定性,对于业务做一些集成测试性价比不高,更多的是如果你有闲心可以给自己的 utils 方法做一些测试。

当然,如果业务稳定、排期宽松,那毫无疑问可以达到你想要的效果。

测试覆盖率

在 19 年的文章中实际上就有关于测试覆盖率的描述,在这里再次重申下,测试需要追求覆盖率,但不要追求覆盖率 100%。

这一点在这次面试中也和面试官讨论过(似乎不止一位),如果没有测试覆盖率指标,那么测试将毫无意义,因为每个人都可以以各种理由偷懒不做测试;但如果过分追求数字,接下来面对的可能是更灾难性的结果:

在个人(开源)项目中自然是想怎么玩都可以的,但在企业开发中我们往往会遇到时间与健壮性的平衡,大部分老板其实只关注「快糙猛」,你先帮我把第一版推上去,剩下的我们之后再说。如果在这种情况下过分追求覆盖率,基本相当于没有人性的强行军,可以先顶一个小目标,比如 80% 覆盖率,而不是 90%-100%。

因为喊个口号容易,但当你实际开始写测试就会发现,要完美覆盖你的 case 而不是只在乎数字的情况下,写测试的时间将是开发时间的 1.5-3 倍,而且是随着覆盖率指标上升指数级上升的,也就是说——有一些犄角旮旯的地方它就是不太好搞,也就是「搞的性价比不高」。

当然如果这些不好搞的地方是核心内容,那就是不得不搞了,这种时候其实更偏向于一劳永逸,也是一种高性价比的投入,这是另一回事。

测试有什么用途

这话说的仿佛是一个废话,你可能会说「测试嘛,当然是为了测试」。但实际上测试为我们提供了以下便利:

  1. 方便及时发现问题——这是测试本来的功能
  2. 通过测试集提供用法的 example——可以有效减少你的文档量,少解说冷门 case
  3. 方便后人接盘代码——当有测试的时候,才会知道你写的是 Bug 还是 feature

实际上一个最简单的集成测试本身就是一个 Quick Start,可以直接贴测试用,而我们通常会同时加上注释,巧了,代码的可读性又上升了。

当然这里偶尔也会遇到让人无语的情况:接受的同学改代码连带着测试一起改了——可能是另一种正确吧。建议大家动测试之前一定要看清楚测试代码的含义和价值。

前端的测试

后端的测试在文章开头已经有传送门了,本文重点还是说下前端的测试,常见的测试还是单元测试和集成测试。

这里以埋点库 SDK 为例,因为 UI 业务上的单元测试发挥空间比较有限,相对的费力不讨好一些,前端 SDK 的 case 更典型(简单)且全面。

单元测试

说起单元测试,我们最先想到的肯定是 jest,老牌测试了,关于怎么使用可以看官方文档和各种文章,这里跳过这一趴;此外,我们最后的遗作 SDK 已经使用了 Vitest,理论上应该是比 jest 更好用一些的。

目录结构来说可以是一个 src 文件对应一个 test 文件,这样更加一目了然一些:

├── src
│   ├── index.ts
│   ├── trackers
│   │   └── web
│   │       ├── click.ts
│   │       ├── expose.ts
│   │       └── pv.ts
│   ├── typings
│   │   └── window.d.ts
│   └── utils
│       ├── buvid.ts
│       ├── format.ts
│       ├── http.ts
│       └── utils.ts
├── test
│   └── unit
│       ├── index.test.ts
│       ├── trackers
│       │   └── web
│       │       ├── click.test.ts
│       │       ├── expose.test.ts
│       │       └── pv.test.ts
│       ├── tsconfig.json
│       └── utils
│           ├── buvid.test.ts
│           ├── format.test.ts
│           └── utils.test.ts

这里有几个值得讨论的点:

首先是我们是否要使用 TDD,TDD 是一个非常老的话题,甚至早在我还没工作的时候就写过一篇文章(2016 年,看看得了,勿挖坟):Node.js 用Mocha+Chai做单元测试 入门,但是在实际工作中碍于文章开头提到的原因,我们很少去真正使用完全的 TDD(在后端测试时我有用过,因为这是明确必须测试的,剩下的情况下都可能会受到迭代排期影响),大部分情况下我们更多的开发思路还是:

  1. 我的库需要提供哪些能力
  2. 这些能力怎么拆分成函数(尽量无副作用)
  3. 挨个覆盖我 export 的函数
  4. 对于想到的边缘 case 进行填补

单元测试最难的部分可能是要挨个 mock 你需要的部分,同时为了避免干扰,建议少用全局变量、多写无副作用函数,能让你的开发之旅顺利很多。

对于大部分基建项目来说,单元测试是一件性价比相当高的方式,非常建议大家抽空做一波。(当然,很容易 mock 麻了,比如我卷到最后就卷不下去了,要 mock 太多东西了)。

集成测试

上面说了半天,其实对于单元测试来说,前后端并没有太大区别,前端 mock 浏览器、网络 IO;后端 mock 网络 IO、底层依赖、文件存储,本质上都是 mock + 对函数进行测试。

对于集成测试来说就更为痛苦,在这里我们努力的使用 cypress,如果说单元测试测的是函数,那么集成测试测的就是能力,简化一下上面在单测中介绍的思路:

  1. 我的库需要提供哪些能力
  2. 用代码怎么实现
  3. 覆盖我设计的能力
  4. 对于想到的边缘 case 进行填补

可以说,如果单元测试还是要你具体代码拆分后再进行测试的设计,那么集成测试从一开始就可以进行设计了——毕竟在系统设计阶段,你已经会敲定「我会提供什么」了。

剩下的可能就是漫长而痛苦的 mock 阶段,甚至你可能需要准备好一个 server 去承载你的测试页面。

总结

对于大部分刚开始写测试的工程师来说,卡点可能更多的在于怎么去取舍和怎么去设计我的测试用例,这里简单的提供一些思路,至于具体库的使用上,网上已经有太多基础教程,而坑就靠 stackoverflow 和 GitHub issues 去填补吧。

而为什么大家都懒得写测试——根据本人实际的测试体验,更多的不是测试,而在于 mock,漫长而痛苦。

展望未来,自然是希望 AI 能够解决我们大部分烦恼的,比如上文说的「根据提供的能力出集成测试」——这是不是有种输入给 AI 就能给我吐代码,我删删改改就能跑的美好错觉呢。

植入部分

如果您觉得文章不错,可以通过赞助支持我。

如果您不希望打赏,也可以通过关闭广告屏蔽插件的形式帮助网站运作。

标签: 知识

仅有一条评论

  1. 除了这两个测试之外,是不是可以在生产(prod)环境加Smoke testing来检验关键功能?

添加新评论