Dojo 敏捷开发:集成 DOH 单元测试到 Ant build

IBMdW 发布于 2012/01/16 19:26
阅读 487
收藏 1

简介: DOH 是一种基于 Dojo 技术的 JavaScript 单元测试框架,也是目前主流的 Dojo 单元测试技术。Ant 是基于 Java 技术的构建工具,Ant build 是敏捷开发中用于持续集成的主流方法。本文首先介绍 Dojo 单元测试的类型以及使用 DOH 运行单元测试的方式,然后重点介绍如何将 DOH 编写的 Dojo 单元测试集成到 Ant build 中,能够让单元测试的结果展示在 build 结果中,并且进一步影响 build 的成功或者失败。

前言

熟悉敏捷开发的读者都知道,在项目开发过程中创建一个定期运行的 build 是至关重要的,它可以帮助项目团队及时的发现项目中存在的问题以及查看迭代的结果。通常 build 中都会包含一项很重要的工作就是执行项目中的单元测试并汇报单元测试的执行结果,如果有单元测试失败那么就让 build 失败。对于使用 Dojo 开发的项目而言,如何将 Dojo 代码的单元测试集成到项目的 build 中是贯彻敏捷开发原则必不可少的一步。

本文将首先简单对 Dojo 单元测试框架 DOH 进行简单介绍,然后说明如何将编写的单元测试集成到 Ant build 中。

DOH 单元测试简介

DOH (Dojo Objective Harness) 是 Dojo 提供的 JavaScript 测试套件,也是当前最有效的 Dojo 代码单元测试框架。它不仅可以用于测试 Dojo 代码,也可以独立于 Dojo ,用于测试其它 JavaScript 代码。

在一个用 Dojo 开发的应用中,按照代码的功能通常可将测试点分为如下几种类型:JavaScript 代码逻辑、Ajax 异步通信、widget 的显示及行为、UI Look & Feel。

  1. JavaScript 代码逻辑,是指一个功能逻辑相对独立的 JavaScript 代码段,通常是一个 JavaScript function 或者一个 Dojo class 的 method。它可以在一个相对封闭的数据集中进行执行,执行的结果是返回一个独立的数据集或者改变封闭数据集中的数据。对于这种代码的单元测试是非常容 易编写和执行的,而且执行时也不会产生任何的副作用。我们只需要构造一个数据集,然后在这个数据集上调用该 JavaScript 代码段,最后检验执行结果是否与预期相符即可。这种单元测试不需要与服务器通信也不需要后端代码的支持,而且大多不会存在浏览器的兼容性问题。
  2. Ajax 异步通信:在使用 Ajax Framework 进行开发的应用系统中,异步请求服务器数据是非常常见的操作,测试 Ajax 很关键的是等待服务器返回结果然后对结果进行判断,DOH 提供了 DOH.Deffered 对象,利用它可以很方便的进行 Ajax 测试。当然这类测试需要服务器端进行支持,也可以在客户端用数据文件模拟服务器端返回的数据进行测试。
  3. Widget 的显示及行为:Dojo 的 dijit 库提供了强大的 Widget 支持,同时也提供了开发自定义 Widget 的能力。在应用系统中,所有用户可见部分的显示和行为都是通过 Widget 来实现的。对于 Widget 的测试就包括对其在浏览器中所展现的样式以及与用户交互过程中所展现的行为的测试。由于这些与浏览器的实现关系非常密切,所以对 Widget 的测试需要针对不同浏览器测试其兼容性问题。
  4. UI Look & Feel:Look & Feel 对于 UI 系统也是非常重要的一个方面。在 Dojo 应用系统中,这里主要是对自开发的 CSS 样式表的验证,检查页面在不同的浏览器中所展现出来的样式是否符合要求。但是,据笔者了解,现有的技术方案都不会对这类问题进行单元测试,而是将这类问题 放到功能测试中进行覆盖。

DOH 作为一种 JavaScript 单元测试框架,它对以上所描述的几种类型都有很好的支持。它针对不同类型的单元测试提供了不同的测试模式。

DOH 单元测试运行方式

编写好的 DOH 单元测试需要怎么运行呢? DOH 支持两种运行方式:浏览器运行方式和命令行运行方式。

浏览器运行方式

Dojo 提供了统一界面来运行单元测试,那就是 DOH 中的 runner.html,它把大量单独的单元测试文件(JavaScript 和 HTML 文件)封装到一个文件中统一管理。浏览器运行方式就是指使用浏览器访问该文件来执行单元测试,并以直观的方式反映运行结果。开发人员可将自己开发的单元测 试样例加到 runner.html 中进行测试,运行时可以在浏览器的页面上看到执行进度、打印的 log 信息、UI 显示界面,以及运行结果等等。

