🦄 2024 独立开发者训练营,一起创业!查看介绍 / 立即报名(剩余10个优惠名额) →

单元测试:用 JavaScript 语言演示一下单元测试是什么

test,testing,unit test,测试,单元测试,这些词总是出现在我眼前,下意识地觉得它没那么重要,也会认为它很难搞,所以我总是把它忽略掉。可它们仍然会不断的出现在各种场合,因为陌生,所以见到它难免有些尴尬。终于,我忍不住想认识一下它。原来它很简单,也很重要。

我说的测试,指的是程序设计里的单元测试,一听到单元这两个字儿我就有点懵,因为我会把它跟计算机联系到一块儿,但其实如果把 “单元” 放到现实世界,就非常容易理解,一栋楼分成几个单元,一本书里的内容分成了几个单元。单元测试就是测试程序里的一小部分,比如,有一个单元测试,可以测试一个类里面的某个方法。测试的到底是哪个部分,是你自己决定的。

单元测试里的 “测试”,测试的就是你做的一些断言(assert)。你在测试里可以说明一下你做的断言,比如我的程序在什么情况下,做什么事情的时候,应该怎么样。比如调用 cart 这个类里的 subtotal 这个方法的时候,如果给它的单价是 10 块钱,数量是 3 个,那么这个方法应该返回 30 。这就是我在某个测试里做的断言,如果我做的断言是真的,那这个测试就会通过,如果是假的,就是哪里出了点问题。

单元测试有两个类型,TDD(测试驱动开发),BDD(行为驱动开发),我没太搞明白它们的具体区别。等我整明白了,再回来写点东西。先说一下我现在的理解。TDD 就是由测试驱动的开发,它是一种开发的方法,在写代码之前,你可以先写一个测试,因为你知道你要写的代码应该怎么样,这个测试应该是非常简单的,不需要花很长时间,可能一两分钟,然后运行测试,看着这个测试失败,再去写程序的最简单的逻辑代码,再次运行测试,让之前写的测试通过,然后可以继续去写逻辑代码,不过每次你都要保证你的测试通过。

示例

