QCon北京「鸿蒙专场」火热来袭!即刻报名,与创新同行~ 了解详情
写点什么

Dojo 单元测试框架 DOH 介绍

  • 2011-06-10
  • 本文字数:4831 字

    阅读完需:约 16 分钟

单元测试的重要性已毋须多言,无论是从保证软件开发质量,还是从节约软件后期维护成本来说,单元测试都是最佳实践。而在敏捷编程领域,随着 TDD(测试驱动开发)被越来越多的开发者所接受,单元测试已经成为开发过程中举足轻重的一部分。

编写单元测试离不开成熟的单元测试框架,由于 JUnit 框架的成功,Java 开发者对于单元测试的接受程度非常高。而 Web2.0 前端开发的单元测试一直以来是一块不太受重视的领域,导致这个状况的有很多:前端开发中逻辑和界面耦合度高、Javascript 的模块概念单薄、Javascript 运行环境(浏览器)不统一等;最主要的原因是缺乏成熟的单元测试框架,用来支持 Web 开发的特性(Ajax,DOM 等)以及 Web 前端的自动化单元测试,然而 Dojo 中的 DOH 工具改变了这个现象。

DOH 简介

Dojo 作为一个成熟的 Javascript 开发工具集,提供了强大的 Javascript 单元测试工具——DOH(Dojo Objective Harness)。DOH 主要是由 Dojo 的创始人 Alex Russel 主持开发,目的就是要针对 Web 前端开发者提供一个有如下特点的测试框架:

  1. 提供用户界面:JUnit 中的红条测试失败、绿条测试通过,大家都已经很熟悉了,DOH 也有类似的用户界面,用户在测试时更加一目了然;
  2. 平台无关:DOH 并不依赖某种浏览器平台,甚至不依赖于浏览器;用户可以根据自己的需要在命令行进行 Javascript 的自动化单元测试;
  3. 支持 Ajax:Ajax 编程在 Web 前端开发中是必不可少的一环,DOH 最有价值的一个特性就是支持 Ajax 的测试;
  4. 不只适合与于 Dojo,可用于任何 JavaScript 程序的单元测试。

本文将以 Dojo1.6.1 版本为例,介绍如何使用 DOH 编写测试用例。

DOH 初体验

Dojo 的核心库(dojo)、控件库 (dijit) 以及一部分的扩展库 (dojox) 都自带了比较完备的测试用例,所以在了解如何编写 DOH 测试用例之前,运行一下 Dojo1.6 版本中已有的测试,可以对 DOH 有个大致的了解。

首先下载 Dojo1.6.1 ,DOH 测试框架就在 dojo-release-1.6.1-src/util/doh 文件夹下,其中 runner.html 页面就是基于浏览器的 DOH 测试用户界面。本文中的 http 服务器使用 Apache2.2,有关 Apache 的配置可以参照这里,首先我们来运行一下最常被使用的dojo.query 的测试用例。dojo.query 的测试模块为test._base.query,在浏览器上运行DOH 测试用例非常简单,只要一个url 即可: http://localhost/dojo161/util/doh/runner.html?testModule=tests._base.query ,dojo161 是在 Apache 中设置的虚拟路径,指向 dojo-release-1.6.1-src 目录;下图是 test._base.query 模块的测试结果:

左边的是测试用例列表,可以看到 test._base.query 测试模块里含有两组测试用例:test.base.query 和 test.base.NodeList,同事还显示了该测试集消耗的时间,右边是测试用例的日志。与 JUnit 相同,绿色表示通过测试,而红色反之。

这里需要重点介绍的的是 testModule 参数:DOH 中的测试对象称为测试模块,测试模块中包含测试用例。DOH 提供了两种载入测试模块的方式,一种是直接载入声明了名称的测试模块, 下面的代码声明了名为 test._base.query 的测试模块,包含两组用例:

复制代码
dojo.provide("tests._base.query");
if(dojo.isBrowser){
doh.registerUrl("tests._base.query", dojo.moduleUrl("tests", "_base/query.html"), 60000);
doh.registerUrl("tests._base.NodeList", dojo.moduleUrl("tests", "_base/NodeList.html"), 60000);
}

