# 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&#32;-&#32;Geek&#32;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)。