Qunit初探
前言
2008年5月, Qunit 随JQuery的核心库一起发布,在2009年重构之后,Qunit独立出来,可以用于测试各种Javascript应用和框架,其提供的断言方法遵循了CommonJS维护规范。尽管Qunit能再服务端和命令行运行,但是其主要还是用于测试浏览器端的Javascript。
先看一个简单地测试demo:
<!DOCTYPE html>
<head>
<link rel="stylesheet" href="qunit.css">
<script src="qunit.js"></script>
</head>
<body>
<script>
test("hello", function() {
ok(true, "world");
});
</script>
<h1 id="qunit-header">QUnit Hello World</h1>
<h2 id="qunit-banner"></h2>
<ol id="qunit-tests"></ol>
</body>
</html>
先从 这里 下载对应的js和css文件。Qunit提供的 test()
方法定义一个为”hello”的测试,第二个参数是传递给 test()
的函数,里面才是真正的测试代码,示例中调用 ok()
方法,该方法第一个参数是一个布尔值,用于判断测试是否通过,第二个是测试不通过时的输出消息。Qunit会在页面加载完后运行,上面运行后截图如下:
test("hello", function() {
ok(false, "test faild");
});
ok()
期待返回一个True,若返回false,测试不会通过:
测试结果中给出了具体的测试结果。和 test()
类似的另一个方法是 asyncTest()
,后者用于异步测试。
Writing QUnit Tests
给一个稍复杂一点的demo:
<!DOCTYPE html>
<html>
<head>
<title>QUnit Test</title>
<link rel="stylesheet" href="qunit.css">
<script src="qunit.js"></script>
<script src="tests.js"></script>
</head>
<body>
<h1 id="qunit-header">QUnit Test</h1>
<h2 id="qunit-banner"></h2>
<div id="qunit-testrunner-toolbar"></div>
<h2 id="qunit-userAgent"></h2>
<ol id="qunit-tests"></ol>
<div id="qunit-fixture">test markup</div>
</body>
</html>
tests.js的文件内容如下:
function format(string, values) {
for (var key in values) {
string = string.replace(new RegExp("\{" + key + "}"), values[key]);
}
return string;
}
test("basics", function() {
var values = {
name: "World"
};
equal( format("Hello, {name}", values), "Hello, World", "single use" );
equal( format("Hello, {name}, how is {name} today?", values),
"Hello, World, how is World today?", "multiple" );
});
这个demo创建一个basics的测试,用于检测 format()
方法能否返回我们期待的值。 format()
的功能就是模板HTML替换技术,MVC的基础之一。
这里利用 equal()
进行断言,它会使用’==’比较传入的第一个(函数的返回值)和第二个(期待的值)参数,第三个参数是输出信息。测试截图如下:
正如我们看到的,对于多行的匹配, format()
的函数是有bug的。当然,这个bug也是很容易修复的:
new RegExp("\{" + key + "}", "g")
The Assertion Methods of QUnit
在上面的两个demo中,已经用过两个断言方法: ok()
和 equal()
。此外,Qunit提供了很多断言:
-
deepEqual(value, expected[, message])
:跟equal()
类似,但是是使用===
进行更严格的比较。 -
notDeepEqual(value, expected[, message])
:deepEqual()
的反操作 -
notEqual(value, expected[, message])
:equal()
的反操作 -
propEqual(value, expected[, message])
:对对象的属性和值进行严格比较,只有当所有value
和expected
严格相等时,测试才会通过。 -
strictEqual(value, expected[, message])
:验证被提供的value
和expected
参数是否严格相等。 -
notPropEqual(value, expected[, message])
:propEqual
的反操作 -
notStrictEqual(value, expected[, message])
:strictEqual
的反操作 -
throws(function [, expected ] [, message ])
:测试是否有毁掉函数抛出异常,并选择性的抛出错误。
需要说明的是,上述方法中的value是一个函数、方法的返回值,或已经声明的变量的值;expected是期望值;message则是断言的简短描述;function则是一个执行函数,并应该返回一个错误。
看一个demo示例吧:
var App = {
max: function() {
var max = -Infinity;
for (var i = 0; i < arguments.length; i++) {
if (arguments[i] > max) {
max = arguments[i];
}
}
return max;
},
isOdd: function(number) {
return number % 2 !== 0;
},
sortObj: function(array) {
array.sort(function(a, b) {
var date1 = new Date(a.timestamp).getTime();
var date2 = new Date(b.timestamp).getTime();
if (date1 < date2) {
return -1;
} else if (date1 === date2) {
return 0;
} else {
return 1;
}
});
}
};
对象 App
包含了三个方法:max、isOdd和sortObj。 sortObj()
接受一个数组对象,理想情况下,该数组对象应该有一个 timestamp
属性,并基于该属性进行排序。
为了测试这三个方法,一个可能测试集如下:
QUnit.test('max', function (assert) {
assert.strictEqual(App.max(), -Infinity, 'No parameters');
assert.strictEqual(App.max(3, 1, 2), 3, 'All positive numbers');
assert.strictEqual(App.max(-10, 5, 3, 99), 99, 'Positive and negative numbers');
assert.strictEqual(App.max(-14, -22, -5), -5, 'All positive numbers');
});
QUnit.test('isOdd', function (assert) {
assert.ok(App.isOdd(5), '5 is odd');
assert.ok(!App.isOdd(2), '5 is not odd');
assert.ok(!App.isOdd(0), '0 is not odd');
assert.throws(function () {
App.isOdd(null);
},
/The given argument is not a number/,
'Passing null raises an Error');
assert.throws(function () {
App.isOdd([]);
},
new Error('The given argument is not a number'),
'Passing an array raises an Error');
});
QUnit.test('sortObj', function (assert) {
var timestamp = Date.now();
var array = [{
id: 1,
timestamp: timestamp
}, {
id: 3,
timestamp: timestamp + 1000
}, {
id: 11,
timestamp: timestamp - 1000
}];
App.sortObj(array);
assert.propEqual(array, [{
id: 11,
timestamp: timestamp - 1000
}, {
id: 1,
timestamp: timestamp
}, {
id: 3,
timestamp: timestamp + 1000
}]);
assert.notPropEqual(App.sortObj(array), array, 'sortObj() does not return an array');
assert.strictEqual(App.sortObj(array), undefined, 'sortObj() returns
});
测试结果如下:
完整地列表戳此: Assert
Module
在DOM操作中,如果清除多于重置,我们可以将其作为测试的一部分。如果有多个测试需要做同样地清除工作,可以用 module()
进行重构。 module()
最初是为分组测试设计的,例如,多个测试都测试一个特殊的方法,该方法就可以作为单个模块的一部分。为了让测试更加颗粒化,我们可以使用 module()
提供的 setup
和 teardown
回调。
module("core", {
setup: function() {
// runs before each test
},
teardown: function() {
// runs after each test
}
});
test("basics", function() {
// test something
});
setup
会在 test
之前运行,而 teardown
则在 test
之后运行。
我们可以使用这些回调创建对象并在测试中使用,而无需依靠闭包(或全局变量)传给测试。之所以会起作用,是因为 setup
和 teardown
以及真实的测试都在一个自定义的可以自动共享和清除的作用域中调用。
下面是一个用于处理货币值的简单demo(不完整):
var Money = function(options) {
this.amount = options.amount || 0;
this.template = options.template || "{symbol}{amount}";
this.symbol = options.symbol || "$";
};
Money.prototype = {
add: function(toAdd) {
this.amount += toAdd;
},
toString: function() {
return this.template
.replace("{symbol}", this.symbol)
.replace("{amount}", this.amount)
}
};
Money.euro = function(amount) {
return new Money({
amount: amount,
template: "{amount} {symbol}",
symbol: "EUR"
});
};
上面的代码创建了Money对象,还有一个为创建美元(dollars)而提供的默认构造函数,为创建欧元(euros)而提供的工厂方法以及两个用于操作和打印的方法。在测试中,不是为每一个单元测试创建Money对象,而是使用 setup
回调创建对象并存储在测试作用域中。
module("Money", {
setup: function() {
this.dollar = new Money({
amount: 15.5
});
this.euro = Money.euro(14.5);
},
teardown: function() {
// could use this.dollar and this.euro for cleanup
}
});
test("add", function() {
equal( this.dollar.amount, 15.5 );
this.dollar.add(16.1)
equal( this.dollar.amount, 31.6 );
});
test("toString", function() {
equal( this.dollar.toString(), "$15.5" );
equal( this.euro.toString(), "14.5 EUR" );
});
上代码中, setup
回调中创建了两个对象,名叫”add”的测试之使用了一个,”toString”这个测试两个对象都使用了。本例中, teardown
回调没必要使用,因为没必要要移除已经创建的Money对象。结果如下截图:
Testing asynchronous code
可以看到,只要代码是同步的,Qunit就能控制什么时候运行测试代码。然而,一旦测试的代码需要使用异步回调(如 setTimeout
或 Ajax
请求),你需要给QUnit反馈,以便停止后面的测试,知道你让它再次运行。
利用 stop()
和 start()
就能实现Qunit反馈,进行异步测试。看demo:
test("async", function() {
stop();
$.getJSON("resource", function(result) {
deepEqual(result, {
status: "ok"
});
start();
});
});
$.getJSON
会去请求”resource”数据,然后判断比较结果。因为 $.getJSON
是异步请求,先调用 stop()
,随后运行代码,再在 callback
结束的时候调用 start()
, 告诉QUnit继续运行。
运行异步代码没有调用 stop()
,让Qunit停止运行,则会导致本意是其他测试的任意结果,如passed或failed。
正如前文说到得, asyncTest
可以代替 test
用于异步测试,并且不用调用 stop
:
asyncTest("async2", function() {
$.getJSON("resource", function(result) {
deepEqual(result, {
status: "ok"
});
start();
});
});
如果测试的结束点多个-多个回调的顺序随机-我们可以使用QUnit内置信号。调用 stop()
与之后调用的 start()
同数目,则Qunit会继续运行直到 stop()
增加的计数被 start()
减少会0.
test("async semaphore", function() {
stop();
stop();
$.getJSON("resource", function(result) {
equal(result.status, "ok");
start();
});
$.getJSON("resource", function(result) {
equal(result.status, "ok");
start();
});
});
Setting Expectations
测试回调的时候,无论是不是异步,我们都不能确定回调会在某些时候被真正调用了。因而,最好的实践是设置一个我们期望的判断个数,这样一来,若一个或多个断言没有被执行,测试就会失败。Qunit提供了 expect()
方法来达到这个目的。
expect(assertionsNumber)
了解了这个概念后,之前针对App字面量对象的测试可以写成这样:
QUnit.test('max', function(assert) {
expect(4);
// Assertions here...
});
QUnit.test('isOdd', function(assert) {
expect(5);
// Assertions here...
});
QUnit.test('sortObj', function(assert) {
expect(3);
// Assertions here...
});
戳此看 demo
expect()
貌似有个取巧的设置方法,就是给 test()
或 asyncTest()
的第二个参数传递一个数字:
asyncTest("async4", 1, function() {
$.getJSON("resource", function(result) {
deepEqual(result, {
status: "ok"
});
start();
});
});
相关文章:
Qunit VS jasmine VS mocha
Automating JavaScript Testing with QUnit
Getting Started with QUnit
QUnit API
边译边学-QUnit下的JavaScript自动化单元测试
淡忘~浅思猜你喜欢 | ||||
Ubuntu下安装XAMPP |
【译】CSS:7个你可能不认识的单位 |
Linux与Windows的8个不同 |
画图解释 SQL join 语句 |
一点思考和新学年目标 |
无觅 |