优点 :可以很直观的查看运行的过程和结果,对 UI 页面和 Widget 测试天然地提供了执行环境的支持。擅长 UI 页面及 Widget 的显示和行为的测试,以及 Ajax 异步通信测试。

不足: 测试过程中需要启动浏览器,UI 的可视化测试需要人为判断是否符合需求,很难自动化。

命令行运行方式

DOH 还提供了一种不依赖于浏览器来运行单元测试的方法,那就是利用命令行的方式。它是通过使用 Dojo util 下面的 JavaScript 执行引擎 – Rhino 来执行单元测试的。运行方式是在 dojo/util/doh 目录下运行命令:

 java -jar ../shrinksafe/js.jar runner.js testModule= ${yourmodule} 

其中 runner.js 是 DOH 提供的一个执行单元测试的 JavaScript 入口点,${yourmodule} 是自定义的测试模块名称。

优点:执行过程不需要人为参与,执行结果也可以通过 DOH 的 assert 自动判断,擅长 JavaScript 逻辑测试,单元测试很容易编写。

不足:没有浏览器的环境,无法对可视化页面或 Widget 的显示效果进行测试,所使用的 Rhino JavaScript 引擎可能与实际使用的浏览器中的 JavaScript 引擎有所差异,这可能会导致一些特定代码的测试结果与实际运行结果不同,无法进行浏览器的兼容性测试。

集成 DOH 单元测试到 Ant build

所谓集成 DOH 单元测试到 Ant build 中,就是寻找一种方式将使用 DOH 编写的单元测试作为 Ant build 的一个 target 进行运行,并且能够将单元测试结果体现在 build 的运行结果中,当单元测试失败时能够使 build 失败。

在介绍集成方式之前,首先来看一下 Dojo 项目代码的组织方式,因为组织方式的不同将会影响到 build 中的一些参数配置。

Dojo 项目代码的组织方式

以下所介绍的 Dojo 项目代码的组织方式只是一种比较流行的组织方式,并不是唯一可行的,您可采用不同的组织方式。本文后面实例中的参数配置将以下面介绍的组织方式为准。

通常一个 Web 项目的所有 Web 资源都包含在一个 WebContent 目录中,其中包括图片、JavaScript 脚本、CSS 等。Dojo 项目还包括 Dojo 类库、项目 Dojo 代码、项目的 Dojo 单元测试代码。如下图展示了这些文件的目录组织方式:


图 1. Dojo 项目的目录组织方式
图 1. Dojo 项目的目录组织方式

其中,dojo 目录是 Dojo 的库目录;com 是项目 Dojo 代码目录,com 是项目代码包的顶级目录;tests 是项目单元测试目录;css 是项目 CSS 文件所在的目录;images 是项目图片文件所在的目录。这些目录中与本文所介绍的主题相关的有 dojo 目录和 tests 目录。

典型的 Dojo 库的 dojo 目录包含子文件夹:dojo、dijit、dojox、util,util 中包含 shrinksafe、doh 等目录;tests 目录中通常会定义一个 module.js 文件,用于定义所有待运行的 Dojo 单元测试列表。

集成命令行运行方式

DOH 的命令行运行方式通过一个 Java 命令运行 js.jar,并且将 runner.js 和待运行的模块通过参数传递进去。集成这种方式到 Ant build 中是显而易见的,因为 Ant 直接提供了运行 Java 命令的方式。清单 1 的 Ant 片段就能够完成运行 Dojo 单元测试的目的(假定 build.xml 文件在 js 目录中)。


清单 1. 集成命令行运行方式的 Ant 片段
				 
 <target name="runUT"> 
    <java jar="dojo/util/shrinksafe/js.jar" fork="true" 
           failonerror="true"> 
        <arg value="dojo/util/doh/runner.js"/> 
        <arg value="dojoUrl=dojo/dojo/dojo.js"/> 
        <arg value="dohBase=dojo/util/doh/"/> 
        <arg value="testUrl=tests/module.js"/> 
        <arg value="testModule=tests.module"/> 
    </java> 
 </target> 

