Testable Javascript

singletons suffer state pollution between tests

singletons.js
1
2
3
4
5
6
module.exports = {
counter: 0,
inc: function() {
return ++this.counter;
}
};
test.js
1
2
3
4
5
6
7
8
9
10
11
var singleton = require('singletons');
module.exports = testCase({
"should equal one after calling inc": function (test) {
test.equal(1, singleton.inc());
test.done();
},
"should get one after calling inc": function(test) {
test.equal(1, singleton.inc());
test.done();
}
});

结果第二个测试会失败,但是从测试本身是看不出为什么第一个可以通过,而第二个则相反。当然,我们可以通过setUptearDown方法来重置对象的状态,但是这无疑增加了维护测试的成本,你得记着重置对象这件事本身就是负担。

下面就是解决方案

singletons_impr.js
1
2
3
4
5
6
7
8
module.exports = function() {
return {
counter: 0,
inc: function() {
return ++this.counter;
}
};
};

再看我们的测试

test.js
1
2
3
4
5
6
7
8
9
10
11
var singleton = require('singletons_impr');
module.exports = testCase({
"should equal one after calling inc": function (test) {
test.equal(1, singleton().inc());
test.done();
},
"should get one after calling inc": function(test) {
test.equal(1, singleton().inc());
test.done();
}
});

这样两次返回的对象都是全新的,测试通过。

不过,似乎这样让测试变得更加麻烦了,好处不明显。但是结合这篇文章[Javascript Modularize 2nd],就会巧妙地解决多个模块依赖某一个模块,某个模块修改被传染至其他模块的问题。因为每个被引入的模块都将被重新执行生成一遍,成为独立的对象。

结论

  • module.exports最好使用function方式导出。

参考链接
[1] Writing Testable JavaScript