现在的位置: 首页 > 综合 > 正文

高效 JavaScript 单元测试

2017年04月15日 ⁄ 综合 ⁄ 共 11540字 ⁄ 字号 评论关闭

一个损坏的 JavaScript 代码示例

        Web 应用程序面临的一个最大挑战是支持不同版本的 Web 浏览器。能在 Safari 上运行的 JavaScript 代码不一定能在 Windows® Internet Explorer (IE)、Firefox 或 Google Chrome 上运行。这个挑战的根源是呈现层中的 JavaScript 代码从一开始就没有进行测试。如果没有对代码进行单元测试,那么在升级或支持新浏览器后,组织可能需要花钱反复测试 Web 应用程序。本文将展示如何通过高效的 JavaScript 代码单元测试降低测试成本。

        一个常见用例是登录表单 JavaScript 验证。考虑清单 1 中的表单。

        清单 1. 登录表单

01 <FORM>
02     <table>
03         <tr>
04             <td>Username</td>
05             <td><input type="text" id="username"/></td>
06             <td><span id="usernameMessage"></span></td>
07         </tr>
08         <tr>
09             <td>Password</td>
10             <td><input type="password" id="password"/></td>
11             <td><span id="passwordMessage"></span></td>
12         </tr>   
13         <tr>
14             <td><input type="button" onclick="new
appnamespace.
15             ApplicationUtil
() .validateLoginForm ()" 
value="Submit"/></td>
16         </tr>
17     </table>
18 </FORM>

        这个表单很简单,仅包含用户名和密码字段。单击提交按钮时,将通过 ApplicationUtil 执行一个特定的表单验证。以下是负责验证 HTML 表单的 JavaScript
对象。清单 2 显示了 ApplicationUtil 对象的代码。

        清单 2. 损坏的 ApplicationUtil 对象代码

01 appnamespace
= {};
02  
03 appnamespace.ApplicationUtil
function()
{};
04  
05 appnamespace.ApplicationUtil.prototype.validateLoginForm
=  
function(){
06     var error
true;
07     document.getElementById
(
"usernameMessage")
.innerText = 
"";
08     document.getElementById
(
"passwordMessage")
.innerText = 
"";   
09  
10     if (!document.getElementById
(
"username")
.value) {
11         document.getElementById
(
"usernameMessage")
.innerText =
12         "This
field is required"
;
13         error
false;
14     }
15      
16     if (!document.getElementById
(
"password")
.value) {
17         document.getElementById
(
"passwordMessage")
.innerText =
18         "This
field is required"
;
19         error
false;
20     }       
21  
22     return error;       
23 };

        在清单 2 中,ApplicationUtil 对象提供一个简单验证:用户名和密码字段都已填充。如果某个字段为空,就会显示一条错误消息:This
field is required

        上面的代码能够在 Internet Explorer 8 和 Safari 5.1 上工作,但无法在 Firefox 3.6 上工作,原因是 Firefox 不支持innerText 属性。通常,(上述代码和其他类似
JavaScript 代码中的)主要问题是不容易发现编写的 JavaScript 代码是不是跨浏览器兼容的。

        这个问题的一个解决方案是进行自动化单元测试,检查代码是不是跨浏览器兼容。

        JsTestDriver

        JsTestDriver library 是最好的 JavaScript 单元测试框架之一,它为 JavaScript 代码提供了跨浏览器测试。图 1 展示了 JsTestDriver 的架构。

        图 1. JsTestDriver 架构

高效 JavaScript 单元测试

        捕获不同的浏览器之后,服务器会负责将 JavaScript 测试用例运行程序代码加载到浏览器中。可以通过命令行捕获浏览器,也可以通过将浏览器指向服务器 URL 来捕获浏览器。一旦捕获到浏览器,该浏览器就被称为从属浏览器。服务器可以加载 JavaScript 代码,在每个浏览器上执行测试用例,然后将结果返回给客户端。

        客户端(命令行)需要以下两个主要项目:

  1. JavaScript 文件,即源文件和测试文件
  2. 配置文件,用于组织源文件和测试文件的加载

        这个架构比较灵活,允许单个服务器从网络中的其他机器捕获任意数量的浏览器。例如,如果您的代码在 Linux 上运行但您想针对另一个 Windows 机器上的 Microsoft Internet Explorer 运行您的测试用例,那么这个架构很有用。

        要使用 JsTestDriver 库,请先下载最新版的 JsTestDriver
1.3.2

jsTestDriver 是开源项目

jsTestDriver 是 Apache
2.0 许可
 下的一个开源项目,托管在 Google Code 上,后者是一个类似于 SourceForge 的项目存储库。只要使用 Open Source Initiative 批准的 许可,开发人员就能在这个存储库中创建和管理公共项目。

还有许多其他 JavaScript 单元测试工具,请参见下面的 参考资料 部分中的其他工具,比如
Dojo Objective Harness (DOH)。

        编写单元测试代码

        现在开始编写 JavaScript 测试用例。为简单起见,我将测试以下用例:

  • 用户名和密码字段均为空。
  • 用户名为空,密码不为空。
  • 用户名不为空,密码为空。

        清单 3 显示了表示 TestCase 对象的 ApplicationUtilTest 对象的部分代码。

        清单 3. ApplicationUtilTest 对象代码的一部分