这里的 failonerror 属性一定要设置为 true,这样当单元测试失败时 build 才会失败。Java 命令里面的 arg 定义了执行 js.jar 时所需要的参数:

  • dojo/util/doh/runner.js: 这个参数指定了 DOH 中 runner.js 相对于 build basedir 的路径;
  • dojoUrl=dojo/dojo/dojo.js: 这个参数指定了 dojoUrl 的值,即相对于 build basedir,Dojo 库中 dojo.js 的路径;
  • dohBase=dojo/util/doh/: 这个参数指定了 dohBase 的值,即相对于 build basedir,DOH 根目录的相对路径,即项目目录中 doh 目录的路径;
  • testUrl=tests/module.js: 这个参数指定了 testUrl 的值,即相对于 build basedir,待执行单元测试的路径,通常直接指向 module.js 文件,它是单元测试的入口;
  • testModule=tests.module: 这个参数指定了 testModule 的值,即待测试模块的名称,通常定义为 module.js 所定义的模块。

当该 target 被执行时,module.js 中 required 的所有单元测试将会被执行,我们写的每一个单元测试都可以用独立的 js 文件进行组织,然后通过 required 方式包含在 module.js 中。单元测试的运行结果会被直接打印出来,当有单元测试失败时 build 就会失败。清单 2 展示了一个简单的 DOH 单元测试 sampleTest.js。


清单 2. DOH 单元测试示例
				 
 dojo.provide("tests.sampleTest"); 
 doh.register("tests.sampleTest", [ function test_sample() { 
 doh.assertEqual("b", "a"); 
 } ]); 

将这个单元测试添加到 module.js 中,即在 module.js 的头部位置中添加一行语句:dojo.require("tests.sampleTest ")

清单 3 展示了这个单元测试运行的结果:


清单 3. 失败的 DOH 单元测试运行结果
				 
 run: 
     [java] ------------------------------------------------------------ 
     [java] The Dojo Unit Test Harness, $Rev: 24146 $ 
     [java] Copyright (c) 2011, The Dojo Foundation, All Rights Reserved 
     [java] ------------------------------------------------------------ 
     [java] 1 tests to run in 1 groups 
     [java] ------------------------------------------------------------ 
     [java] GROUP "tests.sampleTest" has 1 test to run 
     [java] _AssertFailure: doh._AssertFailure: assertEqual() failed: 
     [java] expected 
     [java] b 
     [java] but got 
     [java] a 
     [java] : assertEqual() failed: 
     [java] expected 
     [java] b 
     [java] but got 
     [java] a 
     [java] doh._AssertFailure: assertEqual() failed: 
     [java] expected 
     [java] b 
     [java] but got 
     [java] a 
     [java] ERROR IN: 
     [java]  (function test_sample() {doh.assertEqual("b", "a");}) 
     [java] ------------------------------------------------------------ 
     [java] | TEST SUMMARY: 
     [java] ------------------------------------------------------------ 
     [java]  1 tests in 1 groups 
     [java]  0 errors 
     [java]  1 failures 

 BUILD FAILED 

如果将 sampleTest.js 中 doh.assertEqual(“b”, “a”) 改为 doh.assertEqual(“a”, “a”),就会是一个执行成功的结果,如清单 4 所示。


清单 4. 成功的 DOH 单元测试运行结果
				 
 run: 
     [java] ------------------------------------------------------------ 
     [java] The Dojo Unit Test Harness, $Rev: 24146 $ 
     [java] Copyright (c) 2011, The Dojo Foundation, All Rights Reserved 
     [java] ------------------------------------------------------------ 
     [java] 1 tests to run in 1 groups 
     [java] ------------------------------------------------------------ 
     [java] GROUP "tests.sampleTest" has 1 test to run 
     [java] ------------------------------------------------------------ 
     [java] | TEST SUMMARY: 
     [java] ------------------------------------------------------------ 
     [java]  1 tests in 1 groups 
     [java]  0 errors 
     [java]  0 failures 
 BUILD SUCCESSFUL 

集成浏览器运行方式

命令行运行方式在很多方面存在着缺陷,例如:无法测试与 DOM 操作相关的代码,无法测试浏览器的兼容性等等。而浏览器运行方式就能够弥补这些缺陷。可是将浏览器运行方式集成到 Ant build 中会相对复杂一些。这种方式需要一个可访问服务器的支持,这个服务器可以是一个部署好的公用服务器,也可以是在 build 中启动的一个本地服务器,下面以本地服务器为例。在 build 中启动一个本地的 Web 服务器可以使用嵌入式的 Web 服务器,例如 Jetty。

在浏览器运行方式中,主入口是一个 html 文件,例如 runner.html。我们可以将该文件放在 WebContent 的根目录中,并将启动的 Web 服务器的 Web 应用根目录设置为 WebContent,这样就可以通过 http://localhost/runner.html 访问。

