B.4 添加和运行测试

在测试驱动的开发中,需要在编写代码之前编写测试。这样一来,在看到测试失败之后,开始添加新的代码,然后再让测试通过,验证新代码实现了应有的功能。

首先,我们来测试第2章用到的子选择符,为<ul id="selected-plays">的所有子元素<li>添加horizontal类:

代码清单B-2

  1. test('Child Selector', function() {
  2. expect(1);
  3. var topLis = $('#selected-plays > li.horizontal');
  4. equal(topLis.length, 3, 'Top LIs have horizontal class');
  5. });

这里实际上加入了两个测试。第一个是expect()测试,它告诉QUnit我们想在这个测试集中运行多少个测试。然后,因为我们想要测试在页面中选择元素的能力,所以使用equal()测试来比较顶级<li>元素与数值3。如果这两个值相等,测试通过且通过的次数会加1,如图B-2所示。

异步测试 - 图1

图 B-2

之所以这个测试失败,是因为我们还没有编写代码去添加horizontal类。添加这个类的代码很简单,我们把它写在页面中包含的名为B.js的主脚本文件中:

代码清单B-3

  1. $(document).ready(function() {
  2. $('#selected-plays > li').addClass('horizontal');
  3. });

再次运行测试,就通过了,如图B-3所示。

异步测试 - 图2

图 B-3

现在,Selecting:Child Selector测试在括号里显示数字0,1,1,表示没有失败,总共1个测试,通过了1个测试。下面我们再添加两个对属性选择符的测试:

代码清单B-4

  1. module('Selecting', {
  2. setup: function() {
  3. this.topLis = $('#selected-plays > li.horizontal');
  4. }
  5. });
  6. test('Child Selector', function() {
  7. expect(1);
  8. equal(this.topLis.length, 3,
  9. 'Top LIs have horizontal class');
  10. });
  11. test('Attribute Selectors', function() {
  12. expect(2);
  13. ok(this.topLis.find('.mailto').length == 1, 'a.mailto');
  14. equal(this.topLis.find('.pdflink').length, 1, 'a.pdflink');
  15. });

这里又引入了另一种测试方式:ok()。这个测试接收两个参数:一个应该被求值为true的表达式和一个描述。同样,注意我们已经把topLis变量从代码清单B-2中的Child Selector测试转移到了模块的setup()回调函数中。module()接收可选的第二个参数,这个参数是一个对象,可以包含setup()teardown()函数。在这两个函数中,可以使用this关键字为模块中的所有测试一次性地指定变量。

同样,由于没有编写相应的代码,新测试也会失败:

异步测试 - 图3

图 B-4

在此,我们看到了ok()测试和equal()测试失败时不同的输出。前者只显示测试的标签(a.mailto)和来源,后者还会详细列出期待的结果。鉴于equal()ok()提供的测试失败的细节更多,因此应该优先使用它。

下面我们要在脚本中添加必要的代码:

代码清单B-5

  1. $(document).ready(function() {
  2. $('#selected-plays > li').addClass('horizontal');
  3. $('a[href^="mailto:"]').addClass('mailto');
  4. $('a[href$=".pdf"]').addClass('pdflink');
  5. });

如图B-5所示,这两个测试都通过了:

异步测试 - 图4

图 B-5

虽然失败的equal()测试比失败的ok()测试提供的信息更详尽,但测试通过后两者都只显示测试标签。

异步测试

测试异步JavaScript,比如Ajax请求,对我们来说又是一个挑战。挑战的核心在于当异步测试开始时,测试必须暂停;而当异步请求完成时,测试必须恢复。这种情况我们还是比较熟悉的,在效果队列、Ajax回调函数以及承诺对象中,都存在这种异步操作。在QUnit中,我们要使用一个特殊的测试集,它的名字叫asyncTest()。这个测试集与常规的test()测试集很相似;不同的是,在我们调用一个特殊的start()函数恢复它们之前,它们会暂停运行:

代码清单B-6

  1. asyncTest('JSON', function() {
  2. $.getJSON('b.json', function(json, textStatus) {
  3. //在这里添加测试
  4. }).always(function() {
  5. start();
  6. });
  7. });

这里只是简单地在B.js中请求了JSON数据,并在请求完成——无论成功还是失败时继续进行测试(通过在.always()回调函数中调用start())。在实际的测试中,需要检测textStatus以确定请求成功,然后像下面这样检查响应的JSON数组中的一个对象的值:

代码清单B-7

  1. asyncTest('JSON', function() {
  2. expect(2);
  3. var backbite = {
  4. "term": "BACKBITE",
  5. "part": "v.t.",
  6. "definition": "To speak of a man as you find him when he can't find you."
  7. };
  8. $.getJSON('b.json', function(json, textStatus) {
  9. equal(textStatus, 'success', 'Request successful');
  10. deepEqual(json[1], backbite,
  11. 'result array matches "backbite" map');
  12. }).always(function() {
  13. start();
  14. });
  15. });

为了测试响应的值,这里又使用了一个函数:deepEqual()。正常情况下,在比较两个对象时,除非它们引用的是相同的内存地址,否则不会判定它们相等。但如果我们想比较的是它们的内容,那么使用deepEqual()很合适。这个函数会遍历两个对象,确保它们拥有相同的属性,而且每个属性都有相同的值。