使用JsUnit和JSMock的JavaScript测试驱动开发(上)

发表于:2009-6-01 14:29

字体: | 上一篇 | 下一篇 | 我要投稿

 作者:Dennis Byrne    来源:InfoQ

  本文是一个速成班,介绍了如何编写可维护的JavaScript。我们向一个贯穿全文的例子中逐渐添加新功能,并遵循如下简单的规则:编写一个单元测试,然后让它通过。每个测试都起到质量反馈回路的作用,给那些想修改产品代码的人创建了一个安全保护网,以及一份可以执行的文档。通过简单、失败的测试开始每个功能,我们可以保证所有的功能都被测试覆盖到了。我们也避免了重写代码后再进行测试的高昂代价。考虑到JavaScript开发者很容易深陷泥沼、难以自拔的事实,这显得尤其难能可贵──只需要考虑一下DOM API和JavaScript语言本身之间有多少全局可变状态就够了。

  这个贯穿全文的例子是赌场的3轴老虎机。每轴有5种可能的状态,用图片来表示。当老虎机的play按钮被按下时,每个轴会随机给出一种状态。老虎机的余额根据三个轴的状态是否相等而增加或者减少。

  

  我们的工具有stubs、mock对象和一丁点的依赖注入。我们使用JsUnit运行单元测试,以及一个叫做JsMock的JavaScript mock对象库。集成测试──单元测试的补充,则超出了本文的范围。这并不意味着集成测试不重要──仅仅是因为我们希望得到更快的反馈,而不是从类似 Selenium和Watir这样的工具那里得到更慢、更全面的反馈。

  JsUnit,一个JavaScript单元测试框架

  JsUnit是JavaScript的开源单元测试框架。它受到JUnit的启发,并完全用JavaScript编写。作为最流行的 JavaScript单元测试框架,它还提供了一些ant任务,使开发人员在持续集成服务器上构建时很容易运行测试套件。持续集成是另外一个重要的实践,其与TDD结合使用时是对质量的一个“强有力的保证”,不过这也超出了本文的范围。

  让我们从JsUnit的test runner开始吧。Test runner是一个普通的HTML和JavaScript web页面,意味着你的单元测试可以直接在浏览器或者你想支持的浏览器中运行。解压缩JsUnit下载文件,你就会在根目录下发现testRunner.html。你不需要通过web服务器访问它──只需要通过文件系统加载它进行浏览就可以了。

  

  Test runner最重要的控件是位于页面顶部的文件输入栏。这个控件意在获取一个指向测试页面或者测试页面套件的路径。现在我们看一个JsUnit测试页面的简单例子。

<html>
 <title>A unit test for drw.SystemUnderTest class</title>
 <head>
  <script type='text/javascript' src='../jsunit/app/jsUnitCore.js'></script>
  <script type='text/javascript' src='../app/system_under_test.js'></script>
  <script type='text/javascript'>


    function setUp(){
      // perform fixture set up
    }
    function tearDown() {
      // clean up
    }
    function testOneThing(){
      // instantiating a SystemUnderTest, a class in the drw namespace
      var sut = new drw.SystemUnderTest();
      var thing = sut.oneThing();
      assertEquals(1, thing);
    }


    function testAnotherThing(){
      var sut = new drw.SystemUnderTest();
      var thing = sut.anotherThing();
      assertNotEquals(1, thing);
    }
  </script>
 </head>
 <body/>
</html>

  JsUnit与其它xUnit框架有很多相似之处。正如你期望的那样,test runner加载测试页面,调用每个测试函数。每个测试函数的调用被夹在setUp和tearDown调用之间。setUp函数给测试者提供了一个机会,可以选择在此构造测试夹具(test fixture)。测试夹具用以给页面中所有的测试准备状态。tearDown函数则给测试者提供了另外一个机会,可以去清除或者重置测试夹具。

  然而,与其他的xUnit框架相比,JsUnit在测试生命周期方面稍有不同。每个测试页面被加载到独立的窗口中,以防止应用程序代码通过开放类覆盖测试框架代码。在每个被加载的窗口中,所有的单元测试函数都会被调用到。页面不会为每个测试函数重新加载。从另一方面来说,在JUnit中,测试页面等同于一个测试用例,test runner会给每个测试方法生成一个单独的测试用例的实例。换言之:

JsUnit加载有N个测试函数的测试页面,只需要1次
JUnit创建有N个测试方法的测试用例,需要N次

  JavaScript开发者因此更容易陷入“一招不慎,满盘皆输”的境地,因为对测试页面状态的改变会影响后续测试的结果。而Java开发者在改变测试用例对象的状态时则不会遇到这种危险。JsUnit为什么这样做呢,而不是对每个测试,简单地重新加载一次测试页面?这是因为在测试套件中给每个测试函数重新创建DOM会有性能消耗。值得庆幸的是,JavaScript开发者不必过多关心全局状态变化带来的负面影响。在诸如JVM和CLR的程序平台上,修改静态变量会影响整个测试套件中所有后续的测试,而不仅仅是同一个测试用例的测试。

  jsUnitCore.js脚本必须嵌入到所有的测试页面中。这个重要的文件位于JsUnit下载文件解压之后的app目录中。它包含一组断言函数,与其他xUnit框架的行为多少有些相同。一个细微的区别源于JavaScript有两个等于符号。一个是相等(==)操作符,还有一个三等(===)操作符。比如,下面的第一个表达式是true,第二个是 false:

0 == false
0 === false

  为什么会这样呢?相等操作符不像三等操作符那样严格,允许运行时对第一个布尔表达式执行类型转换。所以不难理解新手会认为下面的断言会通过:

assertEquals(false, 0);

  实际上这个断言会失败,因为JsUnit框架提供的断言函数对所有的比较采用更严格的三等操作符,而不是相等操作符。通过避免相等操作符,JsUnit能够避免许多看似正确实则错误的测试。

21/212>
《2023软件测试行业现状调查报告》独家发布~

关注51Testing

联系我们

快捷面板 站点地图 联系我们 广告服务 关于我们 站长统计 发展历程

法律顾问:上海兰迪律师事务所 项棋律师
版权所有 上海博为峰软件技术股份有限公司 Copyright©51testing.com 2003-2024
投诉及意见反馈:webmaster@51testing.com; 业务联系:service@51testing.com 021-64471599-8017

沪ICP备05003035号

沪公网安备 31010102002173号