使用javascript写程序,我认为第一要务就是单元测试。所以当我把mongoose的简单例子跑通后,第一件事就是学着给它写单元测试。
首先尝试的是Jasmine:http://pivotal.github.com/jasmine/,冲着它3k+的关注值去的。写简单的例子很容易,但涉及到mongoose时,遇到了两个麻烦的问题:
第1处不方便,是因为Jasmine提供了runs(), waits(), waitsFor()这三个方法来支持异步调用。其中在runs中调用异步方法,在waits中等待一定时间,或用waitsFor来检查一些条件来判断之前的异步方法是否完成。这种方式不方便之处就在于waitsFor中有时候不那么检查,所以通常要设一个局部变量来保存状态。如:
it('should dosomthing', function() {
var ok = false;
doSomething(function(){
ok = true;
});
waitsFor( function() {
return ok;
}, 'Do something', 2000};
// 继续执行下面的代码
});
可以看到代码中定义的ok感觉有点多余,代码有点繁琐。
这几天为了找到一种看起来更简单一点的办法,我看了很多与同步异步相关的模块:https://github.com/joyent/node/wiki/modules#wiki-async-flow。虽然最终一个也没有用上,不过也算学习了基础知识,额外收获。(另外此处推荐由群友木木勇创建的一个Promise模块,简洁明了,代码不多,值得参考:https://github.com/sail-sail/Promise)
第2处是因为我想在所有测试前连接mongodb,所有测试后关闭连接,中间就不关闭了。但是Jasmine居然没有提供,而且有人还说“不用beforeAll可以让你的测试代码写得更好”。在这里可以看到一些讨论:https://github.com/pivotal/jasmine/pull/56。从中可以看到有些人对于Jasmine相当不满,纷纷转到另一个测试框架:mocha.
Mocha是由express的作者创建的,品质有保证:http://visionmedia.github.com/mocha/
相比Jasmine,它有以下几个优点:
第1处,对于异步方法的测试,mocha提供了一个非常简单的方法:传入一个callback,只有它被调用,才会执行下一步。这里重写前面的例子:
it('should dosomthing', function(done) {
doSomething(done);
// 下面的代码只有当done在doSomething中被异步调用之后才会运行
});
一行代码就完成了?简单太多了吧。
其它各处可参考官方文档,这里就不多说了。
mongoose这一块花了我很多时间,主要还是因为对于异步编程模块的不适应,老是搞不定那些异步调用。比如,我在initdb方法中,想做以下几件事:
这些方法都是异步的,通过回调执行下一步。我不想写那么多的嵌套语句,希望能找到一种类似同步方法调用的方式来写代码,可惜到最后还是没有发现特别满意的。期间看到了很多方案,但都觉得对于代码可读性的改善不大,最终放弃,还是用了嵌套的方式。
Mongoose提供了两种访问mongodb的方式,一种是通过它封装后的Model,一种是得到connnection对象使用底层的native driver。我在测试过程中,测试的对象是各Model相关方法,但验证时用到了native driver。这里不上代码不好说,先跳过。
我在项目中定义了多个Model,如User/Channel等,我想为每一个都准备单独的测试文件。如何让它们共享before/after/beforeEach方法呢?解决方法比较简单:定义一个单独的文件,把这些方法写在里面,然后在其它文件中require它。
我写在一个db_globle.js中:
var helper = require('./helper');
before(function(done){
helper.connect(function(){done();});
});
after(function(done) {
helper.close(function(){done();});
});
beforeEach(function(done){
helper.initdb(function(){done();});
});
然后在其它文件,如user.js中调用它:
require('should');
require('./db_global');
var mongoose = require('mongoose');
var helper = require('./helper');var models = require('../models');
var User = models.User;describe('Users', function(){
var users = helper.getConnection().collection('users'); it('can be created', function(done){ var user = new User({ email:'xxx@xxx.com', name: 'XXX', salt: '123′, password: '456′ }); user.save(); users.find({email:'xxx@xxx.com'}, function(err, cursor){ cursor.toArray(function(err,docs) { docs.should.have.lengthOf(1); var u = docs[0]; u._id.should.not.be.null; u.email.should.equal('xxx@xxx.com'); u.name.should.equal('XXX'); u.salt.should.equal('123′); u.password.should.equal('456′); done(); }); }); });
});
其中的helper是一个自定义的辅助文件,它定义了操作mongodb的一些方法,大体如下:
var mongoose = require('mongoose');
exports.DB = 'mongo://localhost/test';
exports.connect = function(callback) {
mongoose.connect(exports.DB, callback);
};
exports.close = function(callback) {
mongoose.connection.close(callback);
};
exports.getConnection = function() {
return mongoose.connection;
};
exports.initdb = function(callback) {
var conn = mongoose.connection; // drop database conn.db.dropDatabase(function(err){ if(err) { return callback(err); } console.log('Database droped.'); // insert users conn.collection('users').insert([{ email: 'nowind_lee@qq.com', name: 'Freewind', salt: '111', password: '123456' }], function(err, docs) { // insert others }); });
};
经过几天的努力,终于成功的在mocha中成功地写model测试了:)虽然花了很多时间,但很值得。
在此期间遇到了很多问题,非常感谢万能的stackoverflow和各位群友,特别是为了我的问题忙到半夜的木木勇,还有热心的sapjax。在这里把我提的相关问题汇集如下:
before
and beforeEach
for mocha?2. Global beforeEach
in jasmine?3. Why doesn't the 2rd function run, in this javascript's async example?4. Simplest way to wait some asynchronous tasks complete, in Javascript?5. How to use module q
to refactoring mongoose code?6. How to test a method in Jasmine if the code in beforeEach
is asynchronous?7. How to let the inserting to be synchronized, in mongoose?8. How to insert a doc into mongodb using mongoose and get the generated id?9. How to do raw mongodb operations in mongoose?10. How to organize the code if I want to initialize database before each tests?11. Can't drop a database in mongoose?12. 我的问题,以及木木勇的非嵌套方式初始化数据库代码