FED实验室 - 专注WEB端开发和用户体验

Qunit:使用Qunit测试你的javascript代码

AUTOMATION 煦涵 5392℃ 0评论

一、What is QUnit?

Qunit是一款强大的可用来测试代码的javaScript单元测试框架。QUnit由jQuery团队成员编写,是jQuery的官方测试套件,不仅如此,QUnit还可以测试任何常规javaScript代码,甚至可以通过一些像Rhino或者V8这样的JavaScript引擎,测试服务端JavaScript代码。
如果你不熟悉“单元测试”的相关概念,Don't worry。其实并不难理解:

“在计算机编程中,单元测试(又称为模块测试)是针对程序模块(软件设计的最小单位)来进行正确性检验的测试工作。程序单元是应用的最小可测试部件。在过程化编程中,一个单元就是单个程序、函数、过程等;对于面向对象编程,最小单元就是方法,包括基类(超类)、抽象类、或者派生类(子类)中的方法。"

——引自维基百科。

简单来说,你为代码的每一个功能编写测试用例,如果所有的测试都通过了,就可以确保代码没有bug了(通常,还是由测试有多彻底而定)。

二、Why You Should Test Your Code?

如果你以前从未写过任何单元测试,你可能直接将你的代码应用到网站上,点击一会看看是否有什么问题出现,并且尝试去解决你所发现的问题,采用这种方法会有很多的问题。
1.点击测试的缺点:

1)这个过程是非常乏味的。

点击其实并不是一项简单的工作,因为需要保证每一个东西都被点击到而且极有可能漏掉一两个。

2)为测试做的每一件事情都是不可重用的,这就意味着它很难回归。

什么是回归?想像一下,你写了一些代码并测试他们,修复了所有你发现的缺陷,然后发布。这时,一个用户发过来一些关于新的bug的反馈,并且有一些新的需求。你又回到代码中,处理这些新的bug,并增加新的功能。接下来可能会发生的就是一些旧的缺陷又重现了,这就叫“回归”。这样,你就不得不重新点击一遍,而且有可能你还找不到这些旧的缺陷;即使你这么做,这还需要一段时间才能弄清楚你的问题是由回归引起的。
2.单元测试的优点:

1)使用单元测试,你写测试用例去发现缺陷,一旦代码被修改,您通过测试再筛选一次。一旦出现回归,一些测试用例一定会失败,你可以很容易地认出他们,知道哪部分代码包含了错误。既然你知道你刚才修改了什么,就可以很容易地解决问题。

2)单元测试让跨浏览器兼容性测试变得更容易。

仅仅在不同浏览器中运行你的测试用例,一旦某个浏览器出现问题,修复它并重新运行这些测试用例,确保不会在别的浏览器引起回归,一旦全部通过测试,就可以肯定的说,所有的目标浏览器都支持。
我喜欢提及一个John Resig的项目:TestSwarm。TestSwarm通过分发,将JavaScript单元测试带到了一个新的层次。这是一个包含很多测试用例的网站,任何人都可以去那运行一些测试用例,然后返回结果会返回到服务器。通过这种方式,代码会非常迅速的在不同的浏览器进行测试,甚至不同的平台运行。

三、How to Write Unit Tests with QUnit?

1.您需要搭建一个测试环境:

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>My Qunit Test</title>
<meta name="description" content="">
<meta name="keywords" content="">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<link rel="stylesheet" href="qunit-1.14.0.css" />
</head>
<body>
<div id="qunit"></div>
<div id="qunit-fixture"></div>   
<script src="qunit-1.14.0.js"></script>

<!-- My project js file -->
<script src="myproject.js"></script>

<!-- Test js file -->
<script src="mytest.js"></script>

</body>
</html>

我这里是把qunit-1.14.0.css,qunit-1.14.0.js下载到了本地,当然也可以直接使用CDN。

