单元测试是针对程序模块来进行正确性检验的测试工作,程序单元是应用的小可测试部件。
  在 Web 应用中,我们可以把 Restful API 看作是构成应用的单元。
  Restful API 比较好测试,测试起来也比较简单。
  本文将介绍编写测试的原因和原则,然后以 Node.js 为例子介绍测试 Restful API 的方法。
  为什么要编写测试
  每个开发者都知道单元测试的重要性,但并不是每个开发者都会去编写单元测试。原因也很容易理解:
  · 编写测试需要更多的时间,会拖慢项目进度
  · 编写测试需要写更多的代码,更容易出现错误
  · 当需求更变后,我们要花更多的精力去修改测试代码
  · 有时候测试代码可能会比源代码多几倍
  我以前也抱有类似的想法,认为人工测试足够了,没必要特意去编写单元测试。
  然而随着项目的规模的增大,代码也会慢慢出现意外。
  典型的例子是为了某需求修改了 A 位置,需求完成了,而 B 位置出现了 Bug。
  在读过 王垠大神的《测试的道理》 之后,我更加明白了一个道理:我没有大神般的编码能力,我只能通过单元测试来检验自己的编码。
  除了检验代码的正确性之外,我认为单元测试还有一个很重要的作用:为日后重构项目做准备。
  只要单元测试覆盖得够好,以后重构的时候很容易发现问题,节约大量的时间。
  轮子哥在 知乎 上说过一句很有意思的话:
  所以那些专门写不需要维护的软件的人,讨厌测试,也是情有可原的。
  如果你要编写一个长期维护的软件,那么你好添加单元测试。
  F.I.R.S.T 原则
  当我们决定要编写单元测试之后,我们要考虑怎样 写好 单元测试,换句话说是编写单元测试时需要注意哪些原则。
  那么,有哪些原则是我们需要注意的呢?
  · Fast : 测试必须是快速的
  · Isolated / Independent :
  每个测试都要做 3 A => Arrange(准备), Act(行动), Assert(断言)
  Arrange: 测试过程中用到的数据不能依赖于运行环境,测试中用到的数据应是测试中的一部分
  Act: 调用你想要测试的方法 / API
  Assert: 根据返回结果进行断言
  测试结果不能依赖运行环境
  测试结果不依赖运行测试的顺序
  · Repeatable :
  每个测试必须是可重复执行的,即运行 N 次,会得到 N 次相同的结果
  每个测试的结果不应依赖时间,日期,和随机数的输出
  · Self-validating :
  每个测试都可以自己判断结果来判断测试是否通过
  不需要人类去查阅手册来判断结果
  · Thorough and Timely :
  应该尽量覆盖所有使用场景
  应该尝试测试驱动开发(TDD)
  这是经典的 F.I.R.S.T 原则。
  我们好时刻注意自己编写的单元测试是否遵守这些原则。
  JavaScript 社区里有很多测试框架可以用来编写单元测试,有 ava 、 mocha 、 jasmine 、 tap 等。
  这些测试框架都有提供 beforeEach 、 afterEach API,目的是隔离我们的测试数据,从而满足 Isolated / Independent 和 Repeatable 原则。
  编写单元测试
  假设我们有以下 Restful API (用了 jwt 来做用户验证):
const router = new Router()
// 根据 token 获取用户信息,必须登录
router.get('/user', jwt, user.getSelf)
// 获取用户列表,无需登录
router.get('/users', user.getList)
// 获取指定用户信息,无需登录
router.get('/users/:userId', user.get)
// 创建新用户(用户注册),无需登录
router.post('/users', user.create)
// 更新用户信息,必须登录
router.put('/user', jwt, user.update)
  那么我们应该怎样为这些 Restful API 编写单元测试呢?
  基本流程是:
  · 为 app 创建 http 服务器
  · 对各个 API 发出请求
  · 对响应内容进行断言
  幸运的是,社区里已经有相应的工具让我们可以方便管理这个流程,这个工具是 —— supertest 。
  它提供了非常灵活的 API,足以帮助我们测试 Restful API 了。
  基本用法如下:
  const app = require('../app')
  const request = require('supertest')(app)
  request
  .get('/users')
  .expect(200)
  .end((err, res) => {
  res.body.should.be.an.Array()
  })
  提示
  如果你遇到了 TypeError: app.address is not a function , 请尝试一下以下方法:
  const request = require('supertest').agent(app.listen())