不同的语言创建单元测试的方法都不太一样,但思路都应该是差不多的。我用 JavaScript 做个例子,宁皓网有个课程《Node.js 测试》(http://ninghao.net/course/4065)您也可以参考一下。不管您主要使用的开发语言是什么,只是您还不了解单元测试是个什么东西,可以跟着这个课程练习一下,或者也可以比照下面的文字来练习。

准备

准备一个项目,名字是 nodejs-test,在下面创建两个目录,一个是 test,这里可以存储自己写的测试,一个是 lib,可以存储为应该写的代码。

cd ~/desktop
mkdir nodejs-test
cd nodejs-test
mkdir test
mkdir lib

安装所需要的模块,mocha 是测试框架,它可以帮助我们创建,组织还有运行测试。chai 是一个断言库,提供了几种风格的断言,可以让我们方便的在测试里做断言。

npm init -y
npm install mocha --save-dev
npm install chai --save-dev

使用编辑器打开项目的目录,再编辑一下 package.json,修改一下 script 下面的 test 的值:

"test": "./node_modules/mocha/bin/mocha"

在项目的根目录下面,可以这样来运行测试:

npm test

会返回:

0 passing (2ms)

表示 0 个测试通过,一共花了 2 毫秒。

写个测试

在 test 目录的下面,创建一个 js 文件,名字可以使用要测试的东西的名字,假设我打算创建一个 Cart 类,测试它的文件的名字可以是 cart.js,cart-sepc.js,cart-test.js。我直接使用 cart.js。

分组

每个测试文件里,可以使用 describe 去对测试进行分组,describe 也可以嵌套使用,你也可以使用它的别名 context 去创建测试的分组,使用 describe 划分的一个测试的分组像这样:

describe('分组的描述', function () {
  // 分组里的测试或其它的分组
})

测试

每个测试可以使用一个 it 方法:

it('测试的描述', function () {
  // 安排与实施测试
})

断言

在测试里你要做一些断言,这些断言就是程序在某个阶段的必要的事实。创建这些断言可以使用一些断言库,比如 Node.js 本身就带 assert 模块,我这里要用的是 chai,它可以让我们创建更有表现力的断言。chai 提供了几种类型的断言,assert,should,expect,使用 should 与 expect 风格的断言更像是一句话,下面是几个例子(来自官方):

var assert = chai.assert;

assert.typeOf(foo, 'string');
assert.equal(foo, 'bar');
assert.lengthOf(foo, 3)
assert.property(tea, 'flavors');
assert.lengthOf(tea.flavors, 3);
var expect = chai.expect;

expect(foo).to.be.a('string');
expect(foo).to.equal('bar');
expect(foo).to.have.length(3);
expect(tea).to.have.property('flavors')
  .with.length(3);
chai.should();

foo.should.be.a('string');
foo.should.equal('bar');
foo.should.have.length(3);
tea.should.have.property('flavors')
  .with.length(3);

写一个测试

在项目的 test 目录下创建的那个测试的文件里,添加下面这段代码:

const chai = require('chai')
const cart = require('../lib/cart')
const expect = chai.expect

var cartTest = new cart()

describe('Cart', function () {
  describe('subtotal', function () {
    it('单价是 10 块钱的 3 件商品小计金额应该是 30 块', function () {
      var subtotal = cartTest.subtotal(10, 3)
      expect(subtotal).to.equal(30)
    })
  })
})

chai 是一个断言库,我们在测试里要使用它的 expect 风格的断言。cart 是一个类,它里面包含了我要在测试里测试的方法,把它导入到测试文件里以后,新建一个实例:

var cartTest = new cart()

在测试里我们用 describe 创建了一个 Cart 测试分组,在它里面又嵌套了一个 describe 分组,在这个分组里,使用 it 方法添加了一个测试。在这个测试里,先执行了一下 subtotal 这个方法,把方法返回的值交给了 subtotal:

var subtotal = cartTest.subtotal(10, 3)

然后使用了 chai 的 expect 风格的断言,意思是我断言上面得到的 subtotal 的值应该等于 30 ,如果测试的时候这个结果不是 30,测试就会失败。

写应用的代码

再去创建要测试的那个 Cart 类。在 lib 目录下创建一个 cart.js 文件,在这个文件里创建一个名字是 Cart 的类,然后再导出这个类,代码如下:

class Cart {}

module.exports = Cart

运行测试看着它失败

在终端下面运行一下测试,执行:

npm test

3F750260-DEA7-4D2C-AF26-1DF5FB9D6A78

会提示:

TypeError: cartTest.subtotal is not a function

意思就是 subtotal 这个方法还没有被定义,打开 lib/cart.js ,给 Cart 类添加一个 subtotal 方法:

class Cart {
  subtotal () {}
}

再执行一下测试,就会提示一个 AssertionError,断言的错误,期望 undefined 等于 30:

 AssertionError: expected undefined to equal 30

BD71536E-FF70-4C66-8C22-345A35098837

编辑应用代码让测试通过

继续编辑 subtotal 方法,给它两个参数,unitPrice 表示商品单价,quantity 表示商品的数量,这个方法返回的值就是单价乘以数量:

class Cart {
  subtotal (unitPrice, quantity) {
    return unitPrice * quantity
  }
}

再次运行测试,这个测试就会通过了,因为我们在测试里对 subtotal 这个方法做的断言是真的。

5A266483-9A17-4140-89EC-A5F52F379754

完整的代码

test/cart.js

const chai = require('chai')
const cart = require('../lib/cart')
const expect = chai.expect

var cartTest = new cart()

describe('Cart', function () {
  describe('subtotal', function () {
    it('单价是 10 块钱的 3 件商品小计金额应该是 30 块', function () {
      var subtotal = cartTest.subtotal(10, 3)
      expect(subtotal).to.equal(30)
    })
  })
})

lib/cart.js

class Cart {
  subtotal (unitPrice, quantity) {
    return unitPrice * quantity
  }
}

module.exports = Cart

package.json

{
  "name": "nodejs-test",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "./node_modules/mocha/bin/mocha"
  },
  "keywords": [],
  "author": "wanghao <wanghao@ninghao.net>",
  "license": "ISC",
  "devDependencies": {
    "chai": "^3.5.0",
    "mocha": "^3.0.2",
    "nock": "^8.0.0"
  }
}

总结

我们用了一个极其简单的例子,演示了程序设计里的单元测试到底是怎么回事。测试这个东西有总比没有强,更重要的是你先要接受它。

评论

Cool! 并不难,徒增代码量,但是比较难以接受。

确实啊

代码量少的时候确实麻烦,当代码量大,代码文件多,类之间关系复杂的时候,测试的重要性就能体现出来了。

先搞明白测试是怎么回事,以后我们再说持续 xx 。

期待持续集成、持续部署、持续交付的内容!

皓哥推出的视频好及时呀,正好“面试的时候需要用到JavaScript的测试,而且连个模块你还都讲了”
Experience with JavaScript unit testing, including Chai and Mocha.

祝你面试成功啊 :)

单元测试非常重要,不管系统大还是小,不写单元测试的程序员,都不是好程序员

微信好友

用微信扫描二维码,
加我好友。

微信公众号

用微信扫描二维码,
订阅宁皓网公众号。

240746680

用 QQ 扫描二维码,
加入宁皓网 QQ 群。

统计

15260
分钟
0
你学会了
0%
完成

社会化网络

关于

微信订阅号

扫描微信二维码关注宁皓网,每天进步一点