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

  • 数据库层
  • 应用程序层
  • 客户端层

  • 这个问题无关紧要的是1。 2包含所有程序逻辑(“业务逻辑”)3前端。

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

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

    更新:
    我将问题的措辞从“在Web前端中测试JavaScript”更改为“由测试驱动的JavaScript Web前端开发”,以澄清我的问题。

    最佳答案

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

    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调用仍将正常运行。但是,我们现在有了一个用于注入(inject)模拟文档的向量,可以在调用该函数后检查该模拟文档。单元测试可能看起来像
    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";
    }
    

    这种情况与远程调用类似,尽管您可以不使用注入(inject)存根,而不必实际注入(inject)某种功能。说你有这个功能:
    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中,以免浪费全局 namespace 。)

    要以测试驱动的方式应用所有这些,您只需简单地编写这些测试即可。这是一种简单,简单,最重要的单元测试JS的有效方法。

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

    测试样式

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

    您采用的方法取决于您的测试原理,总体优先级以及您情况的成本效益概况。第一个测试是相对纯的。第二项测试很简单,但相对脆弱。如果我更改makeRemoteCall的实现,则测试将中断。优选地,makeRemoteCall使用$ .getJSON的假设至少由makeRemoteCall的文档证明是正确的。有两种更严格的方法,但是一种具有成本效益的方法是将依赖项包装在包装器函数中。代码库将仅依赖于这些包装器,这些包装器的实现可以在测试时轻松地用测试存根替换。

    08-04 06:46