2001年初,Edward Hieatt开始“移植”JUnit,目的是在浏览器中测试JavaScript。从那以后,JsUnit的下载次数已近10 000次,大约300人加入了JsUnit的新闻组。JsUnit支持一般的xUnit功能,完全用JavaScript编写,如果你习惯使用JUnit或者类似的xUnit框架,就会发现JsUnit使用起来相当简单直观。
JsUnit也有一些不同的地方:这里也有setUp()和tearDown(),不过现在作为函数,而不是方法;测试函数(而不是测试方法)分成多个测试页(而不是测试用例);另外JsUnit提供了自己的基于HTML的测试运行工具。表6-1对这两个框架做了比较。
表6-1 JUnit与JsUnit的比较
| JUnit | JsUnit |
| Test类扩展 TestCase | 测试页包含jsUnitCore.js |
| 测试方法 | 测试函数 |
| Test类 | 基于HTML的测试页 |
| TestSuites | 基于HTML的测试集 |
| 多个测试运行工具 | 基于HTML/JavaScript的测试运行工具 |
| setUp()和tearDown()方法 | setUp()和tearDown()函数 |
| 在虚拟机中运行 | 在浏览器中运行 |
| 用Java编写 | 用JavaScript编写 |
6.2.1 起步
对于JsUnit,起步很简单,只需从JsUnit网站(www.edwardh.com/jsunit/)下载JsUnit zip文件。把这个压缩文件解开,会得到一个jsunit文件夹,可以把Web服务器放在这里,这样整个团队或者整个组织就能更容易地使用JsUnit。JsUnit的大部分“核心”都在jsunit/app目录中,在这里可以看到jsUnitCore.js、jsUnitTracer.js和jsUnitTestManager.js,另外还有其他一些文件。如果你想运行具体的JsUnit测试,可以使用testRunner.html来运行jsunit/tests目录中找到的任何测试页。如果你在使用IntelliJ,而且想具体使用JsUnit,jsunit/intellij目录中包含了需要的所有适当文件。
6.2.2 编写测试
用JsUnit编写测试与用JUnit编写测试很相似。测试函数不能有任何参数,必须有一个前缀test,例如testDateValidation()。测试函数包含在一个测试页(test page)中,这类似于JUnit中的一个Test类。测试页必须包含jsUnitCore.js文件,解开JsUnit zip文件后,就会在jsunit/app目录中找到这个文件。包含这个JavaScript文件实际上就是把一个外部JavaScript文件增加到页面中;只需使用脚本元素<script language="JavaScript" src="jsUnitCore.js"></script>来引用这个文件,要记住,如果你的当前目录不是jsunit/app目录,则还需要提供jsUnitCore.js文件的相关路径信息。当然,在测试页中可以包含任意多个其他函数或JavaScript;实际上,把多个JavaScript函数放在分开的文件中,是一个很好的做法。测试函数也可以放在单独的JavaScript文件中;不过,如果这样做,就需要使用exposeTestFunctionNames()方法,这样JsUnit才能找到测试函数。实际上,如果需要针对不同的页面内容建立测试,可以把测试函数放在一个单独的文件中,这样能避免复制-粘贴问题带来的痛苦。
一般地,JsUnit会自动发现测试函数,就像JUnit会发现所有测试方法一样。不过,有些操作系统/浏览器不能合作。如果你发现不能如你所愿地发现测试函数,使用exposeTestFunctionNames()方法就能解决这个问题。
断言方法
现在你对测试函数和测试页有一定的了解了,下面需要写一些实际的测试!与用JUnit一样,你可以使用断言方法(assert method)。断言方法是任何单元测试的基本模块,它们只是一些简单的布尔表达式,可以指示一个给定语句为true还是false。断言失败时,就会产生一个错误,这样将得到众所周知的红条。与JUnit不同,JsUnit没有提供那么丰富的断言方法,但是已经足够你测试JavaScript代码了。注意,除了fail()方法的注释外,其他断言方法的注释都是可选的(这与JUnit类似,甚至也“不正确”地把可选参数放在最前面,而不是最后)。
assert([comment], booleanValue)
assertTrue([comment], booleanValue)
assertFalse([comment], booleanValue)
assertEquals([comment], value1, value2)
assertNotEquals([comment], value1, value2)
assertNull([comment], value)
assertNotNull([comment], value)
assertUndefined([comment], value)
assertNotUndefined([comment], value)
assertNaN([comment], value)
assertNotNaN([comment], value)
fail(comment)
要看这些方法怎么用于测试(象征性地从字面了解),只需看JsUnit下载包提供的测试页就行了。JsUnit还提供了一个变量:JSUNIT_UNDEFINED_VALUE,它映射到JavaScript中的undefined变量。
我们说的已经够多的了!下面来看一个简单的测试!这个例子中有一个简单的函数,会让两个数相加,而且有两个测试:一个用于正整数的相加,另一个用于负整数相加。要测试这个函数,先创建一个简单的Web页面,如代码清单6-1所示,其中包含了jsUnitCore.js文件,另外包含了要测试的函数和测试函数[4]。当然,在生产代码中,可能不能把测试代码与所测试的函数混在一起,但是作为第一次尝试这样做未尝不可。
代码清单6-1 简单的测试页
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>A Simple Test Page</title>
<script language="JavaScript" src="../jsunit/app/jsUnitCore.js"></script>
<script language="JavaScript">
function addTwoNumbers(value1, value2) {
return value1 + value2;
}
function testValidArgs() {
assertEquals("2 + 2 is 4", 4, addTwoNumbers(2, 2));
}
function testWithNegativeNumbers() {
assertEquals("negative numbers: -2 + -2 is -4", -4,
addTwoNumbers(-2, -2));
}
</script>
</head>
<body>
This is a simple test page for addTwoNumbers(value1, value2).
</body>
</html>
运行这些测试会得到图6-1所示的结果。(后面将更详细地介绍测试运行工具。)
图6-1 简单测试的结果
显然,不太可能把生产代码(函数)与测试函数混在同一个测试页中。你可能会把生产代码放在一个单独的JavaScript文件中,然后在测试页中包含这个文件。代码清单6-2就采用了这种方法。
代码清单6-2 一种更典型的方法
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>Another Test Page</title>
<script language="JavaScript" src="../jsunit/app/jsUnitCore.js"></script>
<script language="JavaScript" src="simpleJS.js"></script>
<script language="JavaScript">
function testValidArgs() {
assertEquals("2 + 2 is 4", 4, addTwoNumbers(2, 2));
}
function testWithNegativeNumbers() {
assertEquals("negative numbers: -2 + -2 is -4", -4,
addTwoNumbers(-2, -2));
}
</script>
</head>
<body>
This is a simple test page for the simpleJS file.
</body>
</html>
JavaScript文件实际上相当简单,如代码清单6-3所示。
代码清单6-3 simple.js
function addTwoNumbers(value1, value2) {
return parseInt(value1) + parseInt(value2);
}
不出所料,结果是一样的(见图6-2)。
图6-2 采用典型方法的结果
可以看到,两个测试函数会自动被发现,而且通常都是这样。不过,如果打开测试页,点击Run之后什么也没有发生,可能就需要使用exposeTestFunctionNames(),以确保JsUnit能找到你的测试,如代码清单6-4所示。
代码清单6-4 使用exposeTestFunctionNames()
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>A Test Page With exposeTestFunctions</title>
<script language="JavaScript" src="../jsunit/app/jsUnitCore.js"></script>
<script language="JavaScript" src="simpleJS.js"></script>
<script language="JavaScript">
function testValidArgs() {
assertEquals("2 + 2 is 4", 4, addTwoNumbers(2, 2));
}
function testWithNegativeNumbers() {
assertEquals("negative numbers: -2 + -2 is -4", -4,
addTwoNumbers(-2, -2));
}
function exposeTestFunctionNames() {
var tests = new Array(2);
tests[0] = "testValidArgs";
tests[1] = "testWithNegativeNumbers";
return tests;
}
</script>
</head>
<body>
This is a simple test page that uses exposeTestFunctionNames.
</body>
</html>
如你所愿,这样就能工作了(见图6-3)。
如果看到如图6-4中所示的错误消息,说明你可能忘了在测试页中包含jsUnitCore.js,或者文件的路径不对。请检查测试页,再运行一次。
点击OK时,会提示你重试或取消这个测试,如图6-5所示。
图6-3 运行使用exposeTestFunctionNames()的测试
图6-4 JsUnit错误消息
图6-5 重试或取消
setUp()和tearDown()
像JUnit一样,JsUnit也支持setUp()和tearDown()。JsUnit与JUnit有一点是一样的,即setUp()和tearDown()是可选的,而且setUp()会在每个测试之前调用,tearDown()会在每个测试之后调用。在大量使用setUp()和tearDown()之前,需要了解JUnit与JsUnit中setUp()和tearDown()方法的实现有两个重要区别。在JUnit中,每次测试运行会导致创建Test类的一个新实例,这说明,声明的所有实例变量在下一次测试运行时会“重置”。不过,JsUnit有所不同,它不会为每次测试运行重新加载测试页,所以变量状态会在多次测试之间保留。还有一个重要区别与测试顺序有关,使用JUnit的话,测试执行的顺序是不能保证的。在JsUnit中,测试会按测试页中声明的顺序执行,先从最上面的测试开始[5]。
代码清单6-5显示了一个相当复杂的例子,其中使用了setUp()和tearDown()方法。这里同样以前面创建的add方法为基础,但是这一次会增加一个表单。你要使用setUp()填写这个表单,然后使用tearDown()方法自行清空。
代码清单6-5 使用setUp()和tearDown()
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>Using setUp and tearDown</title>
<script language="JavaScript" src="jsunit/app/jsUnitCore.js"></script>
<script language="JavaScript" src="simpleJS.js"></script>
<script language="JavaScript">
function setUp() {
document.getElementById("value1").value = "2";
document.getElementById("value2").value = "2";
}
function testValidArgs() {
assertEquals("2 + 2 should equal 4", 4, addNumbers());
}
function addNumbers() {
var val1 = document.getElementById("value1").value;
var val2 = document.getElementById("value2").value;
return addTwoNumbers(val1, val2);
}
function tearDown() {
document.getElementById("value1").value = "";
document.getElementById("value2").value = "";
}
</script>
</head>
<body>
<form id="test">
<input type="text" size="3" id="value1"/>
<input type="text" size="3" id="value2"/>
<input type="button" value="Add" onclick="addNumbers()"/>
</form>
</body>
</html>
不出意外,结果是很典型的(见图6-6)。
图6-6 运行setUp()/tearDown()的例子
JsUnit还包含另外一个特性:setUpPage()函数,这是JUnit中所没有的。JUnit社区的有些人认为JUnit缺乏“一次性启动”和“一次性关闭”功能是设计中一个败笔。有些人则更进一步,尝试扩展JUnit来包含这个特性,或者干脆创建新的测试框架;另外,JUnit的FAQ网站甚至还介绍了一种方法来模拟这种行为[6]。大多数有关JUnit的书也讨论了解决这个问题的方法。
不过,JsUnit则不同,它确实包含了一次性启动方法;setUpPage()函数只对每个测试页调用一次,即在所有测试函数调用之前调用。现在,你可能已经发现,这里很适合完成预处理,特别是在运行测试之前如果需要向页面加载一些数据,setUpPage()函数就非常有用。不同于setUp()和tearDown()函数的是,使用setUpPage()不只是把处理放在这个函数中就行了的。如果确实选择使用这个特性,一定要保证函数完成时要把setUpPageStatus变量设置为complete,这就告诉JsUnit可以继续,接下来可以执行测试页上的测试了。
想看个例子?那好,我们再来看前面的simpleJS.js文件,再增加3个函数,补充更多的数学特性。在此包括减法、乘法和除法函数,如代码清单6-6所示。
代码清单6-6 simpleJS2.js
function addTwoNumbers(value1, value2) {
return parseInt(value1) + parseInt(value2);
}
function subtractTwoNumbers(value1, value2) {
return parseInt(value1) - parseInt(value2);
}
function multiplyTwoNumbers(value1, value2) {
return parseInt(value1) * parseInt(value2);
}
function divideTwoNumbers(value1, value2) {
return parseInt(value1) / parseInt(value2);
}
下面使用setUpPage()函数建立一些简单的测试数据,如代码清单6-7。请注意函数最后一行,必须告诉JsUnit你已经建好了测试页。
代码清单6-7 使用 setUpPage()函数
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>Using setUp and tearDown</title>
<script language="JavaScript" src="jsunit/app/jsUnitCore.js"></script>
<script language="JavaScript" src="simpleJS2.js"></script>
<script language="JavaScript">
var arg1;
var arg2;
function setUpPage() {
arg1 = 2;
arg2 = 2;
setUpPageStatus = "complete";
}
function testAddValidArgs() {
assertEquals("2 + 2 should equal 4", 4, addTwoNumbers(arg1, arg2));
}
function testSubtractValidArgs() {
assertEquals("2 - 2 should equal 0", 0, subtractTwoNumbers(arg1, arg2));
}
function testMultiplyValidArgs() {
assertEquals("2 * 2 should equal 4", 4, multiplyTwoNumbers(arg1, arg2));
}
function testDivideValidArgs() {
assertEquals("2 / 2 should equal 1", 1, divideTwoNumbers(arg1, arg2));
}
</script>
</head>
<body>
This is an example of using setUpPage.
</body>
</html>
这里还是得到一个绿条(见图6-7),你可能有点厌倦了吧
图6-7 运行setUpPage()测试
测试集
有了一些测试页之后,你可能想把它们组织为测试集(test suite,也称测试套件),这与JUnit中的TestSuite很相似。测试集把不同的测试页分组组织,这样只需运行一个测试集就能一次运行类似的一组测试。测试集其实就是一些特殊的测试页,其中包含的测试页或其他测试集(相应地就有了一个主测试集)会按顺序运行。
可以采用定义测试页的方式来定义测试集,不过有两个例外。首先,测试集中不能包含任何测试函数;其次,你的测试集必须包含一个返回JsUnitTestSuite对象的suite()函数。可以使用两个方法向测试集中增加测试页或子测试集:addTestPage(testPage)和addTestSuite(testSuite)。前者向测试集中增加单个的测试页;后者向测试集中增加另一个测试集。要记住,向测试集中增加一个测试页时,需要提供一个完全限定名,或者要提供测试页文件相对于测试运行工具的相对路径名。换句话说,如果jsunit文件夹在测试页所在的目录下,那么测试运行工具就比你的测试更深一个层次,在下一层文件夹中。如果看到图6-8所示的一个错误,要确保提供的路径确实是相对于测试运行工具的相对路径。如果想向测试集中增加其他的测试集,要记住,提供给addTestSuite的参数类型必须为JsUnitTestSuite,它要在suite函数所在的同一个页面中声明。你可在jsunit/tests目录中看到一个测试集例子,希望对你有所帮助。代码清单6-8也显示了一个例子。
图6-8 运行一个有路径错误的测试集
代码清单6-8 一个简单的测试集
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>Sample Test Suite</title>
<script language="JavaScript" src="jsunit/app/jsUnitCore.js"></script>
<script language="JavaScript">
function sampleSuite() {
var sampleSuite = new top.jsUnitTestSuite();
sampleSuite.addTestPage("../anotherTestPage.html");
sampleSuite.addTestPage("../simpleTestPage.html");
return sampleSuite;
}
function suite() {
var testSuite = new top.jsUnitTestSuite();
testSuite.addTestSuite(sampelSuite());
testSuite.addTestPage("../setupTearDownExample.html");
return testSuite;
}
</script>
</head>
<body>
This is a simple test suite.
</body>
</html>
正如你所想,这会得到一个绿条(见图6-9)。
图6-9 运行测试集
跟踪和日志
编写JavaScript时最困难的一部分就是跟踪代码。因为JavaScript不同于其他许多语言,没有一个得力的日志库来帮助你,因此无法以一种一致的方式打印语句,相反,你必须使用alert()。当然,alert()也不是不行,但它肯定不是最理想的方法。为了查找一个问题,要“沿路”布下一大堆alert()函数,这是很讨厌的,而且一旦修正了bug,还要再把这一大堆alert()代码统统去掉。当然,等你删除掉所有额外的alert()函数后,没准又会出现另一个bug,而且就出现在上一个bug的附近,这就要求你又得把所有alert()函数再加上。你现在应该知道,为什么没有多少人喜欢JavaScript了吧!
为了让JavaScript开发人员的日子更好过,JsUnit支持跟踪!JsUnit包含以下3个函数,任何测试都可以调用(注意,在每个函数中,value参数是可选的):
warn(message, [value])
inform(message, [value])
debug(message, [value])
JsUnit支持3个跟踪级别:warn(警告)、info(信息)和debug(调试)。运行测试时,要指定你想在哪个级别上输出。这3个级别按以下顺序层叠:warn, info, debug。这说明,如果运行测试时选择debug,就会看到warn()、inform()或debug()函数发出的所有消息。如果选择warn,则只会显示由warn()函数发出的消息,选择info则会显示由warn()和inform()发出的消息。默认值为no tracing(不跟踪)。下面向这个简单例子增加一些跟踪函数[7],来看看会发生什么(见代码清单6-9)。
代码清单6-9 向一个测试增加跟踪函数
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>A Simple Test Page with Tracing</title>
<script language="JavaScript" src="jsunit/app/jsUnitCore.js"></script>
<script language="JavaScript">
function addTwoNumbers(value1, value2) {
warn("this is a warning message");
warn("this is a warning message with a value", value1);
return value1 + value2;
}
function testValidArgs() {
inform("this is an inform message");
assertEquals("2 + 2 is 4", 4, addTwoNumbers(2, 2));
}
function testWithNegativeNumbers() {
debug("this is a debug message");
assertEquals("negative numbers: -2 + -2 is -4", -4,
addTwoNumbers(-2, -2));
}
</script>
</head>
<body>
This is a simple test page for addTwoNumbers(value1, value2) with tracing.
</body>
</html>
要看跟踪函数得到的输出,需要在测试运行工具中启用跟踪,并选择适当的跟踪级别。如果选择debug,可以看到来自这3个函数的全部消息,“Close Old Trace Window on New Run”(下一次运行测试时关闭上一个跟踪窗口)复选框的作用是,如果你愿意,可以保留以前运行测试的跟踪结果(见图6-10)。
图6-10 设置跟踪级别
这一回不再是在测试运行工具中千篇一律地显示一个绿条了。图6-11显示了跟踪函数的输出。
图6-11 跟踪输出
6.2.3 运行测试
你已经写了一些测试,下面需要运行它们,为此可以使用JsUnit测试运行工具。你已经看到启动测试运行工具的许多例子,不过这是怎么做到的呢?为了访问这个运行工具,要把浏览器指向jsunit文件夹中的testRunner.html文件。这个测试运行工具如图6-12所示。
这个测试运行工具非常类似于JUnit中常用的图形化运行工具。(不过,有意思的是,我们期盼已久的JUnit 4并没有包括图形测试运行工具,而且以后也不会增加。)要运行一个测试,可以点击Choose File(选择文件),来选择要运行的文件。不出所料,在测试运行工具发现失败之前,进度条一直是绿的,如图6-13所示。Runs字段指示测试函数的总数,还可能报告错误或失败。
图6-12 JsUnit测试运行工具
图6-13 成功运行的JsUnit测试运行工具
错误(error)来自浏览器,指示测试页出现了某个问题,失败(failure)指示你的某个断言失败。特定的错误或失败会显示在Errors and Failures文本框中。要了解一个错误或失败的更详细的信息,可以双击相应测试函数。或者,选中这个测试函数,再选择Show Selected。如果是失败,就会出现一个警告,显示出期望值和实际值,另外还会显示你在断言中增加的所有消息。如果看到一个错误,相应的消息(可能)会帮助你缩小查找的范围,更快地找到问题所在。
下面再来看展示setUp()和tearDown()函数的例子,对这个例子做一个小小的调整。在addNumbers()函数中故意增加一个错误,试图获取一个不存在的元素,如代码清单6-10所示。
代码清单6-10 故意增加的一个错误
function addNumbers() {
//arg1 doesn't exist!
var val1 = document.getElementById("arg1").value;
var val2 = document.getElementById("value2").value;
return addTwoNumbers(val1, val2);
}
运行这个测试,可以想像到,会得到一个红条!注意Errors and Failures文本框中显示了一个错误(见图6-14)。
图6-14 JsUnit测试运行工具错误
通过进一步观察,可以看到如图6-15显示的详细信息。由此得知,arg1没有任何属性,你应该查查addNumbers()方法来看发生了什么问题。
图6-15 错误详细信息
在运行工具中,失败看上去都是一样的(都会得到一个红条),但是详细信息不同。如果你熟悉JUnit,会发现这里的失败信息看上去非常熟悉。下面再回到simpleJS.js文件的测试。假装你希望 2 + 2 等于 5,如代码清单6-11所示,来看看会发生什么。
代码清单6-11 另一个故意的错误
function testValidArgs() {
assertEquals("we really do know that 2 + 2 is 4", 5, addTwoNumbers(2, 2));
}
不出所料,测试运行工具会显示一个红条,但是提供详细信息的警告有所不同,
。
图6-16 JsUnit 测试运行工具失败
失败的详细信息很有帮助,如图6-17所示。可以看到你在断言函数中键入的所有消息,会得到期望值和实际值,还会得到一个栈轨迹,显示出哪里出现了失败。
以上基本上介绍了测试运行工具中的所有字段,只有两个字段还没有谈到:Page Load Timeout(页面加载超时)和Setup Page Timeout(建立页面超时)。Page Load Timeout字段是指测试运行工具对于你的测试页有多耐心,如果你的测试页需要的时间比这个框中指定的时间(以秒为单位)长,测试运行工具就会抛出一个错误,如图6-18所示。如果看到这个错误,就要确保你的测试页确实是一个测试页。(换句话说,要保证它包含了jsUnitCore.js文件,而且要确保路径语句是正确的,另外如果是跨网络访问页面,还要保证可以得到这个页面)。当然,如果你在做分布式测试,测试需要的时间就会比较长,这时可能需要增加Page Load Timeout字段中的时间。
时可能需要增加Page Load Timeout字段中的时间。
| 图6-17 失败详细信息 | 图6-18 页面超时警告 |
点击OK时,可能会提示你重试或取消这一次测试,如图6-19所示。
Setup Page Timeout字段是类似的,但是只有当你使用了setUpPage()函数时才适用。它表示了JsUnit测试运行工具等待setUpPage()函数结束的时间(同样以秒为单位)。不要忘了,如果测试页有一个setUpPage()函数,测试运行工具会一直等待,直到setUpPageStatus变量置为complete。可以想像到,测试运行工具会通知你发生了一些意想不到的事情,如图6-20所示。
| 图6-19 重试或取消 | 图6-20 建立页面超时错误 |
类似于页面加载超时,此时你也有机会重试或取消。这很有可能说明你忘了告诉JsUnit:你的setUpPage()函数已经完成。如果确实已经将setUpPageStatus变量置为complete,可能得查看一下在setUpPage()函数里做了些什么。你可能需要增加Setup Page Timeout字段的设置值,或者想办法加快建立页面的速度。
6.2.4 使用标准/定制查询串
如此说来,测试运行工具是很强大的,但是每天都得打开这个测试运行工具,并浏览你的测试页或测试集,这可能非常烦人。好在可以使用一些查询串来预置要运行的文件,让测试运行工具自动地运行测试,甚至可以向测试传递参数!
下面先介绍一些基础知识。假设你有一个测试集,每天早上都要运行,然后你才能喝上早茶。当然,你完全可以打开测试运行工具文件,浏览要运行的测试集,然后点击Run;不过,如果你最近读过Mike Clark所著的Pragmatic Project Automation(Pragmatic Programmers公司2004年出版),可能希望有一种办法能自动地完成这种重复性的任务。好在测试运行工具支持testPage[8] 查询串。在浏览器地址栏中键入以下地址(当然,根据具体环境可能要做相应调整),就会在浏览器中启动测试运行工具,并且预置了给定的测试:
file:///Users/nate/projects/chapter6/web/jsunit/testRunner.html?testPage=/Users/
nate/projects/chapter6/web/sampleTestSuite.html
就像魔法一般,你会看到测试运行工具运行了起来,你传递给testPage的实参会显示在file(文件)框中,如图6-21所示。注意,现在没有Browse(浏览)按钮了,因为你已经告诉测试运行工具要运行什么,它已经知道了。
图6-21 使用testPage查询串
当然,聪明的开发人员可能会对经常运行的测试集建立书签。如果你觉得testPage还不错,可能还想让autoRun试试身手!顾名思义,可以结合autoRun[9]和testPage来加载你最喜欢的测试页或测试集,然后自动运行。这样一个示例查询串如下所示:
file:///Users/nate/projects/chapter6/web/jsunit/testRunner.html?testPage=/Users/
nate/projects/chapter6/web/testPageWithExposeTests.html&autoRun=true
你应该能看到一个漂亮的绿条(见图6-22)。注意这里有Run按钮。(既然还是不能浏览其他测试集或测试页,为什么还要一个Run按钮呢?)因此,你可以很轻松地重新运行你的测试。同样地,能干的程序员很可能在浏览器上为这个查询增加书签。
图6-22 使用带autoRun的testPage的例子
你可能认为开发人员只需要testPage和autoRun,但实际上还不只如此!在后台,测试运行工具会把各个测试页增加到一个不可见的帧。大多数情况下这样是可以的,但是如果有些代码必须在一个可见的帧中运行,而且你使用的是IE,就可以告诉测试运行工具要在一个可见帧中显示你的页面。如果在查询串中加入了showTestFrame,JsUnit就会在一个可见的帧中显示你的页面。如果你输入了showTestFrame=true,测试页会在一个默认高度的帧中显示。如果这个默认值不合适,可以传入一个整数值,将帧高度指定为参数值(以像素为单位)。
有时,你可能想向测试页或测试集传递特定的值。可以在查询串中增加任意的行参/实参对,在测试页或测试集中使用top.jsUnitParmHash.parameterName或top.jsUnit- ParmHash['parameterName']来获取这些值。假设按以下路径打开测试运行工具:file:/// Users/nate/projects/chapter6/web/jsunit/testRunner.html?key=1,就可以使用top.jsUnitParmHash.key或top.jsUnitParmHash['key']来访问参数key。如果你只想运行某些测试,这就很有用。代码清单6-12显示了一个复杂的例子。
代码清单6-12 使用定制查询的测试集例子
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>Sample Test Suite</title>
<script language="JavaScript" src="jsunit/app/jsUnitCore.js"></script>
<script language="JavaScript">
function sampelSuite() {
var suiteToRun = top.jsUnitParmHash.suite;
var sampleSuite = new top.jsUnitTestSuite();
if(suiteToRun == "other") {
sampleSuite.addTestPage("../anotherTestPage.html");
} else {
sampleSuite.addTestPage("../simpleTestPage.html");
}
return sampleSuite;
}
function suite() {
var testSuite = new top.jsUnitTestSuite();
testSuite.addTestSuite(sampelSuite());
testSuite.addTestPage("../setupTearDownExample.html");
return testSuite;
}
</script>
</head>
<body>
This is a simple test suite that uses custom queries.
</body>
</html>
可以想见,在测试运行工具中为路径追加?suite=other,这会导致运行anotherTest-
Page.html,而不运行simpleTestPage.html(见图6-23)。当然,针对当前的流控制逻辑,去掉这个参数,则会正好相反:只运行simpleTestPage.html,而跳过anotherTestPage. html。
图6-23 基于一个定制查询参数运行测试集
尽管这显然是一个简单的例子,但是从中可以看出,在不同场合下,使用定制参数来运行测试集或测试页是非常有用的。当然,你可能想为浏览器的书签文件夹增加这些特殊的查询串,以便以后使用。
还有一个有意思的标准参数是debug。不要把这个参数与debug跟踪级别混为一谈,它们完全不同。debug参数会在使用JsUnit的开发工作中用到。对于这个查询串,你可能不会做太多工作,但是如果你很好奇,可以知道有这样一个参数(见图6-24)。
另外还有两个标准参数,只有与JsUnit服务器结合时才有意义:sub- mitResults和resultId。我们稍后会讨论JsUnit服务器,到时你会更清楚地了解这两个参数,不过要知道,在测试运行工具的浏览器路径中增加submitResults=true,这会告诉JsUnit:将测试的结果发送到JsUnit的“acceptor” servlet。这对你意味着什么?这可是个秘密,不过可以告诉你,使用这个参数的话,测试运行的结果会创建结果的一个XML表示(与JUnit的XML输出有同样的结构),以便以后获取。
只有当使用submitResults=true时,resultId查询才有意义。我们只对JsUnit服务器的工作稍有涉及,根据我们介绍的这一点点内容,你可能可以猜出:提交到JsUnit服务器的各个测试应该有各自惟一的标识符。可以让JsUnit来提供自己的ID,但是如果必须使用你喜欢的编号,也可以通过resultId参数传递这样一个数。
6.2.5 使用JsUnit服务器
虽然向测试运行工具传递各种参数可以较容易地完成自动化测试,但是你很快就会厌倦这样以常规方式手工地运行测试,特别是要考虑到多个操作系统上的多个浏览器时,更是如此。你可能想跟踪以前运行的结果,以便进行审计或完成质量保证。为解决这些问题,JsUnit服务器会提供测试结果的XML日志,从JUnit或Ant脚本运行测试,以及在远程主机上从JUnit或Ant脚本运行测试。
利用JsUnit服务器,你只需点击一个按钮,就能基于你的操作系统/浏览器运行整个测试集。另外,只需在一个重要步骤中把JavaScript的测试增加到Ant脚本中,就能使之成为构建过程的一部分。JsUnit服务器包括一组Java servlet,它们在可嵌入的Jetty开源Web服务器上运行,这样你就无需在打算测试的每个主机上都配置一个Web服务器/servlet容器。一旦完成配置,这个过程就很简单了,只是按下一个按钮而已!
配置服务器
在利用JsUnit服务器之前,需要先进行配置。为此可以修改build.xml文件,这个文件在jsunit文件夹中。这个文件的最前面有一组属性,可以修改这些属性来满足你的需要。这些变量都很好理解,详细内容请见表6-2。
表6-2 服务器配置
| 环境变量 | 内 容 |
| browserFileNames | 你想测试的一组浏览器可执行文件,这是一个完全路径列表,各个路径之间用逗号分隔 |
| url | 测试运行工具的URL,包括适当的查询串来自动运行适当的测试集 |
| port | 运行JsUnit服务器的端口,如果没有这个变量,会就使用端口8080 |
| resourceBase | 定义JsUnit服务器的文档根。如果是空值(一般设置),就会使用jsunit安装目录 |
| logsdirectory | 运行测试的结果会写到这个目录。如果是空值,则默认为jsunit/logs |
| remoteMachineURLs | 指定你想在哪些远程主机上运行测试,即这些远程主机的URL列表,各URL之间用逗号分隔。这些远程主机需要已经安装配置了一个JsUnit服务器 |
一旦配置了测试集(或测试页)和浏览器组合,只需运行standalone_test目标。在NetBeans开发环境中,可以得到如图6-25所示的结果。
图6-25 从NetBeans运行standalone_test目标
运行这个目标,会在你指定的端口上启动Jetty服务器,而一旦服务器开始运行,就会启动你指定的浏览器,而且会运行你配置的测试。当然,不必特别注意结果,如果出现一个失败或错误,它会显示在Ant任务的输出中,指出任务失败。
--------------------- testPageWithError.html:testValidArgs had an error:
Error message is: "TypeError: document.getElementById("arg1") has no properties"
Stack trace follows:
addNumbers()@file:////Users/nate/projects/chapter6/web/testPageWithError.html:21
testValidArgs()@file:////Users/nate/projects/chapter6/web/testPageWithError.html:16
("testValidArgs")@file:///Users/nate/projects/chapter6/web/jsunit/
app/jsUnitTestManager.js:359
("testValidArgs")@file:///Users/nate/projects/chapter6/web/jsunit/
app/jsUnitTestManager.js:359 ()@file:///Users/nate/projects/chapter6/web/jsunit/
app/jsUnitTestManager.js:166 ("file:////Users/nate/projects/chapter6/web/
testPageWithError.html")
@file:///Users/nate/projects/chapter6/web/jsunit/app/jsUnitTestManager.js:104
()@file:///Users/nate/projects/chapter6/web/jsunit/app/jsUnitTestManager.js:338
@file:///Users/nate/projects/chapter6/web/jsunit/app/jsUnitTestManager.js:335
---------------------
假设在查询串上增加了submitResults=true,还可以查看日志文件来检查结果。日志文件采用JUnit结果同样的XML格式,所以像自动化JUnit测试集一样,可以用同样的转换很容易地加以处理。可以直接查看XML文件,也可以使用JsUnit内置的“displayer”servlet。
要使用“displayer”servlet,首先确保JsUnit服务器已经运行。如果未运行,只需运行start_server目标。一旦JsUnit服务器开始运行,打开你最喜欢的浏览器,指向localhost:8080/jsunit/displayer?id=×××,这里的×××是你想查看的结果日志的ID。你会看到测试运行的结果,如图6-26所示。
图6-26 查看测试运行的结果
在远程主机上运行测试
如果想在分布式主机上运行测试集,需要使用distributed_test Ant任务。当然,必须在要用的每个远程主机上配置一个JsUnit服务器,并配置适当的测试和浏览器。运行distributed_test Ant任务会调用一个JUnit测试(net.jsunit.DistributedTest),它会进一步在配置中提供的每个远程主机上调用一个servlet。这个servlet运行的基于JUnit的测试与本地运行的测试(net.jsunit.StandaloneTest)是一样的。然后,会在远程主机上配置的浏览器中运行测试,结果发送回发起者。
6.2.6 获得帮助
在这一章中,我们只是触及JsUnit的皮毛,所以如果你想到别处了解更多有关信息,也没有关系,我们不会难过的。当然,你的第一站应当是JsUnit网站(www.edwardh.com/jsunit/)。实际上,本章许多内容都是从这里参考得来的。如果你有具体的问题,可以通过电子邮件询问JsUnit的创始人Edward Hieatt,或者可以加入新闻组groups.yahoo.com/group/jsunit/。你不会被大量邮件淹没,2005年每个月的邮件不会超过10封。不过,对于常见的问题,这是一个提供解答的绝好资源。如果你发现bug,可以提交给SourceForge bug tracker(https:// sourceforge.net/tracker/?group_id=28041&atid=391976)。除此以外,还可以在Google上查查看能找到哪些资源。
6.2.7 还能用什么?
别误解我们的意思,我们当然喜欢JsUnit,不过你可能还想考虑另外的选择。ThoughtWorks的高手们发布了一个Web应用测试工具,名叫Selenium[10]。开发Selenium是为了测试一个基于内部ThoughtWorks浏览器的应用,但是开发者非常热心,把代码贡献给了开源世界,所以我们也能有幸享用。Selenium可以在所有主要浏览器上运行,而且可以在常用的操作系统上运行。类似于JsUnit,Selenium测试也在浏览器上运行,因此非常适合测试系统功能和浏览器兼容性。要了解更多的有关内容,请参见selenium.thoughtworks.com/index.html。
如果你在服务器端使用了Java,还有很多其他的选择。HttpUnit是用Java编写的,可以用来模拟浏览器。利用HttpUnit,能模拟提交一个表单,测试请求返回的页面,并检查基本JavaScript。一般地,你可能会结合使用HttpUnit和JUnit。HtmlUnit类似于HttpUnit,不过它对页面建模,而不像HttpUnit那样对请求和响应建模。HtmlUnit模拟了浏览器,并与JUnit结合使用。HttpUnit有一个很有意思的特性,它能模拟特定的浏览器,因此你可以测试任何特定于浏览器的逻辑。
在HttpUnit之上还建立了基于Java的jWebUnit。基本说来,jWebUnit大大简化了导航规则,并提供了一些预置的断言。这个工具同样要与JUnit结合使用。JUnit再向前一步是验收测试框架FitNesse,这是Object Mentor的一些人写的。FitNesse之所以独树一帜,是因为你的客户可以使用这个框架定义应用应该做什么。你(甚至你的客户)可以创建输入表,并指定运行应用的期望结果。正如你所料,成功的测试会有绿的结果,失败则显示红色。FitNesse是xUnit测试的一个补充,Object Mentor有一个精辟的说法:xUnit可以确保你正确地建立了代码,而FitNesse可以确保你建立了正确的代码!
这一节很短,主要思想是说明你有很多选择来完成测试。尽管我们介绍的都是免费的工具,但是当然也存在需要花钱的工具。最后我们建议了一种综合的方法,可以充分利用所有这些工具的功能。你可以拿我们在这里谈到的工具小试牛刀,还可以在网上搜索我们没有提到的其他工具,你很快就会找到最适用的方法。



























发表评论