请记住单元测试的意义:确保特定代码模块以预期的方式对某些刺激做出反应。在 JS 中,大部分代码(除非你有像 Sencha 或 YUI 这样的生命周期框架)要么直接操作 DOM,要么进行远程调用。要测试这些东西,您只需应用依赖注入和模拟/存根的传统单元测试技术。这意味着您必须编写要进行单元测试的每个函数或类,以接受依赖结构的模拟。
jQuery 通过允许您将 XML 文档传递给所有遍历函数来支持这一点。而你通常会写
$(function() { $('.bright').css('color','yellow'); }
你会想写
function processBright(scope) {
// jQuery will do the following line automatically, but for sake of clarity:
scope = scope || window.document;
$('.bright',scope).css('color','yellow');
}
$(processBright);
请注意,我们不仅从匿名函数中提取逻辑并为其命名,还使该函数接受范围参数。当该值为 null 时,jQuery 调用仍将正常运行。但是,我们现在有一个用于注入模拟文档的向量,我们可以在调用函数后对其进行检查。单元测试可能看起来像
function shouldSetColorYellowIfClassBright() {
// arrange
var testDoc =
$('<html><body><span id="a" class="bright">test</span></body></html>');
// act
processBright(testDoc);
// assert
if (testDoc.find('#a').css('color') != 'bright')
throw TestFailed("Color property was not changed correctly.");
}
TestFailed 可能如下所示:
function TestFailed(message) {
this.message = message;
this.name = "TestFailed";
}
远程调用的情况与此类似,但与其实际注入一些设施,您可以使用掩码存根逃脱。假设你有这个功能:
function makeRemoteCall(data, callback) {
if (data.property == 'ok')
$.getJSON({url:'/someResource.json',callback:callback});
}
你可以这样测试它:
// test suite setup
var getJSON = $.getJSON;
var stubCalls = [];
$.getJSON = function(args) {
stubCalls[stubCalls.length] = args.url;
}
// unit test 1
function shouldMakeRemoteCallWithOkProperty() {
// arrange
var arg = { property: 'ok' };
// act
makeRemoteCall(arg);
// assert
if (stubCalls.length != 1 || stubCalls[0] != '/someResource.json')
throw TestFailed("someResource.json was not requested once and only once.");
}
// unit test 2
function shouldNotMakeRemoteCallWithoutOkProperty() {
// arrange
var arg = { property: 'foobar' };
// act
makeRemoteCall(arg);
// assert
if (stubCalls.length != 0)
throw TestFailed(stubCalls[0] + " was called unexpectedly.");
}
// test suite teardown
$.getJSON = getJSON;
(您可以将整个内容包装在 module pattern 中,以免在全局命名空间中乱扔垃圾。)
要以测试驱动的方式应用所有这些,您只需先编写这些测试。这是一种直接、简洁、最重要的是对 JS 进行单元测试的有效方法。
像 qUnit 这样的框架可以用来驱动你的单元测试,但这只是问题的一小部分。您的代码必须以测试友好的方式编写。此外,Selenium、HtmlUnit、jsTestDriver 或 Watir/N 等框架用于集成测试,而不是用于单元测试本身。最后,您的代码决不能是面向对象的。单元测试的原理很容易与单元测试在面向对象系统中的实际应用相混淆。它们是独立但兼容的想法。
测试风格
我应该注意到,这里展示了两种不同的测试风格。第一个假设完全不了解 processBright 的实现。它可以使用 jQuery 添加颜色样式,也可以进行原生 DOM 操作。我只是在测试函数的外部行为是否符合预期。其次,我假设知道函数的内部依赖项(即 $.getJSON),并且这些测试涵盖与该依赖项的正确交互。
您采用的方法取决于您的测试理念和总体优先级以及您所处情况的成本效益概况。第一个测试比较纯。第二个测试简单但相对脆弱;如果我更改 makeRemoteCall 的实现,测试将中断。最好,makeRemoteCall 使用 $.getJSON 的假设至少由 makeRemoteCall 的文档证明是合理的。有几种更严格的方法,但一种经济高效的方法是将依赖项包装在包装函数中。代码库将仅依赖于这些包装器,它们的实现可以在测试时轻松地替换为测试存根。