01 <strong>ApplicationUtilTest
= TestCase (
"ApplicationUtilTest");
02  
03 ApplicationUtilTest.prototype.setUp
function ()
{
04 /*:DOC
+= <FORM action=""><table><tr><td>Username</td><td>
05 <input
type="text" id="username"/></td><td><span id="usernameMessage">
06 </span></td></tr><tr><td>Password</td><td>
07 <input
type="password" id="password"/></td><td><span id="passwordMessage"
08 ></span></td></tr></table></FORM>*/
09 };
10  
11 ApplicationUtilTest.prototype.testValidateLoginFormBothEmpty
function ()
{
12     var applicationUtil
new appnamespace.ApplicationUtil
();
13      
14     /*
Simulate empty user name and password */
15     document.getElementById
(
"username")
.value = 
"";
16     document.getElementById
(
"password")
.value = 
"";   
17     applicationUtil.validateLoginForm
();
18     assertEquals
(
"Username
is not validated correctly!"
"This
field is required"
,
19     document.getElementById
(
"usernameMessage")
.innerHTML);
20     assertEquals
(
"Password
is not validated correctly!"
"This
field is required"
,
21     document.getElementById
(
"passwordMessage")
.innerHTML);   
22 };</strong>

        ApplicationUtilTest 对象通过 JsTestDriver TestCase 对象创建。如果您熟悉
JUnit 框架,那么您肯定熟悉setUp 和 testXXX 方法。setUp 方法用于初始化测试用例。对于本例,我使用该方法来声明一个
HTML 片段,该片段将用于其他测试用例方法。

        DOC 注释是一个 JsTestDriver 惯用语,可以用于轻松声明一个 HTML 片段。

        在 testValidateLoginFormBothEmpty 方法中,创建了一个 ApplicationUtil 对象,并在测试用例方法中使用该对象。然后,代码通过检索用户名和密码的
DOM 元素并将它们的值设置为空值来模拟输入空用户名和密码。可以调用validateLoginForm 方法来执行实际表单验证。最后,将调用 assertEquals 来确保 usernameMessage 和 passwordMessagespan
元素中的消息是正确的,即:This field is required

        在 JsTestDriver 中,可以使用以下构件:

  • fail ("msg"):表明测试一定会失败,消息参数将显示为一条错误消息。
  • assertTrue ("msg", actual):断定实际参数正确。否则,消息参数将显示为一条错误消息。
  • assertFalse ("msg", actual):断定实际参数错误。否则,消息参数将显示为一条错误消息。
  • assertSame ("msg", expected, actual):断定实际参数与预期参数相同。否则,消息参数将显示为一条错误消息。
  • assertNotSame ("msg", expected, actual):断定实际参数与预期参数不相同。否则,消息参数将显示为一条错误消息。
  • assertNull ("msg", actual):断定参数为空。否则,消息参数将显示为一条错误消息。
  • assertNotNull ("msg", actual):断定实际参数不为空。否则,消息参数将显示为一条错误消息。

        其他方法的代码包含其他测试用例。清单 4 显示了测试用例对象的完整代码。

        清单 4. ApplicationUtil 对象完整代码

01 ApplicationUtilTest
= TestCase (
"ApplicationUtilTest");
02  
03 ApplicationUtilTest.prototype.setUp
function ()
{
04 /*:DOC
+= <FORM action=""><table><tr><td>Username</td><td>
05 <input
type="text" id="username"/></td><td><span id="usernameMessage">
06 </span></td></tr><tr><td>Password</td><td>
07 <input
type="password" id="password"/></td><td><span id="passwordMessage"
08 ></span></td></tr></table></FORM>*/
09 };
10  
11 ApplicationUtilTest.prototype.testValidateLoginFormBothEmpty
function ()
{
12     var applicationUtil
new appnamespace.ApplicationUtil
();
13      
14     /*
Simulate empty user name and password */
15     document.getElementById
(
"username")
.value = 
"";
16     document.getElementById
(
"password")
.value = 
"";   
17      
18     applicationUtil.validateLoginForm
();
19      
20     assertEquals
(
"Username
is not validated correctly!"
"This
field is required"
,
21     document.getElementById
(
"usernameMessage")
.innerHTML);
22     assertEquals
(
"Password
is not validated correctly!"
"This
field is required"
,
23     document.getElementById
(
"passwordMessage")
.innerHTML);   
24 };
25  
26 ApplicationUtilTest.prototype.testValidateLoginFormWithEmptyUserName
function ()
{
27     var applicationUtil
new appnamespace.ApplicationUtil
();
28      
29     /*
Simulate empty user name and password */
30     document.getElementById
(
"username")
.value = 
"";
31     document.getElementById
(
"password")
.value = 
"anyPassword";   
32      
33     applicationUtil.validateLoginForm
();
34      
35     assertEquals
(
"Username
is not validated correctly!"
,
36     "This
field is required"
,
document.getElementById (
"usernameMessage")
.innerHTML);
37