JavaScript web前端的测试驱动开发

JavaScript web前端的测试驱动开发,javascript,testing,tdd,Javascript,Testing,Tdd,这听起来可能有点愚蠢,但实际上我有点困惑如何为web前端进行JavaScript测试。就我而言,典型的3层体系结构如下所示: function TestFailed(message) { this.message = message; this.name = "TestFailed"; } 数据库层 应用层 客户层 1与这个问题无关。2包含前端的所有程序逻辑(“业务逻辑”) 我对大多数项目进行测试驱动开发,但只针对应用程序逻辑,而不是前端。这是因为在TDD中测试UI是困难和不寻

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

function TestFailed(message) {
    this.message = message;
    this.name = "TestFailed";
}
  • 数据库层
  • 应用层
  • 客户层
  • 1与这个问题无关。2包含前端的所有程序逻辑(“业务逻辑”)

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

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

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

    您可以从用户角度使用诸如或其他等效软件之类的工具测试应用程序

    这些工具以自动化的方式测试应用程序,就像用户坐在应用程序前面一样。这意味着您可以同时测试所有三层,尤其是Javascript,否则可能很难测试。这样的功能测试可能有助于发现用户界面的缺陷和怪癖,以及用户界面如何使用中间层推出的数据


    不幸的是,这些工具非常昂贵,因此可能还有其他等效工具(我很想知道这些工具)。

    我有一个与JS客户端层架构类似的应用程序。在我的例子中,我使用我们公司自己的JS框架来实现客户端层

    这个JS框架是以OOP风格创建的,因此我可以为核心类和组件实现单元测试。此外,为了涵盖所有用户交互(使用单元测试无法涵盖),我正在使用它对框架可视化组件进行集成测试,并在不同的浏览器下进行测试

    所以,若测试代码是以面向对象的方式编写的,那个么TDD可以应用于JavaScript开发。集成测试也是可能的(并且可以用来进行某种TDD)。

    Christian Johansen的一本书《测试驱动的JavaScript开发》可能会对您有所帮助。我只看过书中的一些示例(前几天刚下载了一个示例到Kindle),但它看起来像是一本解决这个问题的好书。你可以去看看

    (注意:我与Christian Johansen没有任何联系,也没有对这本书的销售进行任何投资。解决这个问题似乎是件好事。)

    我们还可以看看JavaScript方法和函数的单元测试。

    在我们公司使用。这是一个测试前端的环境


    看看它。

    记住单元测试的要点是:确保特定的代码模块以预期的方式对某些刺激作出反应。在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.");
    }
    
    这种情况与远程调用类似,不过您可以使用屏蔽存根,而不是实际注入一些功能。假设你有这个功能:

    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;
    
    (您可以将整个内容包装在中,以避免在全局命名空间中乱扔垃圾。)

    要以测试驱动的方式应用所有这些,只需先编写这些测试。这是一种直截了当的、没有虚饰的、最重要的、有效的单元测试方法

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

    测试风格

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

    您采取的方法取决于您的测试理念、总体优先级以及您的情况的成本效益概况。第一个测试是相对纯粹的。第二个测试简单但相对脆弱;如果我更改makeRemoteCall的实现,测试将中断。最好是假设makeRemoteCall u