简介: 最近在负责一个基于nodejs的应用,在不少方面都经历了一个从无到有的过程,测试也是如此。刚开始时,代码都写很差,更别提测试了,那时测试为0。经历过一段时间后, 测试覆盖率在90%+。这就是个人从0到90。剩下的10,还有很长的路,且待下回分解。javascript
最近在负责一个基于nodejs的应用,在不少方面都经历了一个从无到有的过程,测试也是如此。刚开始时,代码都写很差,更别提测试了,那时测试为0。经历过一段时间后,尤为是看到 npm 上优秀的库的测试覆盖率都在100%时,痛下决心开始学习 nodejs 的测试, 目前这个应用的测试覆盖率在90%+。这就是个人从0到90。剩下的10,还有很长的路,且待下回分解。html
对于开发者,测试的重要性毋庸置疑, 所谓”无测试不上线“,”无测试不重构“等。但在实践的过程当中,测试老是有这样的那样的问题,随便列举下:java
一千我的眼中有至少二千种测试理念或方法,很难定义什么是一种好的测试。在团队一直负责测试的推动,咱们的理念很简单,包括如下几个原则:node
一个好的测试,最重要的衡量标准就是测了多少(覆盖率),75%是最低的标准。这个标准对java来讲基本可行,但对nodejs不太适用,javascript是弱类型的、动态语言,没有编译阶段,不少错误只能在运行时才会被发现,因此须要更高的覆盖率,最好是100%,目前我的的标准是90%+。git
每个测试用例,不管在任何环境下,都应该能够反复执行,并产生相同的结果。只有这样,才可以相任你的测试,进而发现真正的bug。这也是集成测试最低要求。github
一个测试用例只测试代码的某一方面,如一个分支,且不强依赖某些特定的环境或条件。npm
不管是单个测试用例,仍是集成测试,必需要保证测试执行足够的快。json
测试测什么主要依据具体的需求、业务、成本、语言等,但也有必定的共性,单元测试准则 给出了一些准则可供参考,这里再也不讨论。小程序
又是一个很是大的话题,本文仅从我的角度给出nodejs测试的工具及方法。浏览器
Nodejs 测试框架众多,目前使用最广的是Mocha。本文详细说明下 Mocha, 并简要介绍几种其它框架。本文的示例,如无特别说明,都是基于Mocha.
A simple, flexible, fun JavaScript test framework for node.js and the browser.
Mocha 是一个功能丰富的Javascript测试框架,它能运行在Node.js和浏览器中,支持BDD、TDD式的测试。
安装
npm install -g mocha
写一个简的测试用例
var assert = require('chai').assert; describe('Array', function() { describe('#indexOf()', function () { it('should return -1 when the value is not present', function () { assert.equal(-1, [1,2,3].indexOf(5)); assert.equal(-1, [1,2,3].indexOf(0)); }); }); });
运行
$ mocha
输出结果以下:
.
1 test complete (1ms)
复制代码
Mocha 容许你使用任何你想用的断言库,包括:
Mocha 提供了 before()
, after()
, beforeEach()
, afterEach()
等 hooks,用于设置前置条件及清理测试,示例以下:
describe('hooks', function() {
before(function() {
// runs before all tests in this block
})
after(function(){
// runs after all tests in this block
})
beforeEach(function(){
// runs before each test in this block
})
afterEach(function(){
// runs after each test in this block
})
// test cases
})
复制代码
专用测试容许只测试指定的测试集或用例,只需在测试集或用例前加.only()
,示例以下:
describe('Array', function(){
describe.only('#indexOf()', function(){
...
})
})
复制代码
跳过相似于 junit 的 @Ignore
, 用于跳过或忽略指定的测试集或用例,只需在测试集或用例前加.skip()
,示例以下:
describe('Array', function(){
describe.skip('#indexOf()', function(){
...
})
})
复制代码
除了使用 Mocha 提供的命令行外,还可使用 编辑器插件 运行,目前支持了:
以JetBrains为例,JetBrain 为其 IDE 套件(IntelliJ IDEA, WebStorm等)提供了 NodeJS, 能够直接运行或调试 mocha 测试用例。基本步骤:
NodeJS
的插件并安装;Edit Configuration
添加 Mocha
, 示例以下:如下列举几个基于 nodejs 的测试框架或工具,它们可用于 nodejs 或 浏览器端javascript 代码的测试,不在本文讨论范围,详见官方文档。
A Behavior Driven Development JavaScript testing framework
Jasmine is a behavior-driven development framework for testing JavaScript code. It does not depend on any other JavaScript frameworks. It does not require a DOM. And it has a clean, obvious syntax so that you can easily write tests.
更多见 官方文档.
To bring a productive testing environment to developers
The main goal for Karma is to bring a productive testing environment to developers. The environment being one where they don't have to set up loads of configurations, but rather a place where developers can just write the code and get instant feedback from their tests.
更多见 官方文档.
mocha 提供了测试的基本框架,在特定的场景下的测试还须要其它工具作以辅助,这个列举几个经常使用的工具。
SuperTest 提供对 HTTP 测试的高度抽象, 极大地简化了基于 HTTP 的测试。
The motivation with this module is to provide a high-level abstraction for testing HTTP, while still allowing you to drop down to the lower-level API provided by super-agent.
$ npm install supertest --save-dev
复制代码
简单的 HTTP 请求
var request = require('supertest');
describe('GET /user', function() { it('respond with json', function(done) { request(app) .get('/user') .set('Accept', 'application/json') .expect('Content-Type', /json/) .expect(200, done); }); });
上传文件
request(app) .post('/') .field('name', 'my awesome avatar') .attach('avatar', 'test/fixtures/homeboy.jpg') // ..
修改响应头或体
describe('GET /user', function() { it('user.name should be an case-insensitive match for "tobi"', function(done) { request(app) .get('/user') .set('Accept', 'application/json') .expect(function(res) { res.body.id = 'some fixed id'; res.body.name = res.body.name.toUpperCase(); }) .expect(200, { id: 'some fixed id', name: 'TOBI' }, done); }); });
更多见 文档
代码覆盖(Code coverage)是软件测试中的一种度量,描述程式中源代码被测试的比例和程度,所得比例称为代码覆盖率。它有四个测量维度:
Istanbul 是 JavaScript 语言最流行的代码覆盖率工具。
安装
$ npm install -g istanbul
运行
最简单的方式:
$ cd /path/to/your/source/root
$ istanbul cover test.js
复制代码
输出运行结果:
..
test/app/util/result.test.js
should static create
should be success
should be static success
should be error
should be static error
299 passing (13s)
[mochawesome] Report saved to /opt/source/node_modules/.mochawesome-reports/index.html
=============================== Coverage summary ===============================
Statements : 92.9% ( 1505/1620 )
Branches : 85.42% ( 410/480 )
Functions : 94.33% ( 133/141 )
Lines : 93.01% ( 1504/1617 )
================================================================================
Done
复制代码
这条命令同时还生成了一个 coverage
子目录,其中的 coverage.json
文件包含覆盖率的原始数据,coverage/lcov-report
是能够在浏览器打开的覆盖率报告,以下图所示:
常见的测试模式包括TDD, BDD。
测试驱动开发是敏捷开发中的一项核心实践和技术,也是一种设计方法论。TDD的原理是在开发功能代码以前,先编写单元测试用例代码,测试代码肯定须要编写什么产品代码。TDD的基本思路就是经过测试来推进整个开发的进行,但测试驱动开发并不仅是单纯的测试工做,而是把需求分析,设计,质量控制量化的过程。总体流程以下:
以一个阶乘的小程序为例。先写出的测试用例,以下所示。此时运行确定会报错,由于被测试的程序尚未写。
var assert = require('assert'),
factorial = require('../index');
suite('Test', function (){
suite('#factorial()', function (){
test('equals 1 for sets of zero length', function (){
assert.equal(1, factorial(0));
});
test('equals 1 for sets of length one', function (){
assert.equal(1, factorial(1));
});
test('equals 2 for sets of length two', function (){
assert.equal(2, factorial(2));
});
test('equals 6 for sets of length three', function (){
assert.equal(6, factorial(3));
});
});
});
复制代码
开始写阶乘的逻辑,以下所示。
module.exports = function (n) {
if (n < 0) return NaN;
if (n === 0) return 1;
return n * factorial(n - 1);
};
复制代码
此时再运行以前的测试用例,看其是否经过,若是所有经过则表示开发完成,如不经过则反复修改被测试的逻辑,直到所有的经过为止。
行为驱动开发是一种敏捷软件开发的技术,它鼓励软件项目中的开发者、QA和非技术人员或商业参与者之间的协做。主要是从用户的需求出发,强调系统行为。其最显著的特征是,经过编写行为和规格说明来驱动软件开发。行为和规格说明看上去与测试十分类似,但它们以前仍是有显著的不一样。
仍是以上面的阶乘为例,BDD模式的测试用例以下:
var assert = require('assert'),
factorial = require('../index');
describe('Test', function (){
before(function(){
// Stuff to do before the tests, like imports, what not
});
describe('#factorial()', function (){
it('should return 1 when given 0', function (){
factorial(0).should.equal(1);
});
it('should return 1 when given 1', function (){
factorial(1).should.equal(1);
});
it('should return 2 when given 2', function (){
factorial(2).should.equal(2);
});
it('should return 6 when given 3', function (){
factorial(3).should.equal(6);
});
});
after(function () {
// Anything after the tests have finished
});
});
复制代码
从示例上看,BDD的测试用例与TDD最大的不一样在于其措辞,BDD读起来更像是一个句子。所以BDD的测试用例可做为开发者、QA和非技术人员或商业参与者之间的协同工具,对开发者来讲,若是你能够很是流畅地读测试用例,天然可以写出更好、更全面的测试。
TDD和BDD本质和目标都是一致的。只是在实施方法上,进行了不一样的探讨来完善整个敏捷开发的体系。TDD的迭代反复验证是敏捷开发的保障,但没有明确如何根据设计产生测试,并保障测试用例的质量,而BDD倡导你们都用简洁的天然语言描述系统行为的理念,刚好弥补了测试用例(即系统行为)的准确性。
几乎全部基于Nodejs的库或应用都选择了BDD, 至于为何,我尚未搞明白。
目前比较流行的断言库包括:
这些断言库除了风格上略有不一样外,实如今功能几乎彻底一致,可根据我的喜爱或应用需求选择。