作者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。
4、設置Expectations
當創建一個test時,最好的做法是限制我們希望執行的斷言的數目。在設置了這個后,如果有些斷言沒有被執行,test將會失敗。Qunit使用expect()方法來設置期待斷言的數目。在測試異步的代碼時這種方法非常有用,但是最好是在處理同步的函數時使用它。expect()用法如下:
asset.expect(assertionsNumber);
assertionsNumber指定了期待斷言的數目。
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... });
5、結論
主要介紹了如何使用Qunit測試JS代碼。我們看到使用Qunit測試代碼非常地簡單,我們了解了測試同步代碼的方法和在斷言asset的主要方法的使用。最后,我們談到了使用expect()設置斷言數目的重要性並且講述了如何使用這個方法。期待你喜歡這篇文章並且在實際項目中使用它。