我是node和js的新手,直到昨天我都不知道回调和异步编程,所以跟我说话就像我是个白痴一样,我是...
随着mixed.io的死亡,我想我会编写自己的小静态网站构建器。我看着gulp和grunt,但是因使用npm as a build tool而感到沮丧。
构建css,缩小,列出等等是非常容易的,但是当构建页面时,生活很快就会陷入回调地狱。
一点阅读,我开始编写页面构建脚本:
var fm = require('front-matter'),
fs = require('fs'),
glob = require('glob'),
md = require('marked');
const SEARCHPATH = "content/pages/";
pages = [];
function searchFiles () {
glob("*.md", { cwd: SEARCHPATH }, readFiles);
}
function readFiles (err, files) {
if(err) throw err;
for (var file of files) {
fs.readFile(SEARCHPATH + file, 'utf8', processFiles);
}
}
function processFiles(err, data) {
if(err) throw err;
var attributes = fm(data).attributes;
var content = md(fm(data).body);
pages.push(attributes, content);
applyTemplate(pages);
}
function applyTemplate(pages) {
console.log(pages);
}
searchFiles();
但它会像我一样寻找所有的世界,即将进入菊花链地狱,每个函数都会调用下一个函数,但我无法访问页面变量而不这样做。
这一切似乎都有点过了。
我在想这个吗?以编程方式构建此更好的方法是什么?
感谢Overflowers。
答案 0 :(得分:0)
您将所有回调分解为函数声明而不是内联表达式,因此您已经有+1,因为您有可以导出和测试的函数对象。
对于这个回答,我假设优先级是孤立的程序性单元测试,而不是模仿require
。 (当我进入一个新项目时,我通常会发现自己正在重构遗留的node.js。)
当我沿着嵌套嵌套回调的这条路走时,我认为最简单的方法是使用嵌套的匿名表达式链作为回调:(在psuedocode中)
function doFiles() {
glob('something', function(files) {
for (f in files) {
fs.readFile(file, function(err, data) {
processFile(data);
}
}
}
}
以编程方式测试上述内容非常复杂。唯一的方法是模拟需求。要测试readFile回调是否正常工作,您必须先控制所有调用!这违反了测试中的隔离。
第二个最好的方法,imo,就是像你一样打破回调。
这样可以更好地隔离单元测试,但仍需要fs
和glob
的模拟需求。
第三种最佳方法imo是注入所有函数依赖项,以便轻松配置模拟对象。这通常看起来很奇怪,但对我来说,目标是100%覆盖率,在不使用模拟需求库的独立单元测试中。它使得每个函数都是一个易于测试的独立对象,并为其配置模拟对象,但通常会使调用该函数更加复杂!
实现这一目标:
function searchFiles () {
glob("*.md", { cwd: SEARCHPATH }, readFiles);
}
会变成
function searchFiles (getFiles, getFilesCallback) {
getFiles("*.md", { cwd: SEARCHPATH }, getFilesCallback);
}
然后可以用
调用它searchFiles(glob, readFiles)
这看起来有点时髦,因为它是一个单行函数,但说明了如何将依赖项注入函数,以便测试可以配置模拟对象并将它们直接传递给函数。重构readFiles
来执行此操作:
function readFiles (err, files, readFile, processFileCb) {
if(err) throw err;
for (var file of files) {
readFile(SEARCHPATH + file, 'utf8', processFileCb);
}
}
readFiles接受readFile
方法(fs.readFile
并在读取文件后执行回调。这样可以在程序化测试中轻松配置模拟对象。
然后测试可以是,在伪代码中:
it('throws err when error is found', function() {
var error = true;
assert throws readFiles(error)
});
it('calls readFile for every file in files', function() {
var files = ['file1'];
var error = false;
var readFile = createSpyMaybeSinon?();
var spyCallback = createSpy();
readFiles(error, files, readFile, spyCallback);
assert(readFile.calls.count(), files.length)
assert readFile called with searchpath + file1, 'utf8', spyCallback
});
一旦你拥有了这些需要客户端提供所有函数依赖关系的函数,那么它们需要一个创造性的bind
回调或小函数表达式来包装调用。
以上假定完整测试覆盖的结束而没有嘲弄要求,这可能不是您的目标:)
A"清洁工"方法imo只是使用来自beginnning的promises,这是异步调用的一个很好的抽象。