在测试模块没有直接提供模块名的情况下,DOH 会将 testModule 参数作为路径载入该路径下的测试模块, 例如当 testModule = tests._base 的时候,DOH 将载入 tests/_base/ 下的测试模块,如 test._base.query、test._base.html、test._base.store…等等。

一个简单的 DOH 测试用例

运行了 Dojo 自带的测试用例之后,想必大家对 DOH 有了初步的了解,不过仅仅运行 Dojo 自己的用例怎么会过瘾呢?接下来以一个简单的 DOH 测试用例作为例子,加深对 DOH 的理解。首先在 dojo 工具包的根目录下创建 myTests 文件夹,用来存放测试用例:

之后在 myTests 文件夹下创建 dojoTest.js,请注意模块名与文件路径相对应:

复制代码
dojo.provide("myTests.dojoTest");
doh.register("easyTests", [
function javascriptTest(){
doh.assertEqual(Math.pow(5, 3), 125);
doh.assertTrue(123 == "123");
doh.assertFalse(99999999 > Infinity);
}
]);

上面的代码注册了一个叫 easyTests 的测试用例,现在这个用例里只有一个叫 javascriptTest 的测试。这里用到了 DOH 中的三个断言:doh.assertEqual、doh.assertTrue、doh.assertFalse,如果觉得断言函数名太长,DOH 也提供了 doh.is、doh.t、doh.f 来代替前面三个断言。

细心的读者可能已经注意到 doh.register 的第二个参数是个数组,所以在 easyTests 下还可以继续添加测试,这次我们来测试 dojo.string 的 trim 和 rep 方法:

复制代码
dojo.provide("myTests.dojoTest");
dojo.require("dojo.string");
doh.register("easyTests", [
function javascriptTest(){
...
},
function dojoStringTest(t){
t.is("text", dojo.string.trim(" text \n"));
t.is("xyxyxy", dojo.string.rep("xy", 3));
}
]);

在添加第二个测试之前需要先载入了 dojo.string 模块。在测试中除了 assertEqual 用 is 简写代替以外,doh 也变成了 t,这是因为测试函数的默认参数就是 doh。

在 JUnit 中,setUp 和 tearDown 做了测试的初始化和结束清理工作。在 DOH 也支持这样编写方式:

