您的位置:首页 > Web前端 > JQuery

30分钟QUnit入门教程

2015-02-24 23:27 423 查看

30分钟Qunit入门教程

15分钟让你了解Javascript单元测试框架QUnit,并能在程序中使用。

QUnit是什么

QUnit是一个强大,易用的JavaScript单元测试框架,由jQuery团队的成员所开发,并且用在jQuery,jQuery UI,jQuery Mobile等项目。

Hello World

学习QUnit还是从例子开始最好,首先我们需要一个跑单元测试的页面,这里命名为index-test.html:

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>QUnit Example</title>
<link rel="stylesheet" href="http://code.jquery.com/qunit/qunit-1.17.1.css">
</head>
<body>
<div id="qunit"></div>
<div id="qunit-fixture"></div>
<script src="http://code.jquery.com/qunit/qunit-1.17.1.js"></script>
<script src="tests.js"></script>
</body>
</html>


这里主要引入了两个文件,一个是QUnit的CSS文件,一个是提供断言等功能的JS文件。

这里另外引入了一个tests.js文件,我们的测试用例就写在这个文件里面。

tests.js:

QUnit.test( "hello test", function( assert ) {
assert.ok( "hello world" == "hello world", "Test hello wordl" );
});


页面载入完毕,QUnit就会自动运行
test()
方法,第一个参数是被测试的单元的标题,第二个参数,就是实际的而是代码,这里的参数assert为QUnit的断言对象,其中提供了不少断言方法,这里使用了
ok()
方法,
ok()
方法接受两个参数,第一个是表明测试是否通过的bool值,第二个则是需要输出的信息。

我们在浏览器中运行index-test.html,就会看到测试结果:



从上到下,可以看到有三个checkbox,这几个的作用,我们后面再说。然后看到浏览器的User-Agent信息。之后是总的测试信息,跑了几个断言,通过了几个,失败了几个。最后是详细信息。

假如我们稍微修改一下刚才的断言条件,改成
!=


assert.ok( "hello world" != "hello world", "Test hello wordl" );


则会得到测试失败的信息:



详细信息中有错误的行号,以及diff信息等。

更多断言

上面介绍了
assert.ok()
方法,QUnit还提供了一些别的断言方法,这里再介绍几个常用的。

equal(actual, expected [,message])

equal()
断言用的是简单的
==
来比较实际值和期望值,相同则通过,否则失败。

修改一下tests.js:

QUnit.test( "hello test", function( assert ) {
//assert.ok( "hello world" == "hello world", "Test hello wordl" );
assert.equal( 0, 0, "Zero, Zero; equal succeeds" );
assert.equal( "", 0, "Empty, Zero; equal succeeds" );
assert.equal( "", "", "Empty, Empty; equal succeeds" );
assert.equal( 0, false, "Zero, false; equal succeeds" );

assert.equal( "three", 3, "Three, 3; equal fails" );
assert.equal( null, false, "null, false; equal fails" );
});


浏览器中运行:



如果你需要严格的比较,需要用
strictEqual()
方法。

deepEqual(actual, expected, [,message])

deepEqual()
断言的用法和
equal()
差不多,它除了使用
===
操作符进行比较之外,还可以通过比较
{key : value}
是否相等,来比较两个对象是否相等。

QUnit.test( "deepEqual test", function( assert ) {
var obj = { foo: "bar" };

assert.deepEqual( obj, { foo: "bar" }, "Two objects can be the same in value" );
});


如果要显式的比较两个值,
equal()
也可以适用。一般来说,
deepEqual()
是个更好的选择。

同步回调

有时候,我们的测试用例包含回调函数,要在回调函数中进行断言。这里可以用到
assert.expect()
函数,它接受一个表示断言数量的int值,表示这个test里面,预计要跑多少个断言。这里为了方便,引入了jQuery库,在index-test.html中加入
<script src="http://code.jquery.com/qunit/qunit-1.17.1.js"></script>


QUnit.test( "a test", function( assert ) {
assert.expect( 1 );

var $body = $( "body" );

$body.on( "click", function() {
assert.ok( true, "body was clicked!" );
});

$body.trigger( "click" );
});


异步回调

assert.expect()
对同步的回调非常有用,但是对异步回调却不是那么适用。

稍微修改一下上面的例子:

