Nodejs的测试和测试驱动开发

时间:2020-12-16 07:05:44

测试是保证软件质量必不可少的一环。测试有很多形式:手动、自动、单元测试等等。这里我们只聊使用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的构造函数,这个函数会用来计算购物车的商品总数。测试驱动开发就是在实现功能之前先写单元测试,这样来驱动你设计可以与测试相适应的代码。

测试驱动开发的步骤:

  1. 写一个测试,并且这个测试会失败。
  2. 写最少的代码来使整个测试可以通过。
  3. 重复。

来看代码:

// 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 --watchrecursive指明会找到根目录下的子目录的测试代码并运行。watch则表示Mocha会监视源代码和测试代码的更改,每次更改之后重新测试。

Nodejs的测试和测试驱动开发

我们测试不过,因为还没有完成功能代码。添加代码:

// src/part1/cart-summary.js
function CartSummary() {} CartSummary.prototype.getSubtotal = function() {
return 0;
}; module.exports = CartSummary;

测试就可以通过了:

Nodejs的测试和测试驱动开发

下一个测试:

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