QUnit系列 -- 4.QUnit介绍(下)

  测试用户操作

  问题

  那些依赖于用户操作的代码,不能通过执行函数来测试。通常元素的事件使用异步函数,例如click,这些需要模拟。

  解决方案

   你可以使用jQuery的 trigger()方法来触发事件,然后测试预期的行为。如果你不想浏览器事件被触发,你可以使用triggerHandler()来执行事件相关方法。这对于测试链接的click事件是有帮助的,因为trigger()可能会使浏览器改变地址栏信息,这恐怕不是测试过程中想要发生的。假设我们有一个简单的KeyLogger需要测试:

function KeyLogger( target ) {
  if ( !(this instanceof KeyLogger) ) {
    return new KeyLogger( target );
  }
  this.target = target;
  this.log = [];
 
  var self = this;
 
  this.target.off( "keydown" ).on( "keydown", function( event ) {
    self.log.push( event.keyCode );
  });
}

  我们可以手动的触发keypress事件,然后观察logger是否工作:

test( "keylogger api behavior", function() {
 
  var event,
      $doc = $( document ),
      keys = KeyLogger( $doc );
 
  // trigger event
  event = $.Event( "keydown" );
  event.keyCode = 9;
  $doc.trigger( event );
 
  // verify expected behavior
  equal( keys.log.length, 1, "a key was logged" );
  equal( keys.log[ 0 ], 9, "correct key was logged" );
 
});

 

  讨论

  如果你的事件处理不依赖任何特定的事件属性,你可以调用trigger(eventType)。但如果你的事件处理依赖于特殊的事件属性,你就需要使用$.Event创建一个事件对象,并设置必要的属性。对于复杂的行为触发相关事件是非常重要的,例如dragging,他由mousedown、mousemove和mouseup组成。即使是看起来很简单的事件也有可能是有很多事件组成的,例如click是由mousedown、mouseup和click组成的。你是否需要触发所有三个事件,依赖于你的测试代码,只触发click大多数情况是满足要求的。

  如果那些仍然不够,你就需要一个框架来帮你模拟用户事件了:

  • syn "是一个合成的事件类库,用来处理大多数的 typing, clicking, moving 和 dragging,能准确的模拟用户的实际操作"。基于QUnit的FuncUnit使用了syn,用来对web站点做功能测试。
  • JSRobot - "一个web应用的测试工具,可以产生真正的敲击键盘,而不是简单的模拟JavaScript事件触发。允许通过敲击触发浏览器实际的事件,而这对于别的框架来说是办不到的事情"。
  • DOH Robot "提供一个 API,允许测试者使用真实的、跨平台的、系统级的输入事件自动运行 UI 测试 "。他为你提供了接近真实浏览器的事件,但是要使用到 Java applets来实现。

 

  保持测试原子性

  问题

  当测试集中在一起的时候,原本应该通过的测试会失败,本该失败的测试会通过,因为之前测试的副作用,使测试结果失效。

test( "2 asserts", function() {
  var $fixture = $( "#qunit-fixture" );
 
  $fixture.append( "<div>hello!</div>" );
  equal( $( "div", $fixture ).length, 1, "div added successfully!" );
 
  $fixture.append( "<span>hello!</span>" );
  equal( $( "span", $fixture ).length, 1, "span added successfully!" );
});

  第一个append()添加了一个div,第二个equal()并没有把它考虑进去。

  解决方案

  使用test()方法保持测试的原子性,使每一个断言清洁而不存在副作用。你应该只依赖 #qunit-fixture元素内部的 fixture标签,修改和依赖其他东西将会存在副作用。

test( "Appends a div", function() {
  var $fixture = $( "#qunit-fixture" );
 
  $fixture.append( "<div>hello!</div>" );
  equal( $( "div", $fixture ).length, 1, "div added successfully!" );
});
 