myproject.js文件中添加将要测试的代码,mytest.js文件中添加测试用例。要运行这些测试,只需在一个浏览器中打开这个html文件。现在需要写一些测试用例了。

2.断言是单元测试的基石。

何为断言?“断言是一个命题,预测你的代码的返回结果。如果预测是假的,断言失败,你就知道出了问题。”

运行断言,需要把它们放入测试用例中:

// Let's test this function
function isEven(val) {
  return val % 2 === 0;
}

test('isEven()', function() {
  ok(isEven(0), 'Zero is an even number');
  ok(isEven(2), 'So is two');
  ok(isEven(-4), 'So is negative four');
  ok(!isEven(1), 'One is not an even number');
  ok(!isEven(-7), 'Neither is negative seven');
})

这里,我们定义一个函数:isEven,用来检测一个数字是否为偶数,并且我们希望测试这个函数来确认它不会返回错误答案。

我们首先调用test(),它构建了一个测试用例;
第一个参数是一个将被显示在结果中的字符串;
第二个参数是包括我们断主的一个回调函数;

我们写了5个断言,所有的都是布尔型的。一个布尔型的断言,期望它的第一个参数为true。第二个参数依然是要显示在结果中的消息。
运行测试用例,得到如下结果:

Qunit_result01

 

由于所有的断言都已通过,我们可以高兴的认为isEven()如我们期望的一样工作正常。让我们看看如果一个断言失败了会发生什么。

// Let's test this function
function isEven(val) {
  return val % 2 === 0;
}

test('isEven()', function() {
  ok(isEven(0), 'Zero is an even number');
  ok(isEven(2), 'So is two');
  ok(isEven(-4), 'So is negative four');
  ok(!isEven(1), 'One is not an even number');
  ok(!isEven(-7), 'Neither does negative seven');

  // Fails
  ok(isEven(3), 'Three is an even number');
})

运行测试用例,得到如下结果:

Qunit_result02
该断言失败因为我们故意把它写错,但是在你的项目中,如果测试未通过,并且所有的断言都是正确的,你将发现一个bug。
3.ok()不仅是QUnit提供的唯一断言,当在测试你的项目时,还会有一些非常有用的其他类型的断言:

1)比较断言equals()期望它的第一个参数(是实际值)等于它的第二个参数(期望值)。它很类似于ok(),但均会输入实现和期望值,使得高度更加简单,像ok()一样,它可带一个可选的第三个参数作为显示的消息。

test('assertions', function() {
  ok( 1 == 1, 'one equals one');
})

替换为:

test('assertions', function() {
  equals( 1, 1, 'one equals one');
})

 

比较断言使用“==”来比较它的参数,所以它不能处理数组或对象的比较,为处理这种情况,QUnit提供了另外一种断言:恒等断言。

2)恒等断言same()
期望相同的参数相等,但是它较深的采用递归比较断言,不仅作用于原始类型,而且包括数组和对象。

same()使用”===”去比较,如有必要的话,所以它在比较特殊值的时候就派上用场了。
3)结构化断言
把所有的断言放在一个单独的测试案例中是相当不好的想法,因为这很难去维护,并且不能返回一个纯净的结果。你需要做的就是结构化他们,把他们放在不同的测试案例,每个目标为一个单独功能。
甚至可以通过调用模块函数来把测试案例组织到不同的模块:

QUnit.module( "Module a" );
QUnit.test( "a basic test example", function( assert ) {
  assert.ok( true, "this test is fine" );
});
QUnit.test( "a basic test example 2", function( assert ) {
  assert.ok( true, "this test is fine" );
});

QUnit.module( "Module b" );
QUnit.test( "a basic test example 3", function( assert ) {
  assert.ok( true, "this test is fine" );
});
QUnit.test( "a basic test example 4", function(assert) {
  assert.ok( true, "this test is fine" );
});

 

