作者Aurelio De Rosa 是 jQuery in Action(一定翻了几遍了吧) 第三版 和 Instant jQuery Selectors 的作者,并且是拥有着超过五年的HTML, CSS, Sass, JavaScript 和 PHP的开发经验的全栈(full-stack)和webapp开发工程师。不但精通JavaScript和 HTML5 APIs,而且也在web安全、可访问性、性能和方面有着深刻的造诣。(sitepoint:http://www.sitepoint.com/author/aderosa/)
软件测试是给定一组输入值,比较真实值和期待值之间异同的过程。软件测试,特别是单元测试对于软件开发人员是必不可少的。不幸运的是,许多开发人员非常惧怕单元测试。
JS里面有许多可以供我们选择进行单元测试的框架。比如,Mocha, Selenium, jasmine 和 QUnit。本文将重点讲述Qunit,Qunit 由开发了许多大名鼎鼎的js库,包括了的jQuery 、jQuery UI等的Jquery团队(jQuery team)开发。
1、配置QUnit。
QUnit本身非常低简单,并且非常方便去使用。主要的概念可以花费几个小时内掌握。
首先安装QUnit,有三种方式:
官网下载源文件; 引用CDN; 使用 bower 安装( bower install --save-dev qunit ); 使用npm安装( npm install --save-dev qunitjs ) ;
除非开发的项目非常简单或者项目准备发布到生产环境,否则不建议使用cdn的方式。在本文中我们使用第一种引入QUnit。在QUnit官网下载最新版的 qunit-1.20.0.js 和 qunit-1.20.0.css。创建一个html文件,内容如下:
1 <!DOCTYPE html> 2 <html> 3 <head> 4 <meta charset="utf-8"> 5 <title>QUnit Example</title> 6 <link rel="stylesheet" href="../js/qunit/qunit-1.20.0.css"> 7 </head> 8 <body> 9 <div id="qunit"></div> 10 <div id="qunit-fixture"></div> 11 <script src="../js/qunit/qunit-1.20.0.js"></script> 12 </body> 13 </html>
在body里面有两组div标签,div#qunit是QUnit用来显示测试结果的容器。div#qunit-fixture是QUnit留给开发者自己使用的,这个容器允许开发者测试添加,修改或者移除的dom代码,使开发者不必再为在每个test后为清理dom树而担忧。如果将在代码执行过程中创建html防止在这个div内,QUnit将帮助我们重置掉这些代码,恢复到初始状态。
下面来看如何编写使用QUnit进行测试。
2、使用QUnit创建一个测试(test)。
QUnit创建test有两个方法:QUnit.test()和QUnit.asyncTest()。正如它们的命名,QUnit.test()常常用来测试同步运行的代码,QUnit.asyncTest()常常测试异步运行的代码。在这一小节,主要讲解如何使用QUnit.test()测试同步运行的代码。
QUnit.test(name, testFunction)
第一个参数name是用来标记test名称的字符串。第二个参数,testFunction是一个函数,包含了一个asset作为它的参数。asset包含了许多可供我们断言的方法。建立一个test。
QUnit.test('My first test', function(assert) { // Assertions here... });
上面的代码段创建了一个名称为"My first test"的test和没有包含任何断言的一个空函数。下面我们来了解
3、assert断言方法
断言(Assertions)能够校验我们的代码是否如我们期望的那样运行,是软件测试的核心。QUnit有许多方法可以校验。asset作为QUnit.test()的回调函数的第一个参数。
罗列了asset的主要方法和它们的主要用法:
deepEqual(value, expected[, message]) : 递归性严格比较的,针对所有的javascript类型。如果真实值和期待值(expected
)属性,属性值都一样并且和有同样的prototype的话,断言通过。
notDeepEqual(value, expected[, message]):与deepEqual()类似,但是用于比较不相等。
equal(value, expected[, message]) : 真实值和期待值(expected
)是否相等,通过非严格方式的 "==",可能会转换类型。
notEqual(value, expected[, message]) : 与equal()类似,但是用于判断不想等(转换类型的不相等)。
strictEqual(value, expected[, message]) : 真实值和期待值(expected
)是否相全等(===)。
notStrictEqual(value, expected[, message]) : 与strictEqual相反,比较不全等。
propEqual(value, expected[, message]) : 比较真实和期待(expected
)的对象的属性和属性值是否相同,相同断言通过。(注意和deepEqual的区别。)
notPropEqual(value, expected[, message]) : 与propEqual相反。
ok(value[, message]:如果断言的第一个参数value为true,断言通过。
throws(function [, expected ] [, message ]) : 判断函数是否抛出一个异常,第二个参数可选,代表不一定要检测抛出异常的类型。
方法的参数:
value:等待比较的值,值可以由函数返回或者是对象的一个方法或者一个变量;
expected : 拿来作对比的值 。在throws()方法中, expected 可以是一个错误对象或者错误对象的实例、错误函数或者构造函数。或者是一个正则表达式 //todo
message : 一个可用来描述断言的字符串;
function:要执行的应该返回错误的函数;
现在:你应该了解了上面的断言方法和它们参数的含义,下面来看一些代码实例。
1 var SEX = [ 'unsigned', 'man', "women" ]; 2 3 var Person = function (name, sex) { 4 this.name = name; 5 this.sex = sex === undefined || SEX.contains(sex = sex.toLowerCase()) ? sex : SEX[ 0 ]; 6 return this; 7 }; 8 9 Person.prototype.selfIntroduction = function () { 10 console.log('I\'m %s , sex is %s', this.name, this.sex); 11 }; 12 13 var person1 = new Person('Grace', SEX[ 0 ]); 14 var person2 = new Person('Bodhi', 'MAN'); 15 var person3 = new Person('Grace', SEX[ 0 ]); 16 17 QUnit.test("test strictEqual", function (assert) { 18 assert.strictEqual(person1.name, 'Grace', 'strictEqual test pass'); 19 assert.notStrictEqual(person1.name, 'Bodhi', 'person2 test pass'); 20 }); 21 QUnit.test('test array', function (assert) { 22 assert.ok([ 5, 8, 9 ].contains(9), 'test contains pass'); 23 assert.notOk([ 5, 8, 9 ].contains(10), 'test not contains pass'); 24 }); 25 QUnit.test('test equal', function (assert) { 26 assert.strictEqual(person1, person3, 'test person1 and person3 strictEqual pass'); 27 assert.propEqual(person1, person3, 'test person1 and person3 propEqual pass.'); 28 }); 29 QUnit.test('test propEqual', function (assert) { 30 assert.propEqual(person1, {name: 'Grace', sex: 'unsigned'}, 'propEqual pass when prototype is not same.'); 31 assert.propEqual(person1, { 32 name: 'Grace', 33 sex: 'man' 34 }, 'propEqual pass when prototype is not same and the value of sex is different.'); 35 });
1 if (!Array.prototype.indexOf) { 2 Array.prototype.indexOf = function (elt) { 3 var len = this.length >>> 0; 4 var from = Number(arguments[1]) || 0; 5 from = (from < 0)? Math.ceil(from) : Math.floor(from); 6 if (from < 0){ 7 from += len; 8 } 9 for (; from < len; from++) { 10 if (from in this && this[from] === elt){ 11 return from; 12 } 13 } 14 return -1; 15 }; 16 } 17 18 if (!Array.prototype.contains) { 19 Array.prototype.contains = function (elt) { 20 return (this.length >>> 0) !== 0 && this.indexOf(elt) !== -1; 21 } 22 }
在上面的例子中,第一个(test strictEqual)使用===进行比较,两个都通过。assert.ok中如果第一个参数返回true,测试通过。assert.strictEqual比较两个对象当且仅当两个对象指向了同一个地址时候才相等,所以第三个测试不通过。
assert.propEqual会忽略原型仅进行属性和值的比较。在propEqual的源码中有这么一段:
1 function objectValues ( obj ) { 2 var key, val, 3 vals = QUnit.is( "array", obj ) ? [] : {}; 4 for ( key in obj ) { 5 if ( hasOwn.call( obj, key ) ) { 6 val = obj[ key ]; 7 vals[ key ] = val === Object( val ) ? objectValues( val ) : val; 8 } 9 } 10 return vals; 11 }
在使用propEqual比较时,会根据obj的类型确定一个空的待返回值的类型。遍历obj中每一个属于自己的属性,如果属性值(value)是一个对象,就以递归(前序遍历)的方式将新的返回的对象中。不会去检查obj的constructor。