test( "Appends a span", function() {
  var $fixture = $( "#qunit-fixture" );
 
  $fixture.append("<span>hello!</span>" );
  equal( $( "span", $fixture ).length, 1, "span added successfully!" );
});

  QUnit会在每次测试之后重置 #qunit-fixture中的元素,移出已经存在的事件。如果你只是使用了fixture内部的元素,每次测试之后你不需要执行手工操作来保持它的原子性。

  讨论

  除了  #qunit-fixture和过滤("高效发展"会讲到)之外,QUnit还提供了noglobals标志,看看下面的测试:
test( "global pollution", function() {
  window.pollute = true;
  ok( pollute, "nasty pollution" );
});

  一般情况下,测试会得到成功的结果。但是选中noglobals后ok()得到失败的结果,这是因为QUnit认为他污染了window对象。在任何时候都没有必要使用那个标志,但是在整合第三方类库的时候,他对于判断是否引起全局命名污染是有帮助的。而且他也能帮助去发现由于副作用引起的bug。

 

  分组测试

  问题

  你已经分割了你所有的测试,来让他们保持原子性并不产生副作用,但是你想保证他们的逻辑可组织,它本身能以组的方式运行。

  解决方案

  你可以使用module()去把测试分组:

module( "group a" );
test( "a basic test example", function() {
  ok( true, "this test is fine" );
});
test( "a basic test example 2", function() {
  ok( true, "this test is fine" );
});
 
module( "group b" );
test( "a basic test example 3", function() {
  ok( true, "this test is fine" );
});
test( "a basic test example 4", function() {
  ok( true, "this test is fine" );
});

  module()后面的测试会被分在一个组里面,测试结果中每个测试的名称会被加上module名称。你还可以通过module名字选择特定测试来运行。

  讨论

  module()除了实现分组功能外,还可以用来提取通用代码,他接受一个可选的第二个参数,定义module每次运行测试开始和结束的行为。

module( "module", {
  setup: function() {
    ok( true, "one extra assert per test" );
  }, teardown: function() {
    ok( true, "and one extra assert after each test" );
  }
});
test( "test with setup and teardown", function() {
  expect( 2 );
});

  你可以一起定义setup和teardown属性,或者只定义其中一个。再次调用modile()方法的时候,会重置掉之前方法定义的setup/teardown方法。

 

  高效发展(Efficient Devlopment)

  问题

  当你的测试需要运行很长时间的时候(例如好几秒),你可以不想浪费时间等待结果。

  解决方案

  QUnit有一系列的方法可以解决这个问题。最有趣的一个是点击头部的 "Hide passed tests"选项,这样QUnit会只显示失败的测试,他不会对测试时间有影响,只是起到聚焦失败测试的作用。

  另外一个有趣的特性是,QUnit会把失败的测试的名字保存在sessionStorage中(你的浏览器必须要支持),默认情况下这个特性是开启的,只是我们没有注意到他的存在。下次你再运行测试的时候,之前失败的测试会被先执行,但是他对输出结果的顺序没有影响,影响的只是执行顺序。结合使用 "Hide passed tests" 你可以立即得到失败的测试。

  讨论

  自动排序会默认发生,你以为着你的测试需要保持原子性,如果不能保证这点将会产生随机的异常。修复问题是个正确的解决方案,或者存在困难,你可以设置QUnit.config.reorder = false。

  除了自动排序之外,还有一些手工可选项。可以点击测试后面的"Rerun"链接,运行单个测试。他会在url中添加"testNumber=N"字符串参数,N代表你点击的测试编号。你可以重刷页面,只运行那个测试,或者使用浏览器的返回按钮区运行所有测试。

  运行module中的所有测试,几乎以相同的方式工作。除非你选择页眉右上角的module,他会在url中添加"module=N"字符串,N代表module的编号,例如:"?module=testEnvironment%20with%20object"。

 

文章来源:http://qunitjs.com/cookbook/

你可能感兴趣的:(it)