4)异步测试
在前面的示例中,所有的断言都是同步调用的,这意味着他们是一个接着一个运行的。在这个真实的世界,同样存在着很多异步的函数,例如Ajax请求或通过setTimeout()或sestInterval()调用的方法。我们如何去测试这些种类的方法呢?QUnit提供了一个特殊的叫做和“异步测试”的测试案例,提供给异步的测试:

test('asynchronous test', function() {
  // Pause the test first
  stop();

  setTimeout(function() {
    ok(true);

    // After the assertion has been called,
    // continue the test
    start();
  }, 100)
})

 

在这,我们使用了stop()去暂停此次测试案例,并且在断言被调用以后,我们使用start()继续。
在调用完test()后立即调用stop()是很平常的;所以QUnit提供了一个捷径:asyncTest()。你可以像这样重写之前的示例:

asyncTest('asynchronous test', function() {
  // The test is automatically paused

  setTimeout(function() {
    ok(true);

    // After the assertion has been called,
    // continue the test
    start();
  }, 100)
})

还有一点要注意:setTimeout()通常会调用它自己的回调函数,但如果它是一个自定义的函数(例如:一个Ajax调用)。你如何确认回调函数被调用了呢?并且如果回调函数没有被调用,start()将不会被执行,整个单元测试将被挂起:

// A custom function
function ajax(successCallback) {
  $.ajax({
    url: 'ajax.json',
    success: successCallback
  });
}

test('asynchronous test', function() {
  // Pause the test, and fail it if start() isn't called after one second
  stop(1000);

  ajax(function() {
      // ...asynchronous assertions

      start();
  })
})

 

你可以通过延时去stop(),它告知QUnit,“如果start()在延时后没有被调用,你应未通过测试”。你可以确认的是整个测试没有挂起而且如果哪里出了问题你可以注意到。
那么多个异步函数呢?你在哪里放置start()?可把它放在setTimeout()里:

// A custom function
function ajax(successCallback) {
  $.ajax({
    url: 'server.php',
    success: successCallback
  });
}

test('asynchronous test', function() {
  // Pause the test
  stop();

  ajax(function() {
    // ...asynchronous assertions
  })

  ajax(function() {
    // ...asynchronous assertions
  })

  setTimeout(function() {
    start();
  }, 2000);
})

延时应该适当的长,足够来允许二者的回调函数在测试继续执行前被调用。但是如果其中一个回调函数没有被调用怎么办?你怎样去知道?这就是expect()加入的原因:

// A custom function
function ajax(successCallback) {
  $.ajax({
    url: 'server.php',
    success: successCallback
  });
}

test('asynchronous test', function() {
  // Pause the test
  stop();

  // Tell QUnit that you expect three assertions to run
  expect(3);

  ajax(function() {
      ok(true);
  })

  ajax(function() {
      ok(true);
      ok(true);
  })

  setTimeout(function() {
      start();
  }, 2000);
})

 

你给expect()传一个数字告知QUnit你期望X个断言去执行,如果一个断言未被执行,这个数字将不会匹配,而且你会注意到有些东西出错了。
这仍有一个expect()的捷径:你只需给test()或asyncTest()的第二个参数传递一个数字:

// A custom function
function ajax(successCallback) {
  $.ajax({
      url: 'server.php',
      success: successCallback
  });
}

// Tell QUnit that you expect three assertion to run
test('asynchronous test', 3, function() {
  // Pause the test
  stop();

  ajax(function() {
      ok(true);
  })

  ajax(function() {
      ok(true);
      ok(true);
  })

  setTimeout(function() {
      start();
  }, 2000);
})

四、参考链接

http://api.qunitjs.com/category/async-control/

How to Test your JavaScript Code with QUnit
http://code.tutsplus.com/tutorials/how-to-test-your-javascript-code-with-qunit--net-9077

下面是「FED实验室」的微信公众号二维码,欢迎扫描关注:

FED实验室

行文不易,如有帮助,欢迎打赏!

赞赏支持or喜欢 (1)or分享 (0)
捐赠共勉
发表我的评论
取消评论

表情

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址