
上一节,我们知道了什么是命令,以及什么是查询。
今天我们继续前面的例子,来试着将一个功能太多的函数进行命令和查询的分离。
/**
* 查询函数(用于设置值并返回)
*
*/
function configure(values) {
var config = { docRoot: '/somewhere' };
var key;
for (key in values) {
config[key] = values[key];
}
return config;
}
/**
* 命令函数(用与验证config的docRoot)
*
*/
function validateDocRoot(config) {
var fs = require('fs');
var stat;
stat = fs.statSync(config.docRoot);
if (!stat.isDirectory()) {
throw new Error('Is not valid');
}
}
/**
* 命令函数(用于验证config的其他值)
*
*/
function validateSomethingElse(config){ ... }
好了,这样我们就将一个复杂的configure()
函数分离称为了三个简单的函数,且每个函数或者是命令函数或者是查询函数,这样会方便对每个函数进行测试。
注意:前面我们说过命令函数属于setter和查询函数属于getter,而新抽象出来的configure()
函数即包含了对config对象的设置值,又返回了一个config对象,那么它到底是命令函数查询呢?
我的一个简单判断就是,
测试代码
有了命令和查询的分离以后,我就可以对这些简单的命令和查询函数进行测试了.
require("@fatso83/mini-mocha").install();
const sinon = require("sinon");
const { assert } = require("@sinonjs/referee");
var fs = require('fs');
/**
* 命令函数(用与验证config的docRoot)
*
*/
function validateDocRoot(config) {
var stat;
stat = fs.statSync(config.docRoot);
if (!stat.isDirectory()) {
throw new Error('Is not valid');
}
}
describe('validate value1', ()=>{
it('accept the correct value',function(){
const config = validateDocRoot({docRoot: '/tmp'});
assert.isUndefined(config);
});
it('accept the incorrect value',function(){
var statSync= sinon.stub(fs , 'statSync').callsFake(()=>{
return {
isDirectory: function(){
return false;
}
};
});
const fn = function() {
validateDocRoot({docRoot: '/xxx'});
}
assert.exception(fn);
});
});
//在这里例子中,我们对validateDocRoot函数进行了测试,
//注意一下,这里提取出validateDocRoot函数以后,要么验证正确,要么抛出错误信息
//与上一节中,抛出了错误还返回一个undefined这种处理方式,更加提供了更好可理解性
//同时这里使用了一个stub来模拟fs模块的功能,不需要nodejs环境就可以直接测试啦。
//以往的情况是必须安装nodejs,现在我们直接在浏览器上就可以直接搞定
function validateSomethingElse(){
}
这里我们的configure()有点小问题就是缺少了验证,因此需要重构一下:
/**
* 此函数接收一个散列对象,要么返回一个有效的配置对象要么抛出一个错误
*
*/
function configure(values) {
var config = { docRoot: '/somewhere' };
var key;
for (key in values) {
config[key] = values[key];
}
validateDocRoot(config);
validateSomethingElse(config);
return config;
}
顺便提一点,在上一个版本的验证函数中,我们看到如果抛出错误,函数返回的是一个undefined
而有了命令和查询分离以后,这个返回undefined的奇怪逻辑也不需要了。
以上函数算是初步进行了分离,但是存在的一个问题是,configure()
的测试需要依赖验证函数,可以尝试做一些抽象,比如:
var fields = {
docRoot: { validator: validateDocRoot, default: '/somewhere'},
somethingElse: { validator: validateSomethingElse}
}
function configure(values) {
var config = {};
for (var key in fields) {
if (typeof values[key] !== 'undefined') {
fields[key].validator(values[key]);
config[key] = values[key];
}else {
config[key] = fields[key].default;
}
}
return config;
}
这个函数有几个优点,验证函数可以方便的测试,对象赋值的时候可以进行验证,且数据都在一个中央位置。
当然这个函数也存在缺陷,至于到底存在什么问题呢?我们下一节进行分析。