QUnit.test( "a test", function( assert ) {
var done = assert.async();
var $body = $( "body" );

$body.on( "click", function() {
assert.ok( true, "body was clicked!" );
done();
$body.unbind('click');
});

setTimeout(function(){
console.log("To click body")
$body.trigger( "click" );
}, 1000)

});


使用
assert.async()
返回一个”done”函数,当操作结束的时候,调用这个函数。另外我在”done”函数调用结束之后,把body的click事件给移除了,这个是为了方便我在点击结果网页的时候,不要出发多次done函数。

结果:



这里我们也可以使用
QUnit.start()与QUnit.stop()
来控制异步回调中断言的判断。

QUnit.test( "a test 1", function( assert ) {
QUnit.stop()
var $body = $( "body" );

$body.on( "click", function() {
assert.ok( true, "body was clicked!" );
QUnit.start();
$body.unbind('click');
});

setTimeout(function(){
console.log("To click body")
$body.trigger( "click" );
}, 1000)

});


QUnit还提供了
QUnit.asyncTest()
方法来简化异步调用的测试,不需要自己手动调用
QUnit.stop()
方法,并且从函数名也可以更容易的让人知道这是个异步调用的测试。

QUnit.asyncTest( "a test 2", function( assert ) {
var $body = $( "body" );

$body.on( "click", function() {
assert.ok( true, "body was clicked!" );
QUnit.start();
$body.unbind('click');
});

setTimeout(function(){
console.log("To click body")
$body.trigger( "click" );
}, 1000)

});


原子性

保持测试用例之间互不干扰很重要,如果要测试DOM修改,我们可以使用
#qunit-fixture
这个元素。
#qunit-fixture
就好比是拿来练级的小怪,每次打死,下次来又会满血复活。

这个元素中你可以写任何初始的HTML,也可以置空,每个
test()
结束,都会恢复初始值。

QUnit.test( "Appends a span", function( assert ) {
var fixture = $( "#qunit-fixture" );

fixture.append( "<span>hello!</span>" );
assert.equal( $( "span", fixture ).length, 1, "div added successfully!" );
});

QUnit.test( "Appends a span again", function( assert ) {
var fixture = $( "#qunit-fixture" );

fixture.append("<span>hello!</span>" );
assert.equal( $( "span", fixture ).length, 1, "span added successfully!" );
});


这里我们无论对
#qunit-fixture
里面的东西做什么,下次测试开始的时候都会“满血复活”。

分组

在QUnit中可以对测试进行分组,并且可以指定只跑哪组测试。

分组需要使用
QUnit.module()
方法。我们可以将刚才我们测试的代码进行一个简单的分组。

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

QUnit.module("Group Async Test")
QUnit.test( "a test", function( assert ) { var done = assert.async(); var $body = $( "body" ); $body.on( "click", function() { assert.ok( true, "body was clicked!" ); done(); $body.unbind('click'); }); setTimeout(function(){ console.log("To click body") $body.trigger( "click" ); }, 1000) });


结果网页中会多一个下拉框,可以选择分组。



并且module也支持在每个测试之前或之后做些准备工作。

QUnit.module("Group DOM Test", {
setup: function(){
console.log("Test setup");
},
teardown: function(){
console.log("Test teardown");
}
})


在执行这个分组的每个
test()
执行前后会分别运行
setup()
teardown()
函数。

AJAX测试

AJAX在前端中占据了非常大的比重,由于AJAX的异步回调的复杂性,要做到业务代码和测试代码分离,也不容易,如果像jasmine框架中,用
waitsFor
来不停检查,超时等,其实不是太优雅。

这里结合jQuery,来一个比较优雅的,如果是使用别的框架,还需要另外研究。

不多说,直接上代码:

我们有一个进行ajax调用的对象:

var X = function () {
this.fire = function () {
return $.ajax({ url: "someURL", ... });
};
};


然后是测试代码:

// create a function that counts down to `start()`
function createAsyncCounter(count) {
count = count || 1; // count defaults to 1
return function () { --count || QUnit.start(); };
}