所以在 Ant build 中启动单元测试就变得简单了,只需要启动一个浏览器并且访问该地址即可,这里的浏览器可以是任何想要测试的浏览器,也可以将每种浏览器测试一遍。以 Firefox 为例,Ant 片段如清单 5 所示。


清单 5. Ant 中启动浏览器
				 
 <target name="runUT"> 
    <exec executable=" C:\Program Files\Mozilla Firefox\firefox.exe"> 
        <arg value="http://localhost/runner.html"/> 
 </exec> 
 </target> 

其中,executable 是浏览器应用程序可执行文件或命令的路径,arg 是访问 runner.html 的 web 路径。这条命令可以达到启动单元测试的目的,但是当浏览器被启动起来以后这条命令就直接返回了,所以 Ant build 可能在单元测试执行结束之前就已经返回了,无法得到单元测试的执行结果。所以我们需要一个办法让 Ant build 等待单元测试执行结束。这时候可以借助 Web 服务器来传递这个消息。

我们可以实现一个服务器端的消息转接服务,该服务可以通过 HTTP 写入或者读取单元测试的执行结果。在 runner.html 中添加一段 JavaScript 代码用于在单元测试执行结束后将结果通过 Ajax 方式写入到服务中。除此之外,我们还需要实现一个 Java 可执行类,该类以一定间隔从服务器端的服务中去取执行结果,当无法取得执行结果时等待一定间隔重试,直到取得执行结果,然后输出执行结果并且根据执行结果 是否成功决定返回结果。在 Ant build 中,当单元测试被启动后启动该 Java 可执行类。通过这个方式我们可以让 Ant build 等待单元测试执行结束并且将单元测试的结果输出,当单元测试失败时让 build 失败。

  1. 编写消息转接服务

    消息转接服务是一个简单的服务器端处理单元,以什么方式实现取决于项目的服务器端技术,可以是 Servlet、REST executor 等等。下面以 REST executor 为例实现该处理单元。

    我们需要将写入和读取的 HTTP 请求都定向到这个处理单元中。写入的 HTTP 请求附带 HTTP 消息体,读取的 HTTP 请求没有 HTTP 消息体。所以该处理单元可以通过是否有 HTTP 消息体来判断是写入请求还是读取请求。实现代码如清单 6。



    清单 6. 消息转接服务代码示例
    				 
    public class ResultService extends ReqExecutor { 
    
               private String result = ""; 
               public ReqExecutor newInstance() { 
                    return this ; 
           } 
                public HTTPResponse execute(HTTPRequest req) { 
                    String body = req.getBody(); 
                    if(body != null && !body.trim().equals("")) result = body; 
                    else { 
                       return new StringResponse(result); 
                    } 
                } 
     } 

    为了保证写入和读取的 HTTP 请求都由同一个 ResultService 对象进行处理,所以这里当 newInstance 时就返回 this。

    将该消息注册到 REST framework 中,并为其指定一个不与其它服务冲突的 URL,例如: ”/resultService”。

  2. 修改 DOH 的执行代码

    在 DOH 中,runner.js 中有一个 doh._report 方法负责在单元测试执行结束后汇报执行结果。我们可以修改该方法,让它在所有测试执行结束后把运行结果通过 Ajax 方式发送给服务器端的消息转接服务。修改后的方法体如清单 7。



    清单 7. 修改后的 doh._report 方法
    				 
    doh._report = function (){ 
            this .debug("| TEST SUMMARY:"); 
            this .debug(this ._line); 
            this .debug("\t", this ._testCount, "tests in", 
                                 this ._groupCount, "groups"); 
            this .debug("\t", this ._errorCount, "errors"); 
            this .debug("\t", this ._failureCount, "failures"); 
      //--------------------------------------------------------------------------- 
                 // 定义 request 对象,把需要写入 build 的信息传递给 server 端
                 var request = { 
                     //url 为用于接收参数的 rest excutor 
            url: "http://localhost/resultService", 
            handleAs: "text", 
            preventCache: true , 
            content : 
            // 总 test case 数量
            "testCount: " + this ._testCount + "\n" + 
            // 总 test suit 数量
            "groupCount: " + this ._groupCount + "\n" + 
            // 出错数量
            "errorCount: " + this ._errorCount + "\n" + 
            // 失败数量
            "failureCount: " + this ._failureCount + "\n" + 
            // 全部执行过程中的 log 内容
            "logBody: " + dojo.byId("logBody").innerHTML + 
                     // 执行结果是成功或者失败,将被 Ant build 用于决定是否让 build 失败
            this ._errorCount + this ._failureCount > 0 ? "successful"  : "failed", 
            load: function (data, ioargs){ 
                console.log("ok"); 
            }, 
            error: function (error){ 
                console.log("error"); 
            } 
                   }; 
                   // 向 server 端发送 POST 请求
                   dojo.xhrPost(request); 
     } 

    方法体中分割线以上部分为原方法体,以下部分为增加的代码。在该方法中可以分别通过内部变量或者 innerHTML 获得所有已执行单元测试的统计信息(比如:test case 的数量、test suit 的数量)和执行结果(比如:出错数量、失败数量、执行的 log 内容)。在以上的样例实现中,只是简单的将所有这些内容以纯文字的形式返回给消息转接服务。如果不希望这样,当然也可以选择其它的方式(例如 JSON)和内容,但是这样也同时需要修改消息转接服务和 Ant build 中的 Java 可执行类。

  3. 集成 Java 可执行类到 Ant build

    这个 Java 可执行类需要提交 HTTP 请求到 http://localhost/resultService,并且获取消息转接服务所保存的执行结果。代码如清单 8。



    清单 8. Java 可执行类示例
    				 
    				 import java.io.IOException; 
     import java.io.InputStream; 
     import java.net.URL; 
    
     public class ResultRequest { 
    
       public static void main(String[] args) throws IOException { 
            URL u = new URL("http://localhost/resultService"); 
            //int tryCounts = 0; 
            while (true ){ 
                //if(tryCounts > 60) System.exit(1); 
                //tryCounts++; 
                InputStream inStream = u.openStream(); 
                String result = readContentFromStream(inStream); 
                if (result != null && !result.trim().equals("")) { 
                    System.out.println(result); 
                    if (result.endsWith("failed")) System.exit(1); 
                   else return ; 
                } else { 
                    Thread.sleep(1000); 
                } 
            } 
        } 
     } 

    该类打开连接消息转接服务的 URL,获得 URL 所返回的内容,如果返回的内容为空则继续等待,等待时间可以设置为一个合适的时间,比如 1 秒钟;如果返回的内容不为空则说明已经取到单元测试的运行结果,然后输出取到的结果并且从结果中获得单元测试是否成功,如果不成功则以错误代码返回,否则 直接返回。

    将该类集成到 Ant build 中,首先将该类编译打包,然后放在一个 build 脚本可以访问的库目录中,例如 WebContent/lib。Ant 片段如清单 9。



    清单 9. 集成 Java 可执行类到 Ant
    				 
     <target name="testResult" depends="runUT"> 
        <java classname="ResultRequest" classpath="lib" failonerror=”true”/> 
     </target> 

    该 target 在 runUT target 后执行,所包含的工作就是执行这个 Java 类。