复制代码
dojo.provide("myTests.dojoTest");
dojo.require("dojo.string");
doh.register("easyTests", [
...,{
name: "Test String Substitute",
_templateString: "",
setUp: function(){
this._templateString = "Dojo ${0} released at ${1}";
},
runTest: function(t){
t.is("Dojo 1.6.1 released at 2010/05/02", dojo.string.substitute(this._templateString, ["1.6.1", "2010/05/02"]));
},
tearDown: function(){
this._templateString = "";
}
]);

name 为该测试的名字;setUp 在 runTest 之前运行;tearDown 在 runTest 之后运行,runTests 是该测试的主体。这里值得注意的是自定义变量 _templateString,方便在 setUp、runTests、tearDown 中使用。

好了,让 DOH 来运行一下这个测试模块, http://localhost/dojo161/util/doh/runner.html?testModule=myTests.dojoTest

与 HTML 集成的 DOH 测试用例

上一节介绍的 DOH 测试用例是比较独立的,但是 Javascript 天生就是要与浏览器、DOM 打交道的,比如 DOM 节点选取功能或拖拽功能的测试就不能独立于 HTML 之外。所以 DOH 测试用例也可以写在 HTML 文件里,直接对 HTML 做操作。接下来我们将编写一个含有 DOH 测试用例的 HTML 文件。

在 myTest 目录下创建 domQuery.html,首先导入必要的 dojo.js 和 runner.js 文件:

复制代码
...
<script type="text/javascript" src="../dojo/dojo.js" djConfig="isDebug: true"></script>
<script type="text/javascript" src="../util/doh/runner.js"></script>
...

然后在 dojo.ready 函数里注册测试用例,注册完毕后不要忘了调用 doh.run() 运行测试:

复制代码
<script>
dojo.addOnLoad(function(){
doh.register("domQueryTest", [
function testQueryByTag(){
doh.is(2, dojo.query("span").length);
},
"doh.is('h3', dojo.query(':first-child', '_foo')[0].innerHTML)",
"doh.is(3, dojo.query('>', '_foo').length)",
...
]);
doh.run();
});
</script>

对于非常简单的测试,如第二个和第三个测试,可以直接用 string 来表示;最后,添加一些测试用的 HTML 代码:

复制代码
<body>
<div class="foo bar" id="_foo">
<h3>h3</h3>
<span id="foo"></span>
<span></span>
</div>
</body>

完成的 HTML 文件并不能被 DOH 的 UI 直接使用,使用 doh.registerUrl 将 HTML 页面注册为测试用例,之后就是运行 DOH 的 runner.html 了。

复制代码
dojo.provide("myTests.domQuery");
try{
doh.registerUrl("myTest.domQuery", dojo.moduleUrl("myTests", "domQuery.html"))
}catch(e){}

集成的 DOH 测试模块

至此,您应该已经了解如何编写单个测试文件。编写测试用例并不困难,而当测试用例越来越多的时候,可以把相关的测试用例集成在一起进行测试,这样就提高了测试的自动化效率。集成测试用例非常简单:只要把已经声明的测试用例用 dojo.require 方法载入到一个模块文件,通常命名为 module.js,当 dojo.require 引入这些文件时,也注册了这些测试。

myTests/module.js

复制代码
dojo.provide("myTests.module");
try{
dojo.require("myTests.dojoTest");
dojo.require("myTests.domQuery");
}catch(e){}

myTests.module 包含了两组测试用例:

手动的给 /doh/runner.html 添加 testModule 参数是个比较烦人的工作,毕竟大多数人是不记得测试模块名称的。在 Dojo 里比较常用的做法是在每个测试模块的同级目录下,添加一个 runTests.html 文件,该文件的作用就是重定向到 runner.html:

myTests/runTests.html

复制代码
<html>
<head>
<title>MyTest Runner</title>
<meta http-equiv="REFRESH" content="0;url=../util/doh/runner.html?testModule=myTests.module">
</head>
<BODY>
Redirecting to D.O.H runner.
</BODY>
</hmtl>

测试 Ajax 异步调用函数

Web2.0 应用中很多功能都是通过 Ajax 异步调用完成的。DOH 提供了 doh.Deferred 对象对一步操作的测试进行支持。doh.Deferred 的使用方式与 dojo.Deferred 类似,用户只要在含有异步调用的单元测试中返回 doh.Deferred 的对象即可。下面代码中的 delay 函数模拟了一个异步调用:延迟触发 count++,之后返回 dojo.Deferred 对象;而在对 delay 的测试函数中给 delay 添加了回调函数,回调函数中的 doh.Deferred 对象的 callback(true) 就表示回调成功。 dojo.Deferred 是 dojo 的 Ajax 调用的核心思想,有兴趣的读者可以看看翻译的 dojo 的官方教程

复制代码
dojo.provide("myTests.delayTest");
function delay(ms){
var count = 0;
var deferred = new dojo.Deferred();
setTimeout(function(){
count++;
deferred.callback(count);
}, ms);
return deferred;
}
doh.register("callbackTest", [
{
name: "Simple ajax call test",
timeout: 5000,
runTests: function(){
var dd = new doh.Deferred();
delay(1000).then(function(res){
doh.is(res == 1);
dd.callback(true);
})
return dd;
}
}
]);

使用命令行运行 DOH

前面已经提到过 DOH 并不依赖于浏览器 UI,我们也可以通过命令行来运行测试模块,定位到 util/doh/ 下,运行以下命令行:

复制代码
java -jar ../shrinksafe/js.jar runner.js testModule=myTest.module

这里需要说明的是,runner.js 是通过 dojo/util 工具包里的 Javascript 的引擎——Rhino 执行的。有关 Rhino 的信息可以看这里: http://www.mozilla.org/rhino/

结束语

尽管前端开发的单元测试还不是那么普及,但随着 Web 程序的日益庞大,维护成本越来越高,前端开发的自动化测试将会占据举足轻重的地位。DOH 是一个灵活而且强大的单元测试框架。它将测试用例模块化为可以单独加载的文件,并提供函数以将测试集成为测试模块,此外还提供了一系列断言 API。

最后,DOH 并不是一个 Dojo 专有的测试框架,也可以用于测试其他的 Javascript 工具包,当然它与 Dojo 的配合使用是最简捷的。DOH 的确是 Dojo 开发者测试 JavaScript 代码的最完整和最有效的框架。

参考资料


感谢张凯峰对本文的审校。

给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家加入到 InfoQ 中文站用户讨论组中与我们的编辑和其他读者朋友交流。

2011-06-10 00:004128

评论

发布
暂无评论
发现更多内容

【Java 进阶训练营】 JVM 知识总结

wgl

宏昆酒店集团携手DataPipeline打造实时数据融合平台,酒店业精益管理的新秘诀

DataPipeline数见科技

数据库 中间件 数据同步 数据融合 数据管理

HarmonyOS(鸿蒙)——单击事件的四种写法

李子捌

28天写作 21天挑战 鸿蒙系统 12月日更

从渔夫和游客说到晒太阳的狗狗派

mtfelix

28天写作

程序员做技术管理需要懂哪些方面?

Seven的代码实验室

程序员 技术管理

模块三-架构文档

撿破爛ぃ

「架构实战营」

模块四-reids存储方案

撿破爛ぃ

「架构实战营」

kali系统之复现漏洞分析与审计

网络安全学海

黑客 网络安全 安全 信息安全 渗透测试

架构实战营模块三课后作业-外包学生管理系统架构文档

Jude

架构实战营

2021,中国计算产业的“攀登者勋章”

脑极体

数据管理典范!「山东城商行联盟数据库准实时数据采集系统」入选2021中国大数据应用样板案例

DataPipeline数见科技

大数据 数据同步 数据融合 数据迁移 数据管理

虚拟机性能监控与故障处理

Joseph295

Netflix系统架构

俞凡

架构 微服务 netflix 大厂实践

Spark-概览

xujiangniao

git tips(qbit)

qbit

git #Github

[架构实战营] 模块三作业

Geek_0ed632

「架构实战营」

儿童教育有感-说话用词不当

wood

28天写作

Spring核心原理之 IoC容器中那些鲜为人知的细节(3)

Tom弹架构

Java spring 源码

《权力——为什么只为某些人拥有》读书笔记

圣迪

特质 权力 影响力

失败与成功是一体两面

石云升

学习笔记 28天写作 12月日更

Code Review全面审查清单

俞凡

Code Review 生产力

2021年终总结:30多岁依然没有放弃自我成长!

王磊

HarmonyOS(鸿蒙)——双击事件

李子捌

28天写作 21天挑战 鸿蒙开发 12月日更

【安全漏洞】CVE-2021-42287&CVE-2021-42278 域内提权

H

网络安全 信息安全 漏洞

linux动态链接的程序如何在其他系统上运行

SkyFire

动态链接 装载器

FFmpeg的一些使用实践

为自己带盐

ffmpeg 28天写作 12月日更

100% 展示 MySQL 语句执行的神器-Optimizer Trace

程序员历小冰

MySQL 28天写作 12月日更

Gin-Vue-Admin 使用gin+vue进行极速开发的全栈开发基础平台【gva第一节】

坚果

Go 28天写作 Vue 3 12月日更

注意:字符串substring方法在jkd6,7,8中的差异

CRMEB

webpack-dev-server启动后, localhost:8080返回index.html的原理

汪子熙

JavaScript 前端开发 webpack 28天写作 12月日更

Dubbo 框架学习笔记十二

风翱

dubbo 12月日更

Dojo单元测试框架DOH介绍_Java_阮奇_InfoQ精选文章