JavaScript Web前端的测试驱动开发

fut*_*lib 32 javascript testing tdd

这可能听起来有点愚蠢,但我实际上有点困惑如何处理Web前端的JavaScript测试.就我而言,典型的3层架构如下所示:

  1. 数据库层
  2. 应用层
  3. 客户层

1在这个问题上无关紧要.2包含前端的所有程序逻辑("业务逻辑")3.

我为大多数项目进行测试驱动的开发,但仅针对应用程序逻辑,而不是前端.这是因为在TDD中测试UI很困难且不常见,而且通常没有完成.相反,所有应用程序逻辑都与UI分离,因此测试该逻辑很简单.

三层体系结构支持这一点:我可以将我的后端设计为REST API,由我的前端调用.JS测试如何适应?对于典型的三层架构,JS(即客户端上的JS)测试没有多大意义,是吗?

更新: 我已经将问题的措辞从"在网络前端测试JavaScript"改为"JavaScript网络前端的测试驱动开发"来澄清我的问题.

G-W*_*Wiz 23

记住单元测试的重点是:确保特定的代码模块以预期的方式对某些刺激作出反应.在JS中,代码的很大一部分(除非你有像Sencha或YUI这样的生命周期框架)将直接操作DOM或进行远程调用.要测试这些东西,您只需应用依赖注入和模拟/存根的传统单元测试技术.这意味着您必须编写要进行单元测试的每个函数或类,以接受依赖结构的模拟.

jQuery支持这一点,允许您将XML文档传递给所有遍历函数.而你通常会写

$(function() { $('.bright').css('color','yellow'); }
Run Code Online (Sandbox Code Playgroud)

你会反而想写

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);
Run Code Online (Sandbox Code Playgroud)

请注意,我们不仅从匿名函数中提取逻辑并为其命名,我们还使该函数接受范围参数.当该值为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.");
}
Run Code Online (Sandbox Code Playgroud)

TestFailed看起来像这样:

function TestFailed(message) {
    this.message = message;
    this.name = "TestFailed";
}
Run Code Online (Sandbox Code Playgroud)

这种情况与远程调用类似,但不是实际注入某些工具,而是可以使用屏蔽存根.假设你有这个功能:

function makeRemoteCall(data, callback) {
    if (data.property == 'ok') 
        $.getJSON({url:'/someResource.json',callback:callback});
}
Run Code Online (Sandbox Code Playgroud)

你会测试它:

// 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;
Run Code Online (Sandbox Code Playgroud)

(您可以将整个事物包装在模块模式中,而不是丢弃全局命名空间.)

要以测试驱动的方式应用所有这些,您只需先编写这些测试.这是一个简单,没有多余的装饰,最重要的是,有效的JS单元测试方法.

像qUnit这样的框架可以用来驱动你的单元测试,但这只是问题的一小部分.您的代码必须以对测试友好的方式编写.此外,像Selenium,HtmlUnit,jsTestDriver或Watir/N这样的框架用于集成测试,而不是用于单元测试本身.最后,您的代码绝不能面向对象.单元测试的原理很容易与面向对象系统中单元测试的实际应用相混淆.它们是独立但兼容的想法.

测试样式

我应该注意这里演示了两种不同的测试方式.第一个假设完全忽略了processBright的实现.它可以使用jQuery来添加颜色样式,也可以使用本机DOM操作.我只是测试函数外部行为是否符合预期.在第二个中,我假设知道函数的内部依赖(即$ .getJSON),这些测试涵盖了与该依赖的正确交互.

您采用的方法取决于您的测试理念和总体优先级以及您的情况的成本效益概况.第一次测试相对纯净.第二个测试很简单但相对脆弱; 如果我改变makeRemoteCall的实现,测试将会中断.优选地,makeRemoteCall使用$ .getJSON的假设至少由makeRemoteCall的文档证明是合理的.有一些更有纪律的方法,但一种经济有效的方法是将依赖包装在包装函数中.代码库只依赖于这些包装器,它们的实现可以在测试时轻松替换为测试存根.