回页首

提高代码可测试性

根据前面的介绍,DOH 支持两种运行模式,这两种运行模式也可以通过不同的方式集成到 Ant build 中。但是,由于这两种方式都有各自的特点以及所擅长的测试类型,所以我们可以通过重构代码和编写恰当的单元测试来提高代码的可测试性。

首先,尽可能地将代码中的复杂逻辑抽象为独立的 JavaScript 逻辑模块,以便于可以按照 JavaScript 代码逻辑进行测试,并且使用命令行的方式进行运行。例如,Widget 中不与 DOM 内容进行交互的方法逻辑;Ajax 通信中对请求或者响应消息进行处理的逻辑代码等等。

其次,将无法抽象成为独立 JavaScript 逻辑模块的代码的测试选择浏览器方式运行。例如,Widget 的行为中需要与 DOM 内容进行交互的方法;在各种浏览器中行为可能会不一样的代码等等。

通过将单元测试集成到 Ant build 中,您也可以通过自己的尝试和摸索不断发现其它能够提高代码可测试性的方法。

小结

本文首先简单介绍了 Dojo 单元测试的几种类型以及使用 DOH 进行 Dojo 单元测试的两种运行方式 ,然后重点阐述了这两种运行方式如何与 Ant build 进行集成以满足敏捷开发的需要。将单元测试集成到 build 是敏捷开发中持续集成的关键要素,通过使用本文所介绍的方法将 Dojo 单元测试集成到项目的 build 中,可以使项目开发成员在开发过程中及时地发现 Dojo 代码中的缺陷,从而提高项目的开发效率和代码质量。

文章出处: IBM developerWorks
加载中
0
红叔
红叔
我们用的是maven3。
返回顶部
顶部