测试是保证软件质量必不可少的一环。测试有很多形式:手动、自动、单元测试等等。这里我们只聊使用Mocha这个框架在Nodejs中实现单元测试。单元测试是测试等重要组成,这样的测试只对于一个方法,这样的一小段代码,实施有针对的测试。
这里会逐步深入的讲解单元测试。首先是最简单的单元测试,没有外部依赖,只有简单的输入。接着是实用Sino框架实现stub等有依赖的测试。最后讲解如何单元测试异步代码。
安装Mocha 和Chai
安装Mocha:
npm install mocha -g
Mocha和其他的javascript单元测试框架,如:jasmine和QUnit不同,他没有assertion库。但是,Mocha允许你实用你自己的。最流行的Assertion库有should.js、expect.js和Chai,当然Nodejs内置的也可以使用。这里我们用Chai。
首先创建一个package.json并安装Chai:
touch package.json
echo {} > package.json
npm install chai --save-dev
Chai包含三种assertion方式:should方式、expect方式和assert方式。个人喜欢expect式的,所以下面就使用这个方式了。
第一个Test
第一个例子,我们用测试驱动开发(TDD)的方式创建一个CartSummary
的构造函数,这个函数会用来计算购物车的商品总数。测试驱动开发就是在实现功能之前先写单元测试,这样来驱动你设计可以与测试相适应的代码。
测试驱动开发的步骤:
- 写一个测试,并且这个测试会失败。
- 写最少的代码来使整个测试可以通过。
- 重复。
来看代码:
// tests/part1/cart-summary-test.js
var chai = require('chai');
var expect = chai.expect; // we are using the "expect" style of Chai
var CartSummary = require('./../../src/part1/cart-summary');
describe('CartSummary', function() {
it('getSubtotal() should return 0 if no items are passed in', function() {
var cartSummary = new CartSummary([]);
expect(cartSummary.getSubtotal()).to.equal(0);
});
});
describe
方法是用来创建一组测试的,并且可以给这一组测试一个描述。一个测试就用一个it方法。it方法的第一个参数是一个描述。第二个参数是一个包含一个或者多个assertion的方法。
运行测试只需要在项目的根目录运行命令行:mocha tests --recursive --watch
。recursive指明会找到根目录下的子目录的测试代码并运行。watch则表示Mocha
会监视源代码和测试代码的更改,每次更改之后重新测试。
我们测试不过,因为还没有完成功能代码。添加代码:
// src/part1/cart-summary.js
function CartSummary() {}
CartSummary.prototype.getSubtotal = function() {
return 0;
};
module.exports = CartSummary;
测试就可以通过了:
下一个测试:
it('getSubtotal() should return the sum of the price * quantity for all items', function() {
var cartSummary = new CartSummary([{
id: 1,
quantity: 4,
price: 50
}, {
id: 2,
quantity: 2,
price: 30
}, {
id: 3,
quantity: 1,
price: 40
}]);
expect(cartSummary.getSubtotal()).to.equal(300);
});
这个测试时失败的。。。
下面就来修改代码,让测试通过:
// src/part1/cart-summary.js
function CartSummary(items) {
this._items = items;
}
CartSummary.prototype.getSubtotal = function() {
if (this._items.length) {
return this._items.reduce(function(subtotal, item) {
return subtotal += (item.quantity * item.price);
}, 0);
}
return 0;
};
Stub和Sinon
假设我们现在需要给CartSummary添加getTax方法。最终的使用看起来是这样的:
var cartSummary = new CartSummary([ /* ... */ ]);
cartSummary.getTax('NY', function() {
// executed when the tax API request has finished
});
getTax
方法会使用量外的一个tax模块,包含一个calculate
的方法。虽然我们还没有实现tax模块,但是我们还是可以完成getTax
的测试。该怎么做呢?
首先,安装Sinon:
npm install --save-dev sinon
安装Sinon之后,我们就可以给出tax.calculate的定义了:
// src/part1/tax.js
module.exports = {
calculate: function(subtotal, state, done) {
// implemented later or in parallel by our coworker
}
};
创建完成tax.calculate之后就可以使用Sinon的魔法了。用Sinon给出一个tax.calculate的零时实现。这个零时的实现就是Stub(也叫做桩)。代码:
// tests/part1/cart-summary-test.js
// ...
var sinon = require('sinon');
var tax = require('./../../src/part1/tax');
describe('getTax()', function() {
beforeEach(function() {
sinon.stub(tax, 'calculate', function(subtotal, state, done) {
setTimeout(function() {
done({
amount: 30
});
}, 0);
});
});
afterEach(function() {
tax.calculate.restore();
});
it('get Tax() should execute the callback function with the tax amount', function(done) {
var cartSummary = new CartSummary([{
id: 1,
quantity: 4,
price: 50
}, {
id: 2,
quantity: 2,
price: 30
}, {
id: 3,
quantity: 1,
price: 40
}]);
cartSummary.getTax('NY', function(taxAmount) {
expect(taxAmount).to.equal(30);
done();
});
});
});
上面已经使用Sinon创建stub
方法了。这里再细讲一下。使用sinon.stub方法创建Stub:
var stub = sinon.stub(object,'method', func);
给object添加一个名称为method(第二个参数)的方法,方法体的实现在第三个参数中给出。
上例中使用的方法体:
function(subtotal, state, done) {
setTimeout(function() {
done({
amount: 30
});
}, 0);
}
setTimeout
方法是用来模拟真实环境的,在实际使用的时候肯定会有一个异步的网络请求来请求tax服务。方法体的替换在beforeEach
里,这些代码会在测试开始之前执行。在所有测试完成之后调用afterEach
,并把tax.calculate
恢复到原来的模样。
上面的例子也展示了如何测试异步代码。在it
方法中指明一个参数(上例使用的是done
)。Mocha会传入一个方法,并等待异步代码返回再结束测试。当然,这个等待是由超时时间的,一般是2000毫秒。如果异步代码的测试,没有按照上面的方法写的话,那么所有的测试都会通过。
Sinon的"间谍"
Sinon的间谍(spy)是用来完成另外一种替身测试的(test double),它可以用来记录方法调用。包括方法的调用次数、调用的时候的参数是什么样的以及是否抛出异常。下面就是更新后的测试:
it('getTax() should execute the callback function with the tax amount', function(done) {
var cartSummary = new CartSummary([
{
id: 1,
quantity: 4,
price: 50
},
{
id: 2,
quantity: 2,
price: 30
},
{
id: 3,
quantity: 1,
price: 40
}
]);
cartSummary.getTax('NY', function(taxAmount) {
expect(taxAmount).to.equal(30);
expect(tax.calculate.getCall(0).args[0]).to.equal(300);
expect(tax.calculate.getCall(0).args[1]).to.equal('NY');
done();
});
});
在测试中添加了两个expect。getCall用来获取tax.calculate的第一次调用的第一个参数值,第二个getCall用来获取tax.calculate的第一次调用的第二个参数。主要可以用来检测被测试方法的参数是否正确。
总结
在本文中探讨了如何在Node中使用Mocha以及Chai和Sinon实现单元测试。希望各位喜欢。
原文地址:https://www.codementor.io/nodejs/tutorial/unit-testing-nodejs-tdd-mocha-sinon