// an async test that expects 2 assertions
QUnit.asyncTest("testing something asynchronous", 2, function(assert) {
var countDown = createAsyncCounter(1), // the number of async calls in this test
x = new X;

// A `done` callback is the same as adding a `success` handler
// in the ajax options. It's called after the "real" success handler.
// I'm assuming here, that `fire()` returns the xhr object
x.fire().done(function(data, status, jqXHR) {
assert.ok(data.ok);
assert.equal(data.value, "123");
}).always(countDown); // call `countDown` regardless of success/error
});


countDown
方法是用来记录有多少个AJAX调用,然后在最后一个完成之后,调用
QUnit.start()
方法。
QUnit.asyncTest
中第二个参数”2”类似
assert.expect( 2 )
中的“2”。这里
done()和always()
方法是jQuery的deferred对象提供的,而
$.ajax()
会返回jqXHR对象,这个对象具有deferred对象的所有只读方法。

如果你需要记录一些错误信息,可以添加
.fail()
方法。

自定义断言

自定义断言,就是直接使用
QUnit.push()
封装一些自定义的判断。
QUnit.push()
assert.equal
的关系就类似于
$.ajax
$.get
的关系。

QUnit.assert.mod2 = function( value, expected, message ) {
var actual = value % 2;
this.push( actual === expected, actual, expected, message );
};

QUnit.test( "mod2", function( assert ) {
assert.expect( 2 );

assert.mod2( 2, 0, "2 % 2 == 0" );
assert.mod2( 3, 1, "3 % 2 == 1" );
});


上面的代码自定义了一个叫mod2的断言。
QUnit.push()
有四个参数,一个Boolean型的result,一个实际值actual,一个期望值expected,以及一个说明message。

官网建议把自定义断言定义在全局的
QUnit.assert
对象上,方便重复利用。

调试工具与其他

最后我们来看看一开始说到的三个checkbox。



Hide passed tests

很好理解,就是隐藏通过的测试,勾选之后,通过的测试就不显示出来了,在测试用例多的时候非常有用。而且使用了HTML5的
sessionStorage
技术,会记住之前没通过的测试,然后页面重新载入的时候只测试之前那部分没有通过的case。

Check for Globals

“全局检查“,如果勾选了这项,在进行测试之前,QUnit会检查测试之前和测试之后
window
对象中的属性,如果前后不一样,就会显示不通过。

No try-catch

选中则意味着QUnit会在
try-catch
语句之外运行回调,此时,如果测试抛出异常,测试就会停止。主要是因为有些浏览器的调试工具是相当弱的,尤其IE6,一个未处理的异常要比捕获的异常可以提供更多的信息。即使再次抛出,由于JavaScript不擅长异常处理,原来的堆栈跟踪在大多数浏览器里都丢失了。如果遇到一个异常,无法追溯错误代码的时候,就可以使用这个选项了。

另外每个测试旁边都有个”Rerun”的按钮,可以单独运行某个测试。



题外话

有些童鞋可能会问,单元测试真的有必要吗?

实际上,相信我们写完代码至少都会进行一些简单的输入输出测试,检查代码是否会报错。但是这相对比较手工,测试完也许就把测试数据丢了,不便于下次重复利用。当我们代码的内部逻辑进行了一些改动,我们又需要进行之前的一些测试,而且很容易漏掉一些测试,造成回归错误(改这里,造成那里出错)。如果我们有保留完整的单元测试代码,就可以方便的进行测试了。这样在重构的时候,也可以做到有理有据,脚踏实地。

在维护的层面上看,有了单元测试代码,当别人看到你代码,不知道怎么用的时候,就可以去看看你的单元测试代码,自然知道你的类,你的方法的大致用法。

同时,在进行每日构建的时候,还可以自动运行单元测试代码,让代码更健壮:-)

总体来说,做单元测试的好处还是比较多的。

建议可以先从简单的算法开始做单元测试,慢慢养成习惯。

结语

好吧,我承认,我骗了你,读到这里,你肯定花了不止30分钟。既然你看到了这里,那证明我的阴谋成功了,被忽悠的感觉爽吧?

如果要投诉我,欢迎给我留言~~

参考资料

QUnit官网

QUnit Cookbook

http://stackoverflow.com/questions/9431597/unit-testing-ajax-requests-with-qunit

http://www.zhangxinxu.com/wordpress/2013/04/qunit-javascript-unit-test-%E5%8D%95%E5%85%83%E6%B5%8B%E8%AF%95/
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息