## 目标
建立一个 lesson6 项目,在其中编写代码。
main.js: 其中有个 fibonacci 函数。fibonacci 的介绍见:[http://en.wikipedia.org/wiki/Fibonacci_number](http://en.wikipedia.org/wiki/Fibonacci_number) 。
此函数的定义为 `int fibonacci(int n)`
* 当 n === 0 时,返回 0;n === 1时,返回 1;
* n > 1 时,返回 `fibonacci(n) === fibonacci(n-1) + fibonacci(n-2)`,如 `fibonacci(10) === 55`;
* n 不可大于10,否则抛错,因为 Node.js 的计算性能没那么强。
* n 也不可小于 0,否则抛错,因为没意义。
* n 不为数字时,抛错。
test/main.test.js: 对 main 函数进行测试,并使行覆盖率和分支覆盖率都达到 100%。
## [](https://github.com/alsotang/node-lessons/tree/master/lesson6#知识点)知识点
1. 学习使用测试框架 mocha : [http://mochajs.org/](http://mochajs.org/)
2. 学习使用断言库 should : [https://github.com/tj/should.js](https://github.com/tj/should.js)
3. 学习使用测试率覆盖工具 istanbul : [https://github.com/gotwarlost/istanbul](https://github.com/gotwarlost/istanbul)
4. 简单 Makefile 的编写 : [http://blog.csdn.net/haoel/article/details/2886](http://blog.csdn.net/haoel/article/details/2886)
## [](https://github.com/alsotang/node-lessons/tree/master/lesson6#课程内容)课程内容
首先,作为一个 Node.js 项目,先执行 `npm init` 创建 package.json。
其次,建立我们的 main.js 文件,编写 `fibonacci` 函数。
~~~
var fibonacci = function (n) {
if (n === 0) {
return 0;
}
if (n === 1) {
return 1;
}
return fibonacci(n-1) + fibonacci(n-2);
};
if (require.main === module) {
// 如果是直接执行 main.js,则进入此处
// 如果 main.js 被其他文件 require,则此处不会执行。
var n = Number(process.argv[2]);
console.log('fibonacci(' + n + ') is', fibonacci(n));
}
~~~
OK,这只是个简单的实现。
我们可以执行试试
`$ node main.js 10`
[![](https://raw.githubusercontent.com/alsotang/node-lessons/master/lesson6/1.png)](https://box.kancloud.cn/2015-08-03_55bf0d44d55de.png)
嗯,结果是 55,符合预期。
接下来我们开始测试驱动开发,现在简单的实现已经完成,那我们就对它进行一下简单测试吧。
我们先得把 main.js 里面的 fibonacci 暴露出来,这个简单。加一句
`exports.fibonacci = fibonacci;`(要是看不懂这句就去补补 Node.js 的基础知识吧)
就好了。
然后我们在 `test/main.test.js` 中引用我们的 main.js,并开始一个简单的测试。
~~~
// file: test/main.test.js
var main = require('../main');
var should = require('should');
describe('test/main.test.js', function () {
it('should equal 55 when n === 10', function () {
main.fibonacci(10).should.equal(55);
});
});
~~~
把测试先跑通,我们再讲这段测试代码的含义。
装个全局的 mocha: `$ npm install mocha -g`。
`-g` 与 非`-g` 的区别,就是安装位置的区别,g 是 global 的意思。如果不加的话,则安装 mocha 在你的项目目录下面;如果加了,则这个 mocha 是安装在全局的,如果 mocha 有可执行命令的话,那么这个命令也会自动加入到你系统 $PATH 中的某个地方(在我的系统中,是这里 `/Users/alsotang/.nvm/v0.10.29/bin`)
在 lesson6 目录下,直接执行
`$ mocha`
输出应如下
[![](https://raw.githubusercontent.com/alsotang/node-lessons/master/lesson6/2.png)](https://box.kancloud.cn/2015-08-03_55bf0d4c0eece.png)
那么,代码中的 describe 和 it 是什么意思呢?其实就是 BDD 中的那些意思,把它们当做语法来记就好了。
大家来看看 nodeclub 中,关于 topicController 的测试文件:
[https://github.com/cnodejs/nodeclub/blob/master/test/controllers/topic.test.js](https://github.com/cnodejs/nodeclub/blob/master/test/controllers/topic.test.js)
这文件的内容没有超出之前课程的范围吧。
`describe` 中的字符串,用来描述你要测的主体是什么;`it` 当中,描述具体的 case 内容。
而引入的那个 should 模块,是个断言库。玩过 ruby 的同学应该知道 `rspec`,rspec 它把测试框架和断言库的事情一起做了,而在 Node.js 中,这两样东西的作用分别是 mocha 和 should 在协作完成。
should 在 js 的 Object “基类”上注入了一个 `#should` 属性,这个属性中,又有着许许多多的属性可以被访问。
比如测试一个数是不是大于3,则是 `(5).should.above(3)`;测试一个字符串是否有着特定前缀:`'foobar'.should.startWith('foo');`。should.js API 在:[https://github.com/tj/should.js](https://github.com/tj/should.js)
should.js 如果现在还是 version 3 的话,我倒是推荐大家去看看它的 API 和 源码;现在 should 是 version 4 了,API 丑得很,但为了不掉队,我还是一直用着它。我觉得 expect 麻烦,所以不用 expect,对了,expect 也是一个断言库:[https://github.com/LearnBoost/expect.js/](https://github.com/LearnBoost/expect.js/) 。
回到正题,还记得我们 fibonacci 函数的几个要求吗?
~~~
* 当 n === 0 时,返回 0;n === 1时,返回 1;
* n > 1 时,返回 `fibonacci(n) === fibonacci(n-1) + fibonacci(n-2)`,如 `fibonacci(10) === 55`;
* n 不可大于10,否则抛错,因为 Node.js 的计算性能没那么强。
* n 也不可小于 0,否则抛错,因为没意义。
* n 不为数字时,抛错。
~~~
我们用测试用例来描述一下这几个要求,更新后的 main.test.js 如下:
~~~
var main = require('../main');
var should = require('should');
describe('test/main.test.js', function () {
it('should equal 0 when n === 0', function () {
main.fibonacci(0).should.equal(0);
});
it('should equal 1 when n === 1', function () {
main.fibonacci(1).should.equal(1);
});
it('should equal 55 when n === 10', function () {
main.fibonacci(10).should.equal(55);
});
it('should throw when n > 10', function () {
(function () {
main.fibonacci(11);
}).should.throw('n should <= 10');
});
it('should throw when n < 0', function () {
(function () {
main.fibonacci(-1);
}).should.throw('n should >= 0');
});
it('should throw when n isnt Number', function () {
(function () {
main.fibonacci('呵呵');
}).should.throw('n should be a Number');
});
});
~~~
还是比较清晰的吧?
我们这时候跑一下 `$ mocha`,会发现后三个 case 都没过。
于是我们更新 fibonacci 的实现:
~~~
var fibonacci = function (n) {
if (typeof n !== 'number') {
throw new Error('n should be a Number');
}
if (n < 0) {
throw new Error('n should >= 0')
}
if (n > 10) {
throw new Error('n should <= 10');
}
if (n === 0) {
return 0;
}
if (n === 1) {
return 1;
}
return fibonacci(n-1) + fibonacci(n-2);
};
~~~
再跑一次 `$ mocha`,就过了。这就是传说中的测试驱动开发:先把要达到的目的都描述清楚,然后让现有的程序跑不过 case,再修补程序,让 case 通过。
安装一个 istanbul : `$ npm i istanbul -g`
执行 `$ istanbul cover _mocha`
这会比直接使用 mocha 多一行覆盖率的输出,
[![](https://raw.githubusercontent.com/alsotang/node-lessons/master/lesson6/3.png)](https://box.kancloud.cn/2015-08-03_55bf0d4e6b11d.png)
可以看到,我们其中的分支覆盖率是 91.67%,行覆盖率是 87.5%。
打开 `open coverage/lcov-report/index.html` 看看
[![](https://raw.githubusercontent.com/alsotang/node-lessons/master/lesson6/4.png)](https://box.kancloud.cn/2015-08-03_55bf0d5688228.png)
其实这覆盖率是 100% 的,24 25 两行没法测。
mocha 和 istanbul 的结合是相当无缝的,只要 mocha 跑得动,那么 istanbul 就接得进来。
到此这门课其实就完了,剩下要说的内容,都是些比较细节的。比较懒的同学可以踩坑了之后再回来看。
上面的课程,不完美的地方就在于 mocha 和 istanbul 版本依赖的问题,但为了不引入不必要的复杂性,所以上面就没提到这点了。
假设你有一个项目A,用到了 mocha 的 version 3,其他人有个项目B,用到了 mocha 的 version 10,那么如果你 `npm i mocha -g` 装的是 version 3 的话,你用 `$ mocha` 是不兼容B项目的。因为 mocha 版本改变之后,很可能语法也变了,对吧。
这时,跑测试用例的正确方法,应该是
1. `$ npm i mocha --save-dev`,装个 mocha 到项目目录中去
2. `$ ./node_modules/.bin/mocha`,用刚才安装的这个特定版本的 mocha,来跑项目的测试代码。
`./node_modules/.bin` 这个目录下放着我们所有依赖自带的那些可执行文件。
每次输入这个很麻烦对吧?所以我们要引入 Makefile,让 Makefile 帮我们记住复杂的配置。
~~~
test:
./node_modules/.bin/mocha
cov test-cov:
./node_modules/.bin/istanbul cover _mocha
.PHONY: test cov test-cov
~~~
这时,我们只需要调用 `make test` 或者 `make cov`,就可以跑我们相应的测试了。
至于 Makefile 怎么写?以及 .PHONY 是什么意思,请看这里:[http://blog.csdn.net/haoel/article/details/2886](http://blog.csdn.net/haoel/article/details/2886) ,左耳朵耗子陈皓2004年的文章。
- 关于
- Lesson 0: 《搭建 Node.js 开发环境》
- Lesson 1: 《一个最简单的 express 应用》
- Lesson 2: 《学习使用外部模块》
- Lesson 3: 《使用 superagent 与 cheerio 完成简单爬虫》
- Lesson 4: 《使用 eventproxy 控制并发》
- Lesson 5: 《使用 async 控制并发》
- Lesson 6: 《测试用例:mocha,should,istanbul》
- Lesson 7: 《浏览器端测试:mocha,chai,phantomjs》
- Lesson 8: 《测试用例:supertest》
- Lesson 9: 《正则表达式》
- Lesson 10: 《benchmark 怎么写》
- Lesson 11: 《作用域与闭包:this,var,(function () {})》
- Lesson 12: 《线上部署:heroku》
- Lesson 13: 《持续集成平台:travis》
- Lesson 14: 《js 中的那些最佳实践》
- Lesson 15: 《Mongodb 与 Mongoose 的使用》
- Lesson 16: 《cookie 与 session》
- Lesson 17: 《使用 promise 替代回调函数》