之前本来在 12 月就应该写这一篇文章的,大致是同事需要做 SDK 开发,我要求必须要有对应测试,但对于怎么去设计测试比较迷茫,本来早在 19 年就写过一篇如何构造一些有意义的测试,但从某种角度来说这更偏后端一些,对于前端的测试来说有一些不同。
然后被裁了,本来不想写了,但之前帮做模拟面试以及被面试时其实都有提到一些内容,所以这里简单谈下我对前端测试的一些看法。
新读者注意:本人屁话较多,不喜勿喷,上角点叉。(我好脆弱啊哥哥.jpg)
什么时候我们需要测试
如果你的回答是:「当然是什么时候都需要测试」——那么恭喜你,你还没有接受过现实排期和业务的毒打(这里指的是国内互联网的情况)。
理想情况下,我们当然希望「都有测试,测试覆盖率达到 100%」,但是现实往往无法支撑起这个美好的愿景。
在这种情况下我们在什么时候选择编写测试代码呢?比较核心的几个点是:
- 核心、高风险业务内容,比如我 19 年写的文章就是针对支付、会员、订单这一块的后端服务进行的大规模覆盖
- 基础能力提供方,比如基础设施、通用基建,包括本文引子部分的 SDK
- 对于前端来说基础组件也是可以作为测试的项
其实从上面我们就可以看出,除了核心业务是为了止损外,大部分可以优先写测试的部分都是因为整体需求开发相对可控,不至于朝令夕改,同时作为基本盘,服务人数众多,牵一发而动全身,一个 Bug,全家完蛋。
当然实际上很多公司处于 0 测试的状态推进,包括之前在 B 站时我使用的一些内部工具,其实也是没有测试或者是测试覆盖率低的状态。因此也确实在其中埋了不少隐患。
出于大部分公司的迭代速率和需求的不稳定性,对于业务做一些集成测试性价比不高,更多的是如果你有闲心可以给自己的 utils 方法做一些测试。
当然,如果业务稳定、排期宽松,那毫无疑问可以达到你想要的效果。
测试覆盖率
在 19 年的文章中实际上就有关于测试覆盖率的描述,在这里再次重申下,测试需要追求覆盖率,但不要追求覆盖率 100%。
这一点在这次面试中也和面试官讨论过(似乎不止一位),如果没有测试覆盖率指标,那么测试将毫无意义,因为每个人都可以以各种理由偷懒不做测试;但如果过分追求数字,接下来面对的可能是更灾难性的结果:
在个人(开源)项目中自然是想怎么玩都可以的,但在企业开发中我们往往会遇到时间与健壮性的平衡,大部分老板其实只关注「快糙猛」,你先帮我把第一版推上去,剩下的我们之后再说。如果在这种情况下过分追求覆盖率,基本相当于没有人性的强行军,可以先顶一个小目标,比如 80% 覆盖率,而不是 90%-100%。
因为喊个口号容易,但当你实际开始写测试就会发现,要完美覆盖你的 case 而不是只在乎数字的情况下,写测试的时间将是开发时间的 1.5-3 倍,而且是随着覆盖率指标上升指数级上升的,也就是说——有一些犄角旮旯的地方它就是不太好搞,也就是「搞的性价比不高」。
当然如果这些不好搞的地方是核心内容,那就是不得不搞了,这种时候其实更偏向于一劳永逸,也是一种高性价比的投入,这是另一回事。
测试有什么用途
这话说的仿佛是一个废话,你可能会说「测试嘛,当然是为了测试」。但实际上测试为我们提供了以下便利:
- 方便及时发现问题——这是测试本来的功能
- 通过测试集提供用法的 example——可以有效减少你的文档量,少解说冷门 case
- 方便后人接盘代码——当有测试的时候,才会知道你写的是 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(在后端测试时我有用过,因为这是明确必须测试的,剩下的情况下都可能会受到迭代排期影响),大部分情况下我们更多的开发思路还是:
- 我的库需要提供哪些能力
- 这些能力怎么拆分成函数(尽量无副作用)
- 挨个覆盖我 export 的函数
- 对于想到的边缘 case 进行填补
单元测试最难的部分可能是要挨个 mock 你需要的部分,同时为了避免干扰,建议少用全局变量、多写无副作用函数,能让你的开发之旅顺利很多。
对于大部分基建项目来说,单元测试是一件性价比相当高的方式,非常建议大家抽空做一波。(当然,很容易 mock 麻了,比如我卷到最后就卷不下去了,要 mock 太多东西了)。
集成测试
上面说了半天,其实对于单元测试来说,前后端并没有太大区别,前端 mock 浏览器、网络 IO;后端 mock 网络 IO、底层依赖、文件存储,本质上都是 mock + 对函数进行测试。
对于集成测试来说就更为痛苦,在这里我们努力的使用 cypress,如果说单元测试测的是函数,那么集成测试测的就是能力,简化一下上面在单测中介绍的思路:
- 我的库需要提供哪些能力
- 用代码怎么实现
- 覆盖我设计的能力
- 对于想到的边缘 case 进行填补
可以说,如果单元测试还是要你具体代码拆分后再进行测试的设计,那么集成测试从一开始就可以进行设计了——毕竟在系统设计阶段,你已经会敲定「我会提供什么」了。
剩下的可能就是漫长而痛苦的 mock 阶段,甚至你可能需要准备好一个 server 去承载你的测试页面。
总结
对于大部分刚开始写测试的工程师来说,卡点可能更多的在于怎么去取舍和怎么去设计我的测试用例,这里简单的提供一些思路,至于具体库的使用上,网上已经有太多基础教程,而坑就靠 stackoverflow 和 GitHub issues 去填补吧。
而为什么大家都懒得写测试——根据本人实际的测试体验,更多的不是测试,而在于 mock,漫长而痛苦。
展望未来,自然是希望 AI 能够解决我们大部分烦恼的,比如上文说的「根据提供的能力出集成测试」——这是不是有种输入给 AI 就能给我吐代码,我删删改改就能跑的美好错觉呢。
最后: 下方这份完整的软件测试视频学习教程已经整理上传完成,朋友们如果需要可以自行免费领取 【保证100%免费】
这些资料,对于【软件测试】的朋友来说应该是最全面最完整的备战仓库,这个仓库也陪伴上万个测试工程师们走过最艰难的路程,希望也能帮助到你!