# Node 单元测试 ## 为什么做这个事情 我在我写的文章里面多次提到单元测试的重要性。重要的事情说三遍“单测很重要”、“单测很重要”、“单测很重要”。 单纯说这句话没公信力和权威性,那我举例子来说明吧。 场景1 某业务线在不断的版本迭代,在版本6的时候发现功能 A 的代码太乱太多了。小刘同学打算重构,他辛辛苦苦解决后,打算提交给测试工程师进行测试。测试工程师说“小刘,你这个代码全是 Bug 呀,我点进去就 Crash”。小刘听到后很尴尬,心里想“功能 A 的代码我写的很小心,一行行检查过去的,不可能有问题”。测试说“我点击商品页,点击加入购物车,马上 Crash 了”。你这个测试被打回,阻塞主流程了。小刘想了想才发现他自己开发的模块是没有问题的,但是他在重构功能 A 的时候不小心把依赖功能 A 的地方少传递了配置参数 😅 场景2 某公司基础普通组有小刘同学,在设计某个新技术方案的时候,辛辛苦苦花了3天时间出了技术方案,他去找老板聊,老板让他把思路描述下,再把设计的测试用例给一下。小刘吞吞吐吐讲完了设计思路,但是他说还没有设计测试用例。老板说你没测试用例,我怎么 review 你的设计,一行行看代码理解逻辑吗?一句句听你的设计判断有没有问题吗?以后你找我听技术设计,你理好设计思路,和测试用例。我看看主流程和一些边界的输入输出,确保这些东西正确那就是没问题的。我没有那么多时间一行行看代码。(说的也是,组长是 P9 忙得很) 场景3 某公司基础普通组有小刘同学在做了移动端的 APM 监控和数据上报 SDK 的第一版,但是他很乖,写好了单元测试。忘了说了小刘同学是负责 iOS 端的,同事做 Android 端的小张同学和他对应,不同就是他没有写单元测试的代码。 需求下来了,需要迭代版本2,小刘和小张都开发好代码了,需要进行测试。哈哈哈小刘乐坏了,他花了0.5天就测试结束,小张花了1.5天进行测试。为什么呢?小刘写代码都要写单元测试代码,小张不写。虽然写单元测试代码可能需要花一点点时间。但是当新版本迭代的时候就不需要回头继续**全量测试**。他只要按下 `Command + U`, Xcode 就会跑单元测试相关的代码。 那单元测试的好处?😂 什么?你还问好处,上面那么清晰明了,那我再总结下: - 确保你写的代码的每个分支都可以被覆盖,防止线上代在用户端,不小心执行到未知的分支里面 - 在进行新版本迭代或者重构的时候,可以集中精力到新逻辑里面,旧的逻辑可以用 UT 测试覆盖率来确保 - 在团队内进行 code review 或者 merge review 的时候,review 的人可以看代码中主要逻辑,结合 ut 来判断。 另外,测试来说一般是结合 CI、CD 的,像我们公司有自己的工具 cli 工具, iOS、Android、RN、React、Vue、Node 项目都一起处理,分析依赖、打包构建(打包系统根据工程特点调度特定打包机)、测试、hot fix、埋点统计、APM等等。 所以如果公司规模小,就写好 UT 然后结合 lint 做一些处理,公司规模大、开发有能力则需要结合 ci、cd 将测试的能力结合进去。 另外 UT 是一道工序,最好在每个开发者写代码的时候做 MR,团队内 MR +1 数大于3才可以合并到分支,且 +1 的人里面必须有一个同一个项目的同学,必须有一个同技术栈且比你高水平的人,MR 指出的问题修改好才可以合并。且 MR 代码不能太大,因为太大,给你做 MR 的同学会很耗费精力。人家阅读你代码时间成本太大。 ## Node 侧如何进行 UT 开发 Node 在大学三年级的时候就听说了,也写过,之前也用来写过爬虫、自动化脚本、cli 等,之前学习过如何在 Node 侧写 ut,这篇文章用来总结下。 举个例子,有个 Node 工程,一个模块的主要功能是获取该目录下的所有文件。开发代码如下 ```javascript // fetchCodeFiles.js const fs = require('fs-extra'), glob = require('glob') const fetchCodeFiles = async (dirPath) => { return new Promise((reslove, reject) => { glob('**/*.?(sh|pch|json|xcconfig|mm|cpp|h|m)', { root: dirPath, cwd: dirPath, realpath: true }, (err, files) => { if (err) reject(err) reslove(files) }) }); } module.exports = fetchCodeFiles ``` 单元测试该怎么做? 1. 在终端下切换到当前工程目录,安装 `npm install yamljs --save` 2. 在工程根目录下新建 `test` 文件夹 3. 为你需要的开发文件写测试代码。文件命名建议 `模块名称.test.js` 4. 测试代码需要引入 `assert`。 5. 通过 `describe` 方法、 `it` 方法、`assert` 方法描写测试代码 6. 为了方便测试,在 `package.json` 文件中的 `scripts` 下添加描述 `"test": "mocha"` 7. 为了更方便,我使用的是 iterm2,在 .zshrc 文件里设置别名 `alias nt="node test"` 提升效率的配置 .zshrc 可以查看文章: [Mac 终端效率神技](./../Chapter7 - Geek Talk/7.10.md) 上面开发代码的测试代码如下: ```javascript // fetchCodeFiles.test.js const fetchCodeFiles = require('./../src/fetchCodeFiles'), assert = require('assert') describe("fetch all code files", () => { describe("fetch all code files", () => { it("should return 12 when code directory is '/Users/liubinpeng/Workspace/search_key/test/code'", (done) => { done(assert(fetchCodeFiles('/Users/liubinpeng/Workspace/search_key/test/code')) === 12) }) it("should return 4 when code directory is '/Users/liubinpeng/Workspace/search_key/test/code/Classes'", (done) => { done(assert(fetchCodeFiles('/Users/liubinpeng/Workspace/search_key/test/code/Classes')) === 4) }) it("should return 0 when code directory is '/Users/liubinpeng/Workspace/search_key/test/code/EmptyCodeFiles'", (done) => { done(assert(fetchCodeFiles('/Users/liubinpeng/Workspace/search_key/test/code/EmptyCodeFiles')) === 0) }) }) }) ``` ```json // package.json { "name": "search", "version": "1.0.0", "description": "des", "main": "index.js", "scripts": { "test": "mocha", "start": "node src/index.js" }, "author": "", "license": "ISC", "dependencies": { "fs-extra": "^8.1.0", "glob": "^7.1.6", "yamljs": "^0.3.0" }, "devDependencies": { "mocha": "^6.2.2" } } ``` 运行结果 ![测试结果](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/2019-12-16-nodeUT.png) ## Mocha 本文主要想说明的是 UT 的重要性,以及 Node 测如何做单元测试。当然 UT 没这么简单,具体深入的不是本文的终点,感兴趣的可以查看 Mocha 这个项目的[官方文档](https://mochajs.org)。