Nodejs的测试和测试驱动开发
测试是保证软件质量必不可少的一环。测试有很多形式:手动、自动、单元测试等等。这里我们只聊使用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
Nodejs的测试和测试驱动开发的更多相关文章
- APP测试中的头疼脑热:测试人员如何驱动开发做好自测
如今,随着移动互联网的浪潮越翻越涌,移动APP测试工作的现状已经成了那本"家家难念"的经.不管公司大小,不管测试哪种类型的APP,让广泛测试者苦不堪言的就属重复性最多,测试工作量最 ...
- TDD测试驱动开发
TDD测试驱动开发 一.概念 TDD故名思意就是用测试的方法驱动开发,简单说就是先写测试代码,再写开发代码.传统的方式是先写代码,再测试,它的开发方式与之正好相反. TDD是极限编程的一个最重要的设计 ...
- Android深度探索HAL与驱动开发 第四章 源代码下载和编译
前面说过Android移植主要就是Linux内核的移植,而Linux内核移植主要是Linux驱动的移植,所以为了开发和测试Linux驱动,有必要学习在Ubuntu Linux下如何搭建两套开发环境:A ...
- Android深度探索HAL与驱动开发 第三章 Git入门
Git功能十分复杂,简单来说它使你的开发更为快捷和可控,尤其是在开源项目上展现的友好的交互和回馈. 熟悉一些git指令操作对开发者的帮助可以避免开发者受到一些外在因素打断开发进度,甚至延误项目的che ...
- Scrum敏捷软件开发之技术实践——测试驱动开发TDD
重复无聊的定义 测试驱动开发,英文全称Test-Driven Development,简称TDD,是一种不同于传统软件开发流程的新型的开发方法.它要求在编写某个功能的代码之前先编写测试代码,然后只编写 ...
- 《ServerSuperIO Designer IDE使用教程》-1.标准Modbus和非标准协议的使用、测试以及驱动开发。附:v4.2发布
ServerSuperIO Designer IDE v4.2版本更新内容: 增加ServerSuperIO.Host运行程序,可以使用IDE进行测试,Host为运行环境. 针对设备驱动增加导入监测点 ...
- 使用模拟对象(Mock Object)技术进行测试驱动开发
敏捷开发 敏捷软件开发又称敏捷开发,是一种从上世纪 90 年代开始引起开发人员注意的新型软件开发方法.和传统瀑布式开发方法对比,敏捷开发强调的是在几周或者几个月很短的时间周期,完成相对较小功能,并交付 ...
- 软件工程 - Test-Driven Development (TDD),测试驱动开发
参考 https://baike.baidu.com/item/%E6%B5%8B%E8%AF%95%E9%A9%B1%E5%8A%A8%E5%BC%80%E5%8F%91/3328831?fr=al ...
- python webdriver 测试框架-行为驱动例子
安装行为驱动模块lettuce(卷心菜)模块 pip install lettuce Successfully installed argparse-1.4.0 colorama-0.3.9 extr ...
- 自动化测试架构设计 &&自动化持续集成测试任务实战[线性测试、模块驱动测试、数据驱动测试、关键字驱动测试]
1 为什么设计自动化测试架构 1.1 企业现状分析 压力大:产品需求不明确,上线时间确定,压力山大. 混乱:未立项,开发时间已过半,前期无控制,后期无保障. 疲于应付:开发人员交付的文件质量差,测试跟 ...
随机推荐
- sqlserver数据库命名规则
sqlserver数据库命名规则: (1)第一个字符必须是字母或“_”.“@”.“#” (2)数据库名称不能是T-SQL的保留字 (3)不允许嵌入空格或其他特殊字符
- 多线程的异常处理、线程取消、临时变量、lock
异步多线程的异常,抓不到,因为是在子线程执行. #region 多线程的异常处理.线程取消.临时变量.lock { try { List<Task> list = new List< ...
- c# ?. 空值传播运算符
当左侧为空时不执行右侧代码,避免出现为null的错误,同时也避免了判断是否为null,可以和??一起连用,省了好多事.举例如下: 以前:var res=obj==null?5:obj.a; 现在:va ...
- 用上了Godaddy的美国主机
最近把两个域名转移到Godaddy上面后,就不停地收到它的促销邮件,送一些优惠码打折. 昨天通过它的优惠链接买了一个豪华型空间,支持无限空间,无限域名绑定. 看着邮件上写的是打半折,同时还免费送一个域 ...
- JVM 运行时数据区 (三)
JVM运行时数据区 运行时数据区由 程序计数器.java虚拟机栈.本地方法栈.堆.方法区 组成: 1.程序计数器 每一个Java线程都有一个程序计数器,用于保存程序执行到当前方法的哪一个指令,它是线程 ...
- Python3实战系列之二(获取印度售后数据项目)
问题:续接上一篇.说干咱就干呀,勤勤恳恳写程序呀! 目标:安装python和pycharm.要编写并运行python程序就需要电脑有开发工具和运行环境,所以此篇就是安装编辑和运行python程序的软件 ...
- Axure学习(一)
了解Axure目前的一些评价信息以及可以学习Axure的网址.
- 【UI测试】--独特性
- MongoDB相关记录
win10中zip安装 下载地址:http://dl.mongodb.org/dl/win32/x86_64 首先解压至某文件夹, 使用管理员权限打开cmd或者powershell, 进入指定目录中的 ...
- 2017/2/8 hibernate + oracle 实现id的自增 同时 hibernate项目跑起来 会自己增加字段的原因 oracle触发器的使用
hibernate + oracle 实现id的自增 1.在oracle中先创建一个序列 : 序列语法 如下 create sequence (序列名称)seq_student_id minva ...