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
会提示:
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
编辑应用代码让测试通过
继续编辑 subtotal 方法,给它两个参数,unitPrice 表示商品单价,quantity 表示商品的数量,这个方法返回的值就是单价乘以数量:
class Cart { subtotal (unitPrice, quantity) { return unitPrice * quantity } }
再次运行测试,这个测试就会通过了,因为我们在测试里对 subtotal 这个方法做的断言是真的。
完整的代码
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! 并不难,徒增代码量,但是比较难以接受。
8 年 2 个月 以前
确实啊
8 年 2 个月 以前
代码量少的时候确实麻烦,当代码量大,代码文件多,类之间关系复杂的时候,测试的重要性就能体现出来了。
8 年 2 个月 以前
先搞明白测试是怎么回事,以后我们再说持续 xx 。
8 年 2 个月 以前
期待持续集成、持续部署、持续交付的内容!
8 年 2 个月 以前
皓哥推出的视频好及时呀,正好“面试的时候需要用到JavaScript的测试,而且连个模块你还都讲了”
Experience with JavaScript unit testing, including Chai and Mocha.
8 年 2 个月 以前
祝你面试成功啊 :)
8 年 2 个月 以前
单元测试非常重要,不管系统大还是小,不写单元测试的程序员,都不是好程序员
7 年 6 个月 以前