嘉盛建设集团官方网站,wordpress html代码,南京网站建设价位,电子商务网站网络拓扑图grunt 插件编写针对grunt插件的测试结果比预期的要简单。 我需要运行多个任务配置#xff0c;并想通过在主目录中键入grunt test来调用它们。 在第一个任务失败后#xff0c;咕声通常会退出。 这使得不可能在主项目gruntfile中存储多个失败方案。 从那里运行它们将需要--for… grunt 插件 编写针对grunt插件的测试结果比预期的要简单。 我需要运行多个任务配置并想通过在主目录中键入grunt test来调用它们。 在第一个任务失败后咕声通常会退出。 这使得不可能在主项目gruntfile中存储多个失败方案。 从那里运行它们将需要--force选项但是grunt会忽略所有不是最佳的警告。 较干净的解决方案是在单独的目录中有一堆gruntfile然后从主项目gruntfile调用它们。 这篇文章解释了如何做到这一点。 示范项目 演示项目是带有一个grunt任务的小型grunt插件。 根据action options属性的值任务要么失败并显示警告要么将成功消息打印到控制台中。 任务 grunt.registerMultiTask(plugin_tester, Demo grunt task., function() {//merge supplied options with default optionsvar options this.options({ action: pass, message: unknown error});//pass or fail - depending on configured optionsif (options.actionpass) {grunt.log.writeln(Plugin worked correctly passed.);} else {grunt.warn(Plugin failed: options.message);}
}); 有三种不同的方式编写grunt插件单元测试。 每个解决方案在test目录中都有自己的nodeunit文件并在这篇文章中进行说明 plugin_exec_test.js –最实用的解决方案 plugin_fork_test.js – 解决了先前解决方案失败的罕见情况 plugin_spawn_test.js – 可能 但最不实用。 所有这三个演示测试都包含三种不同的任务配置 // Success scenario
options: { action: pass }
// Fail with complete failure message
options: { action: fail, message: complete failure }
//Fail with partial failure message
options: { action: fail, message: partial failure } 每个配置都存储在test目录内的单独gruntfile中。 例如存储在gruntfile-pass.js文件中的成功方案如下所示 grunt.initConfig({// prove that npm plugin works toojshint: { all: [ gruntfile-pass.js ] },// Configuration to be run (and then tested).plugin_tester: { pass: { options: { action: pass } } }
});// Load this plugins task(s).
grunt.loadTasks(./../tasks);
// next line does not work - grunt requires locally installed plugins
grunt.loadNpmTasks(grunt-contrib-jshint);grunt.registerTask(default, [plugin_tester, jshint]); 这三个测试gruntfiles看起来几乎相同只有plugin_tester目标的options对象改变了。 从子目录运行Gruntfile 我们的测试gruntfiles存储在test子目录中而grunt不能很好地处理这种情况。 本章介绍了问题所在并介绍了两种解决方法。 问题 要查看问题请转到演示项目目录并运行以下命令 grunt --gruntfile test/gruntfile-problem.js Grunt响应以下错误 Local Npm module grunt-contrib-jshint not found. Is it installed?
Warning: Task jshint not found. Use --force to continue.Aborted due to warnings.说明 Grunt假定grunfile和node_modules存储库存储在同一目录中。 虽然node.js require函数会在所有父目录中搜索所需模块但loadNpmTasks不会。 这个问题有两种可能的解决方案一种简单而有趣 在测试目录 简单 中创建本地npm存储库 从父目录中执行繁重的加载任务 fancy 。 尽管第一个“简单”解决方案比较干净但演示项目使用了第二个“精美”解决方案。 解决方案1复制Npm存储库 主要思想很简单只需在tests目录内创建另一个本地npm存储库 将package.json文件复制到tests目录。 向其中添加仅测试依赖项。 每次运行测试时请运行npm install命令。 这是更清洁的解决方案。 它只有两个缺点 测试依赖项必须单独维护 所有插件依赖项都必须安装在两个位置。 解决方案2从父目录加载Grunt任务 另一个解决方案是强制grunt从存储在另一个目录中的npm存储库加载任务。 Grunt插件加载 Grunt有两种方法可以加载插件 loadTasks(directory-name) –将所有任务加载到目录中 loadNpmTasks(plugin-name) –加载插件定义的所有任务。 loadNpmTasks函数采用grunt插件和模块存储库的固定目录结构。 它猜测应该存储任务的目录名称然后调用loadTasks(directory-name)函数。 本地npm存储库为每个npm软件包都有单独的子目录。 所有grunt插件都应该具有tasks子目录并且其中的.js文件都包含任务。 例如 loadNpmTasks(grunt-contrib-jshint)调用从node_mudules/grunt-contrib-jshint/tasks目录加载任务等效于 grunt.loadTasks(node_modules/grunt-contrib-jshint/tasks) 因此如果要从父目录加载grunt-contrib-jshint插件的所有任务可以执行以下操作 grunt.loadTasks(../node_modules/grunt-contrib-jshint/tasks) 循环父目录 更为灵活的解决方案是遍历所有父目录直到找到最近的node_modules存储库或到达根目录为止。 这是在grunt-hacks.js模块内部实现的。 loadParentNpmTasks函数循环父目录 module.exports new function() {this.loadParentNpmTasks function(grunt, pluginName) {var oldDirectory, climb, directory, content;// search for the right directorydirectory climbnode_modules/ pluginName;while (continueClimbing(grunt, oldDirectory, directory)) {climb ../;oldDirectory directory;directory climbnode_modules/ pluginName;}// load tasks or return an errorif (grunt.file.exists(directory)) {grunt.loadTasks(directory/tasks);} else {grunt.fail.warn(Tasks plugin pluginName was not found.);}}function continueClimbing(grunt, oldDirectory, directory) {return !grunt.file.exists(directory) !grunt.file.arePathsEquivalent(oldDirectory, directory);}}(); 修改后的Gruntfile 最后我们需要通过以下步骤替换grunt.loadNpmTasks(grunt-contrib-jshint)中通常的grunt.loadNpmTasks(grunt-contrib-jshint)调用 var loader require(./grunt-hacks.js);
loader.loadParentNpmTasks(grunt, grunt-contrib-jshint); 缩短的gruntfile module.exports function(grunt) {var loader require(./grunt-hacks.js);grunt.initConfig({jshint: { /* ... */ },plugin_tester: { /* ... */ }});grunt.loadTasks(./../tasks);loader.loadParentNpmTasks(grunt, grunt-contrib-jshint);
}; 缺点 该解决方案有两个缺点 它不处理集合插件。 如果grunt曾经改变grunt插件的预期结构则必须修改解决方案。 如果您还需要集合插件请查看grunts task.js以了解如何支持它们。 从Java脚本调用Gruntfile 我们需要做的第二件事是从javascript调用gruntfile。 唯一的麻烦是咕unt声会在任务失败时退出整个过程。 因此我们需要从子进程中调用它。 节点模块子进程具有三种不同的功能能够在子进程内部运行命令 exec –在命令行执行命令 spawn –在命令行上执行命令的方式不同 fork –在子进程中运行节点模块。 第一个是exec 最易于使用并在第一章中进行了说明。 第二章介绍了如何使用fork以及为什么它不如exec最佳。 第三章是关于生成。 执行力 Exec在子进程中运行命令行命令。 您可以指定要在哪个目录中运行它设置环境变量设置超时然后在该超时后将命令终止。 当命令完成运行时exec调用回调并将其传递给stdout流stderr流和命令崩溃时的错误。 除非另有配置否则命令将在当前目录中运行。 我们希望它在tests子目录中运行所以我们必须指定options对象的cwd属性 {cwd: tests/} 。 stdout和stderr流内容都存储在缓冲区中。 每个缓冲区的最大大小设置为204800如果命令产生更多输出则exec调用将崩溃。 这笔钱足以应付我们的小任务。 如果需要更多则必须设置maxBuffer options属性。 致电执行 以下代码段显示了如何从exec运行gruntfile。 该函数是异步的并在完成之后调用whenDoneCallback var cp require(child_process);function callGruntfile(filename, whenDoneCallback) {var command, options;command grunt --gruntfile filename --no-color;options {cwd: test/};cp.exec(command, options, whenDoneCallback);
} 注意如果将npm安装到测试目录 简单解决方案 则需要使用callNpmInstallAndGruntfile函数而不是callGruntfile function callNpmInstallAndGruntfile(filename, whenDoneCallback) {var command, options;command npm install;options {cwd: test/};cp.exec(command, {}, function(error, stdout, stderr) {callGruntfile(filename, whenDoneCallback);});
} 单元测试 第一节点单元测试运行成功方案然后检查流程是否成功完成而没有失败标准输出是否包含预期的消息以及标准错误是否为空。 成功场景单元测试 pass: function(test) {test.expect(3);callGruntfile(gruntfile-pass.js, function (error, stdout, stderr) {test.equal(error, null, Command should not fail.);test.equal(stderr, , Standard error stream should be empty.);var stdoutOk contains(stdout, Plugin worked correctly.);test.ok(stdoutOk, Missing stdout message.);test.done();});
}, 第二节点单元测试运行“完全失败”方案然后检查进程是否按预期失败。 请注意标准错误流为空警告被打印到标准输出中。 失败的场景单元测试 fail_1: function(test) {test.expect(3);var gFile gruntfile-fail-complete.js;callGruntfile(gFile, function (error, stdout, stderr) {test.equal(error, null, Command should have failed.);test.equal(error.message, Command failed: , Wrong error message.);test.equal(stderr, , Non empty stderr.);var stdoutOk containsWarning(stdout, complete failure);test.ok(stdoutOk, Missing stdout message.);test.done();});
} 第三次“部分故障”节点单元测试与之前的测试几乎相同。 整个测试文件可在github上找到 。 缺点 坏处 必须预先设置最大缓冲区大小。 叉子 Fork在子进程中运行node.js模块等效于在命令行上调用node module-name 。 Fork使用回调将标准输出和标准错误发送给调用方。 两个回调都可以被多次调用并且调用方可以分段获取子进程的输出。 仅在需要处理任意大小的stdout和stderr或需要自定义grunt功能时使用fork才有意义。 如果您不这样做则exec更易于使用。 本章分为四个子章节 从javascript 呼叫grunt 读取节点模块中的命令行参数 在子进程中启动节点模块 编写单元测试。 呼唤咕unt声 Grunt并非以编程方式被调用。 它没有公开“公共” API也没有对其进行记录。 我们的解决方案模仿了grunt-cli的功能因此相对安全。 Grunt-cli与grunt核心分开分发因此更改的可能性较小。 但是如果确实更改则此解决方案也必须更改。 从javascript运行咕unt声需要我们执行以下操作 将gruntfile名称与其路径分开 更改活动目录 调用grunts tasks功能。 从javascript呼叫grunt this.runGruntfile function(filename) {var grunt require(grunt), path require(path), directory, filename;// split filename into directory and filedirectory path.dirname(filename);filename path.basename(filename);//change directoryprocess.chdir(directory);//call gruntgrunt.tasks([default], {gruntfile:filename, color:false}, function() {console.log(done);});
}; 模块参数 该模块将从命令行调用。 节点将命令行参数保留在内部 process.argv数组 module.exports new function() {var filename, directory;this.runGruntfile function(filename) {/* ... */};//get first command line argumentfilename process.argv[2];this.runGruntfile(filename);
}(); 呼叫叉 Fork具有三个参数模块的路径带有命令行参数的数组和options对象。 使用tests/Gruntfile-1.js参数调用module.js child cp.fork(./module.js, [tests/Gruntfile-1.js], {silent: true}) silent: true选项使返回的child进程的stdout和stderr在父级内部可用。 如果将其设置为true则返回的对象将提供对调用者的stdout和stderr流的访问。 在每个流上调用on(data, callback) 。 每次子进程向流发送某些内容时都会调用传递的回调 child.stdout.on(data, function (data) {console.log(stdout: data); // handle piece of stdout
});
child.stderr.on(data, function (data) {console.log(stderr: data); // handle piece of stderr
}); 子进程可能崩溃或正常结束其工作 child.on(error, function(error){// handle child crashconsole.log(error: error);
});
child.on(exit, function (code, signal) {// this is called after child process endedconsole.log(child process exited with code code);
}); 演示项目使用以下函数来调用fork和绑定回调 /*** callbacks: onProcessError(error), onProcessExit(code, signal), onStdout(data), onStderr(data)*/
function callGruntfile(filename, callbacks) {var comArg, options, child;callbacks callbacks || {};child cp.fork(./test/call-grunt.js, [filename], {silent: true});if (callbacks.onProcessError) {child.on(error, callbacks.onProcessError);}if (callbacks.onProcessExit) {child.on(exit, callbacks.onProcessExit);}if (callbacks.onStdout) {child.stdout.on(data, callbacks.onStdout);}if (callbacks.onStderr) {child.stderr.on(data, callbacks.onStderr);}
} 编写测试 每个单元测试都调用callGruntfile函数。 回调会在标准输出流中搜索所需的内容检查退出代码是否正确在错误流中出现错误时失败或者在fork调用返回错误时失败。 成功场景单元测试 pass: function(test) {var wasPassMessage false, callbacks;test.expect(2);callbacks {onProcessError: function(error) {test.ok(false, Unexpected error: error);test.done();},onProcessExit: function(code, signal) {test.equal(code, 0, Exit code should have been 0);test.ok(wasPassMessage, Pass message was never sent );test.done();},onStdout: function(data) {if (contains(data, Plugin worked correctly.)) {wasPassMessage true;}},onStderr: function(data) {test.ok(false, Stderr should have been empty: data);}};callGruntfile(test/gruntfile-pass.js, callbacks);
} 对应于失败场景的测试几乎相同可以在github上找到。 缺点 缺点 使用的grunt函数不属于官方API。 子进程输出流以块而不是一个大块的形式提供。 产生 Spawn是fork和exec之间的交叉。 与exec类似spawn能够运行可执行文件并向其传递命令行参数。 子进程输出流的处理方式与fork中的处理方式相同。 它们通过回调分段发送给父级。 因此与使用fork一样仅当需要任意大小的stdout或stderr时使用spawn才有意义。 问题 产卵的主要问题发生在Windows上。 必须准确指定要运行的命令的名称。 如果使用参数grunt调用spawn则spawn期望可执行文件名不带后缀。 grunt.cmd真正的grunt可执行文件grunt.cmd 。 否则 spawn 忽略Windows环境变量PATHEXT 。 循环后缀 如果要从spawn调用grunt 则需要执行以下操作之一 针对Windows和Linux使用不同的代码或者 从环境中读取PATHEXT并循环遍历直到找到正确的后缀。 以下函数循环遍历PATHEXT并将正确的文件名传递给回调 function findGruntFilename(callback) {var command grunt, options, extensionsStr, extensions, i, child, onErrorFnc, hasRightExtension false;onErrorFnc function(data) {if (data.message!spawn ENOENT){grunt.warn(Unexpected error on spawn extensions[i] error: data);}};function tryExtension(extension) {var child cp.spawn(command extension, [--version]);child.on(error, onErrorFnc);child.on(exit, function(code, signal) {hasRightExtension true;callback(command extension);});}extensionsStr process.env.PATHEXT || ;extensions [].concat(extensionsStr.split(;));for (i0; !hasRightExtension iextensions.length;i) {tryExtension(extensions[i]);}
} 编写测试 一旦有了grunt命令名就可以调用spawn 。 Spawn会触发与fork完全相同的事件因此 callGruntfile接受完全相同的回调对象并将其属性绑定到子进程事件 function callGruntfile(command, filename, callbacks) {var comArg, options, child;callbacks callbacks || {};comArg [--gruntfile, filename, --no-color];options {cwd: test/};child cp.spawn(command, comArg, options);if (callbacks.onProcessError) {child.on(error, callbacks.onProcessError);}/* ... callbacks binding exactly as in fork ...*/
} 测试也几乎与上一章中的测试相同。 唯一的区别是在执行其他所有操作之前您必须先找到grunt可执行文件名。 成功场景测试如下所示 pass: function(test) {var wasPassMessage false;test.expect(2);findGruntFilename(function(gruntCommand){var callbacks {/* ... callbacks look exactly the same way as in fork ... */};callGruntfile(gruntCommand, gruntfile-pass.js, callbacks);});
} 完整的成功方案测试以及两个失败方案测试都可以在github上获得 。 缺点 缺点 Spawn会忽略PATHEXT后缀需要使用自定义代码来处理它。 子进程输出流以块而不是一个大块的形式提供。 结论 有三种方法可以从gruntfile内部测试grunt插件。 除非您有非常强烈的理由不这样做否则请使用exec 。 翻译自: https://www.javacodegeeks.com/2015/02/testing-grunt-plugin-from-grunt.htmlgrunt 插件