如何进行高效JavaScript单元测试

简介:

如何进行高效JavaScript单元测试

 导读:能在一个浏览器上运行的JavaScript并不一定能在其他浏览器上运行。如果没有对代码进行单元测试,那么在决定升级或支持新浏览器的时候,组织就需要花钱测试或重新测试Web应用程序。在本文中,了解JavaScript单元测试如何帮助您降低测试成本,轻松支持更多浏览器。

  一个损坏的JavaScript代码示例

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

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

  清单 1.登录表单

  • <FORM> 
  •     <table> 
  •         <tr> 
  •             <td>Username</td> 
  •             <td><input type="text" id="username"/></td> 
  •             <td><span id="usernameMessage"></span></td> 
  •         </tr> 
  •         <tr> 
  •             <td>Password</td> 
  •             <td><input type="password" id="password"/></td> 
  •             <td><span id="passwordMessage"></span></td> 
  •         </tr>     
  •         <tr> 
  •             <td><input type="button" onclick="new appnamespace.  
  •             ApplicationUtil().validateLoginForm()" value="Submit"/></td> 
  •         </tr> 
  •     </table> 
  • </FORM>
  •   这个表单很简单,仅包含用户名和密码字段。单击提交按钮时,将通过ApplicationUtil执行一个特定的表单验证。以下是负责验证HTML表单的JavaScript对象。清单2显示了ApplicationUtil对象的代码。

      清单 2.损坏的ApplicationUtil对象代

  • appnamespace = {};  
  • appnamespace.ApplicationUtil = function() {};  
  • appnamespace.ApplicationUtil.prototype.validateLoginForm =  function(){  
  •     var error = true;  
  •     document.getElementById("usernameMessage").innerText = "";  
  •     document.getElementById("passwordMessage").innerText = "";    
  •     if (!document.getElementById("username").value) {  
  •         document.getElementById("usernameMessage").innerText =   
  •         "This field is required";  
  •         error = false;  
  •     }  
  •       
  •     if (!document.getElementById("password").value) {  
  •         document.getElementById("passwordMessage").innerText =   
  •         "This field is required";  
  •         error = false;  
  •     }         
  •     return error;         
  • };
  •   在清单 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测试用例运行程序代码加载到浏览器中。可以通过命令行捕获浏览器,也可以通过将浏览器指向服务器URL来捕获浏览器。一旦捕获到浏览器,该浏览器就被称为从属浏览器。服务器可以加载JavaScript代码,在每个浏览器上执行测试用例,然后将结果返回给客户端。

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

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

      这个架构比较灵活,允许单个服务器从网络中的其他机器捕获任意数量的浏览器。例如,如果您的代码在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 对象代码的一部分

  • ApplicationUtilTest = TestCase("ApplicationUtilTest");  
  •  
  • ApplicationUtilTest.prototype.setUp = function () {  
  • /*:DOC += <FORM action=""><table><tr><td>Username</td><td> 
  • <input type="text" id="username"/></td><td><span id="usernameMessage"> 
  • </span></td></tr><tr><td>Password</td><td> 
  • <input type="password" id="password"/></td><td><span id="passwordMessage" 
  • ></span></td></tr></table></FORM>*/  
  • };  
  •  
  • ApplicationUtilTest.prototype.testValidateLoginFormBothEmpty = function () {  
  •     var applicationUtil = new appnamespace.ApplicationUtil();  
  •       
  •     /* Simulate empty user name and password */  
  •     document.getElementById("username").value = "";  
  •     document.getElementById("password").value = "";   
  •       
  •     applicationUtil.validateLoginForm();  
  •       
  •     assertEquals("Username is not validated correctly!", "This field is required",   
  •     document.getElementById("usernameMessage").innerHTML);  
  •     assertEquals("Password is not validated correctly!", "This field is required",   
  •     document.getElementById("passwordMessage").innerHTML);    
  • };


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

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

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

  • ApplicationUtilTest = TestCase("ApplicationUtilTest");  
  •  
  • ApplicationUtilTest.prototype.setUp = function () {  
  • /*:DOC += <FORM action=""><table><tr><td>Username</td><td> 
  • <input type="text" id="username"/></td><td><span id="usernameMessage"> 
  • </span></td></tr><tr><td>Password</td><td> 
  • <input type="password" id="password"/></td><td><span id="passwordMessage" 
  • ></span></td></tr></table></FORM>*/  
  • };  
  •  
  • ApplicationUtilTest.prototype.testValidateLoginFormBothEmpty = function () {  
  •     var applicationUtil = new appnamespace.ApplicationUtil();  
  •       
  •     /* Simulate empty user name and password */  
  •     document.getElementById("username").value = "";  
  •     document.getElementById("password").value = "";   
  •       
  •     applicationUtil.validateLoginForm();  
  •       
  •     assertEquals("Username is not validated correctly!", "This field is required",   
  •     document.getElementById("usernameMessage").innerHTML);  
  •     assertEquals("Password is not validated correctly!", "This field is required",   
  •     document.getElementById("passwordMessage").innerHTML);    
  • };  
  •  
  • ApplicationUtilTest.prototype.testValidateLoginFormWithEmptyUserName = function () {  
  •     var applicationUtil = new appnamespace.ApplicationUtil();  
  •       
  •     /* Simulate empty user name and password */  
  •     document.getElementById("username").value = "";  
  •     document.getElementById("password").value = "anyPassword";    
  •       
  •     applicationUtil.validateLoginForm();  
  •       
  •     assertEquals("Username is not validated correctly!",   
  •     "This field is required", document.getElementById("usernameMessage").innerHTML);  
  •     assertEquals("Password is not validated correctly!",   
  •     "", document.getElementById("passwordMessage").innerHTML);    
  • };  
  •  
  • ApplicationUtilTest.prototype.testValidateLoginFormWithEmptyPassword = function () {  
  •     var applicationUtil = new appnamespace.ApplicationUtil();  
  •       
  •     document.getElementById("username").value = "anyUserName";  
  •     document.getElementById("password").value = "";   
  •       
  •     applicationUtil.validateLoginForm();  
  •       
  •     assertEquals("Username is not validated correctly!",   
  •     "", document.getElementById("usernameMessage").innerHTML);  
  •     assertEquals("Password is not validated correctly!",   
  •     "This field is required", document.getElementById("passwordMessage").  
  •     innerHTML);   
  • };

  •  配置用于测试的不同浏览器

      测试JavaScript代码的一个推荐实践是将JavaScript源代码和测试代码放置在不同的文件夹中。对于图 2中的示例,我将JavaScript源文件夹命名为“js-src”,将JavaScript测试文件夹命名为“js-test”,它们都位于“js”父文件夹下。

      图 2. JavaScript测试文件夹结构

      组织好源和测试文件夹后,必须提供配置文件。默认情况下,JsTestDriver运行程序会寻找名为jsTestDriver.conf的配置文件。您可以从命令行更改配置文件名称。清单 5显示了JsTestDriver配置文件的内容。

      清单 5.JsTestDriver配置文件内容

  • server: http://localhost:9876  
  •  
  • load:  
  •   - js-src/*.js  
  •   - js-test/*.js
  •   配置文件采用YAML格式。server指令指定测试服务器的地址,load指令指出了将哪些JavaScript文件加载到浏览器中以及加载它们的顺序。

      现在,我们将在IE、Firefox和Safari浏览器上运行测试用例类。

      要运行测试用例类,需要启动服务器。您可以使用以下命令行启动JsTestDriver服务器:

  • java -jar JsTestDriver-1.3.2.jar --port 9876 --browser "[Firefox Path]",  
  •           "[IE Path]","[Safari Path]"
  •   使用这个命令行,服务器将以Port 9876启动,捕获您的机器上的Firefox、IE和Safari浏览器。

      启动并捕获浏览器后,可以通过以下命令行运行测试用例类:

    java -jar JsTestDriver-1.3.2.jar --tests all

     运行命令后,您将看到第一轮结果,如清单 6 所示。

      清单 6.第一轮结果

  • Total 9 tests (Passed: 6; Fails: 3; Errors: 0) (16.00 ms)  
  •   Firefox 3.6.18 Windows: Run 3 tests (Passed: 0; Fails: 3; Errors 0) (8.00 ms)  
  •     ApplicationUtilTest.testValidateLoginFormBothEmpty failed (3.00 ms):   
  •     AssertError: Username is not validated correctly! expected "This field   
  •     is required" but was "" Error("Username is not validated correctly!   
  •     expected \"This field is required\" but was \"\"")@:0()@http://localhost  
  •     :9876/test/js-test/TestApplicationUtil.js:16  
  •  
  •     ApplicationUtilTest.testValidateLoginFormWithEmptyUserName failed (3.00 ms):   
  •     AssertError: Username is not validated correctly! expected "This field is   
  •     required" but was "" Error("Username is not validated correctly! expected   
  •     \"This field is required\" but was \"\"")@:0()@http://localhost:9876/test  
  •     /js-test/TestApplicationUtil.js:29  
  •  
  •     ApplicationUtilTest.testValidateLoginFormWithEmptyPassword failed (2.00 ms):   
  •     AssertError: Password is not validated correctly! expected "This field is   
  •     required" but was "" Error("Password is not validated correctly! expected   
  •     \"This field is required\" but was \"\"")@:0()@http://localhost:9876/test/  
  •     js-test/TestApplicationUtil.js:42  
  •       
  •   Safari 534.50 Windows: Run 3 tests (Passed: 3; Fails: 0; Errors 0) (2.00 ms)  
  •   Microsoft Internet Explorer 8.0 Windows: Run 3 tests (Passed: 3; Fails: 0;   
  •   Errors 0) (16.00 ms)  
  • Tests failed: Tests failed. See log for details.
  •   注意,在清单 6 中,主要问题出在 Firefox 上。测试在 Internet Explorer 和 Safari 上均可顺利运行。

      修复JavaScript代码并重新运行测试用例

      我们来修复损坏的JavaScript代码。我们将使用innerHTML替代innerText。清单 7显示了修复后的ApplicationUtil对象代码。

      清单 7.修复后的ApplicationUtil对象代码

  • appnamespace = {};  
  •  
  • appnamespace.ApplicationUtil = function() {};  
  •  
  • appnamespace.ApplicationUtil.prototype.validateLoginForm =  function(){  
  •     var error = true;  
  •     document.getElementById("usernameMessage").innerHTML = "";  
  •     document.getElementById("passwordMessage").innerHTML = "";    
  •  
  •     if (!document.getElementById("username").value) {  
  •         document.getElementById("usernameMessage").innerHTML =   
  •         "This field is required";  
  •         error = false;  
  •     }  
  •       
  •     if (!document.getElementById("password").value) {  
  •         document.getElementById("passwordMessage").innerHTML =   
  •         "This field is required";  
  •         error = false;  
  •     }         
  •  
  •     return error;         
  • };
  •   使用 --test all 命令行参数重新运行测试用例对象。清单 8 显示了第二轮运行结果。

      清单 8.第二轮运行结果

  • Total 9 tests (Passed: 9; Fails: 0; Errors: 0) (9.00 ms)  
  •   Firefox 3.6.18 Windows: Run 3 tests (Passed: 3; Fails: 0; Errors 0) (9.00 ms)  
  •   Safari 534.50 Windows: Run 3 tests (Passed: 3; Fails: 0; Errors 0) (5.00 ms)  
  •   Microsoft Internet Explorer 8.0 Windows: Run 3 tests (Passed: 3; Fails: 0; Errors 0)   
  •   (0.00 ms)
  •   如清单 8所示,JavaScript代码现在在IE、Firefox和Safari上都能正常运行。

      结束语

      在本文中,您了解了如何使用一个最强大的JavaScript单元测试工具(JsTestDriver)在不同的浏览器上测试JavaScript应用程序代码。还了解了什么是JsTestDriver,如何配置它,以及如何在Web应用程序中使用它来确保应用程序的JavaScript代码的质量和可靠性。


    本文出自seven的测试人生公众号最新内容请见作者的GitHub页:http://qaseven.github.io/

    目录
    相关文章
    |
    2月前
    |
    数据采集 人工智能 自然语言处理
    Midscene.js:AI 驱动的 UI 自动化测试框架,支持自然语言交互,生成可视化报告
    Midscene.js 是一款基于 AI 技术的 UI 自动化测试框架,通过自然语言交互简化测试流程,支持动作执行、数据查询和页面断言,提供可视化报告,适用于多种应用场景。
    546 1
    Midscene.js:AI 驱动的 UI 自动化测试框架,支持自然语言交互,生成可视化报告
    |
    3月前
    |
    机器学习/深度学习 自然语言处理 前端开发
    前端神经网络入门:Brain.js - 详细介绍和对比不同的实现 - CNN、RNN、DNN、FFNN -无需准备环境打开浏览器即可测试运行-支持WebGPU加速
    本文介绍了如何使用 JavaScript 神经网络库 **Brain.js** 实现不同类型的神经网络,包括前馈神经网络(FFNN)、深度神经网络(DNN)和循环神经网络(RNN)。通过简单的示例和代码,帮助前端开发者快速入门并理解神经网络的基本概念。文章还对比了各类神经网络的特点和适用场景,并简要介绍了卷积神经网络(CNN)的替代方案。
    606 1
    |
    4月前
    |
    人工智能 监控 JavaScript
    模拟依赖关系和 AI 是Vue.js测试的下一个前沿领域
    模拟依赖关系和 AI 是Vue.js测试的下一个前沿领域
    52 1
    |
    4月前
    |
    JavaScript 前端开发
    JavaScript - 测试 Prototype
    JavaScript - 测试 Prototype
    24 0
    |
    4月前
    |
    JavaScript 前端开发
    JavaScript - 测试 jQuery
    JavaScript - 测试 jQuery
    30 0
    |
    5月前
    |
    Web App开发 JavaScript 前端开发
    添加浮动按钮点击滚动到网页底部的纯JavaScript演示代码 IE9、11,Maxthon 1.6.7,Firefox30、31,360极速浏览器7.5.3.308下测试正常
    添加浮动按钮点击滚动到网页底部的纯JavaScript演示代码 IE9、11,Maxthon 1.6.7,Firefox30、31,360极速浏览器7.5.3.308下测试正常
    |
    6月前
    |
    JavaScript 前端开发 测试技术
    Vue.js开发者必看!Vue Test Utils携手端到端测试,打造无懈可击的应用体验,引领前端测试新风尚!
    【8月更文挑战第30天】随着Vue.js的普及,构建可靠的Vue应用至关重要。测试不仅能确保应用质量,还能提升开发效率。Vue Test Utils作为官方测试库,方便进行单元测试,而结合端到端(E2E)测试,则能构建全面的测试体系,保障应用稳定性。本文将带你深入了解如何使用Vue Test Utils进行单元测试,通过具体示例展示如何测试组件行为;并通过Cypress进行E2E测试,确保整个应用流程的正确性。无论是单元测试还是E2E测试,都能显著提高Vue应用的质量,让你更加自信地交付高质量的应用。
    103 0
    |
    6月前
    |
    JavaScript 前端开发 应用服务中间件
    【qkl】JavaScript连接web3钱包,实现测试网络中的 Sepolia ETH余额查询、转账功能
    【区块链】JavaScript连接web3钱包,实现测试网络中的 Sepolia ETH余额查询、转账功能
    189 0
    |
    6月前
    |
    Web App开发 应用服务中间件 定位技术
    three.js:三维模型加载量测试
    three.js:三维模型加载量测试
    252 4
    |
    8月前
    |
    前端开发 JavaScript 算法
    JavaScript事件监听测试代码
    JavaScript事件监听测试代码
    44 0

    热门文章

    最新文章

  • 1
    当面试官再问我JS闭包时,我能答出来的都在这里了。
    45
  • 2
    【02】仿站技术之python技术,看完学会再也不用去购买收费工具了-本次找了小影-感觉页面很好看-本次是爬取vue需要用到Puppeteer库用node.js扒一个app下载落地页-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-优雅草卓伊凡
    28
  • 3
    Node.js 中实现多任务下载的并发控制策略
    34
  • 4
    【2025优雅草开源计划进行中01】-针对web前端开发初学者使用-优雅草科技官网-纯静态页面html+css+JavaScript可直接下载使用-开源-首页为优雅草吴银满工程师原创-优雅草卓伊凡发布
    26
  • 5
    【JavaScript】深入理解 let、var 和 const
    49
  • 6
    【04】Java+若依+vue.js技术栈实现钱包积分管理系统项目-若依框架二次开发准备工作-以及建立初步后端目录菜单列-优雅草卓伊凡商业项目实战
    47
  • 7
    【03】Java+若依+vue.js技术栈实现钱包积分管理系统项目-若依框架搭建-服务端-后台管理-整体搭建-优雅草卓伊凡商业项目实战
    57
  • 8
    【02】Java+若依+vue.js技术栈实现钱包积分管理系统项目-商业级电玩城积分系统商业项目实战-ui设计图figmaUI设计准备-figma汉化插件-mysql数据库设计-优雅草卓伊凡商业项目实战
    57
  • 9
    如何通过pm2以cluster模式多进程部署next.js(包括docker下的部署)
    72
  • 10
    【01】Java+若依+vue.js技术栈实现钱包积分管理系统项目-商业级电玩城积分系统商业项目实战-需求改为思维导图-设计数据库-确定基础架构和设计-优雅草卓伊凡商业项目实战
    55