javascript單元測試


 

1.      什么是單元測試

在計算機編程中,單元測試(又稱為模塊測試)是針對程序模塊(軟件設計的最小單位)來進行正確性檢驗的測試工作。程序單元是應用的最小可測試部件。在過程化編程中,一個單元就是單個程序、函數、過程等;對於面向對象編程,最小單元就是方法,包括基類(超類)、抽象類、或者派生類(子類)中的方法。

每個理想的測試案例獨立於其它案例;為測試時隔離模塊,經常使用stubs、mock 或fake等測試馬甲程序。單元測試通常由軟件開發人員編寫,用於確保他們所寫的代碼符合軟件需求和遵循開發目標。

單元測試的目標是隔離程序模塊並證明這些單個模塊是正確的。單元測試能確保在開發過程的早期就能發現問題,是為了讓程序“死得更早”。我們應該從開發的早期就為所有函數和方法編寫單元測試,可讀性強的單元測試可以使程序員方便地檢查代碼片斷是否依然正常工作。良好設計的單元測試案例覆蓋程序單元分支和循環條件的所有路徑。采用這種自底向上的測試路徑,先測試程序模塊再測試模塊的集合,一旦變更導致錯誤發生,借助於單元測試可以快速定位並修復錯誤。

2.      JavaScript單元測試現狀

單元測試在后台開發中非常流行和普及,比如JAVA開發者的JUnit等,而在前端開發中則使用的非常少。究其原因,主要是單元測試更適用於邏輯代碼的測試,這對於JAVA等后台編程語言來說測試起來非常方便,但是前端開發很多時候要要UI打交道,UI相關的代碼不是不可以進行單元測試,但的確很麻煩,比起邏輯代碼來說困難多了,這就導致了單元測試在前端開發沒有普及起來。

但是隨着單元測試的普及,尤其是敏捷開發的推動,涌現了許多優秀的JavaScript單元測試框架,如QUnit、Jasmine等。所有的這些框架基本上都能對Javascript代碼進行很好的測試,當然UI部分的代碼測試一樣比較麻煩,但是我們可以通過精心構造我們的測試代碼來測試部分UI代碼。但是每個框架都不是萬能的,它們都有各自擅長的領域,下面選取了幾個具有代表性的框架進行介紹。

 

3.      單元測試常用框架

l   QUnit框架

a)        簡介

QUnit是jQuery團隊開發的JavaScript單元測試工具,功能強大且使用簡單。目前所有的JQuery代碼都使用QUnit進行測試,原生的JavaScript也可以使用QUnit。

最初,John Resig將QUnit設計為jQuery的一部分。2008年,QUnit才有了自己的名字、主頁和API文檔,也開始允許其他人用它來做單元測試。但當時QUnit還是基於jQuery的。直到2009年,QUnit才可以完全的獨立運行。

b)       優點

使用起來非常方便,有漂亮的外觀和完整的測試功能(包括異步測試);

非常簡單,容易上手,目前公開的API只有19個;

不需要依賴其它任何軟件包或框架,只要能運行JS的地方就可以,QUnit本身只有一個JS文件和CSS文件,當然如果需要可以和jQuery等其它框架集成;

不僅支持在瀏覽器中測試,還支持在Rhino和node.js等后端測試。

c)        不足

對自動化支持不好,很難和Ant、Maven或自動構建等工具集成,主要用在瀏覽器中進行測試。

d)       API

QUnit所有的API可以分為三類:Setup,Assertions,Asynchronous Testing,下面就分別對這些API做些介紹:

Setup:

test( name, [expected], testFun )   代表QUnit中的一個測試

name:要測試的名稱,比如“加法函數”或“add”等

expected:可選參數,用來表示該測試函數的斷言的數量,是個正整數

testFun:一個函數,所有的測試代碼都應該包括在該函數里,通常這是一個匿名函數。

例:

test(“add function”, 1, function() {
    equal(add(1, 2), 3);
});

asyncTest( name, [expected], testFun )   代表QUnit中的一個異步測試,參數同test

expect( amount )   用在測試函數中,用於聲明測試斷言的數量,這個函數和test中的expected參數的作用是一樣的。主要作用就是檢查你聲明的個數和你寫的斷言的實際個數是否一致。

module( name, [lifecycle] )   主要用於測試函數的分組,一個module函數為一個分組,比如module(“validate”)表示后面的測試用例都是validate相關的代碼,或者module(“common.js”),表明后面的測試用例都是common.js里面的代碼。一個測試文件可以寫多個module。

name:分組或者模塊的名稱

lifecycle:可選參數,它是一個對象,可以設置setup和teardown回調函數

例:

module(“common.js”, 
    {
        setup:function(){},
        teardown: function() {} 
    }
);

setup:在module開始之前執行,可以為該module下面的測試代碼做一些准備工作

teardown:將會在該module的所有測試代碼執行后執行,比如做一些清理還原工作等。

QUnit.init( )   用於初始化QUnit測試框架,通常這個函數是不需要我們手工調用的。

QUnit.reset( )   重設函數,通常是在每個test函數執行后由QUnit自己調用來重設整個QUnit測試環境,當然必要時我們自己也可以調用它來復原,不常用。

 

Assertions:

ok( state, [message] )   斷言。state值為true時表示通過,否則失敗。

equal( actual, expected, [message] )   比較參數actual和expected是否相等,相當於 ==

notEqual( actual, expected, [message] )   比較兩個參數是否不相等,相當於 !=

deepEqual( actual, expected, [message] )   主要用於數組和對象等類型的值是否相等,會遞歸遍歷它們所包含的值是否相等。

notDeepEqual( actual, expected, [message] )   主要用於數組和對象等類型的值是否不相等,會遞歸遍歷它們所包含的值是否不相等。

strictEqual( actual, expected, [message] )   比較兩個參數是否嚴格相等,相當於 ===

notStrictEqual( actual, expected, [message] )   比較兩個參數是否不嚴格相等,相當於 !==

throws( block, expected, [message] )   測試block函數是否拋出一個異常,拋出則通過,不拋則失敗。

block:我們要測試的函數

expected:可選參數,是一個類型,用來驗證第一個函數拋出的異常是否是我們預期的類型。

例:

function CustomError( message ) {
    this.message = message;
}

CustomError.prototype.toString = function() {
    return this.message;
};
throws(
    function() {
        throw new CustomError(“some error description”);
    },
    CustomError,
    "raised error is an instance of CustomError"
);

 

Asynchronous Testing:

stop( [increment] )   停止測試的運行,用於異步測試。在異步測試時一般先把QUnit的test runner停下來。

increment:增加停止的時間。

start( [decrement] )   當異步調用成功后就應該把停止的test runner啟動起來讓它接着往前跑

decrement:用來減少停止的時間。

例:

test( "a test", function() {
    stop();
    var result = null;
    $.ajax(
        url,
        {},
        function(data){
            result = data;
        }
    );
    setTimeout(function() {
        equals(result, "success" );
        start();
    }, 150 );
});

 

e)        使用

test.html

導入qunit.css,qunit.js

依次導入被測試文件src.js和測試文件test.js

src.js里是我們要測試的一些函數

test.js里放我們的測試

打開test.html,顯示:

如果期望值與函數執行的結果不一致,會報錯:

test.js

test.html顯示:

期望值與結果不符,測試不通過。

 

與瀏覽器自動化測試工具集成的接口:

都是QUnit自動調用的一些函數,一般不用改,也可以自己定制

QUnit.log(Function({ result, actual, expected, message }))   這個接口會在每個斷言執行后被自動調用

result:斷言是否通過

message:斷言里的message參數

例:

QUnit.log(function(details){
	alert(“Log: ” + details.result + “ ” + details.message);
})

QUnit.testStart(Function({ name }))   在每個測試函數執行前被自動調用

name:測試函數中的name參數值

QUnit.testDone(Function({ name, failed, passed, total }))   在每個測試函數結束后執行被自動調用

name:同上

failed:指失敗斷言的個數

passed:指成功斷言的個數

total:所有斷言的個數

QUnit.moduleStart(Function({ name }))   在每個module所有的測試代碼執行前被自動調用

name:module函數中name參數的值

QUnit.moduleDone(Function({ name, failed, passed, total }))   在每個module所有的測試代碼執行完之后被自動執行

failed:指失敗斷言的個數

passed:指成功斷言的個數

total:指所有斷言的個數

QUnit.begin(Function())   在所有的測試代碼調用之前運行

QUnit.done(Function({ failed, passed, total, runtime }))   在所有的測試代碼調用之后運行

failed:指失敗斷言的個數

passed:指成功斷言的個數

total:指所有斷言的個數

runtime:所有代碼的執行時間

 

API及下載:http://api.qunitjs.com/

參考:http://www.weakweb.com/articles/255.html

http://www.iteye.com/topic/981253

 

l  Jasmine框架

a)        簡介

Jasmine是一個有名的JavaScript單元測試框架,它是獨立的行為驅動開發框架,語法清晰易懂。

行為驅動開發(BDD)是一種敏捷軟件開發的技術,它鼓勵軟件項目中的開發者、QA和非技術人員或商業參與者之間的協作。BDD最初是由Dan North在2003年命名,它包括驗收和客戶測試驅動等的極限編程的實踐,作為對測試驅動開發的回應。在過去的數年里,得到了極大的發展。

BDD的重點是通過與利益相關者的討論取得對預期的軟件行為的清醒認識。它通過用自然語言書寫非程序員可讀的測試用例擴展了測試驅動開發方法。行為驅動開發人員使用混合了領域中統一的語言的母語語言來描述他們的代碼的目的。這讓開發者得以把精力集中在代碼應該怎么寫,而不是技術細節上,而且也最大程度的減少了將代碼編寫者的技術語言與商業客戶、用戶、利益相關者、項目管理者等的領域語言之間來回翻譯的代價。

 

BDD的做法包括:

l  確立不同利益相關者要實現的遠景目標

l  使用特性注入方法繪制出達到這些目標所需要的特性

l  通過由外及內的軟件開發方法,把涉及到的利益相關者融入到實現的過程中

l  使用例子來描述應用程序的行為或代碼的每個單元

l  通過自動運行這些例子,提供快速反饋,進行回歸測試

l  使用“應當(should)”來描述軟件的行為,以幫助闡明代碼的職責,以及回答對該軟件的功能性的質疑

l  使用“確保(ensure)”來描述軟件的職責,以把代碼本身的效用與其他單元(element)代碼帶來的邊際效用中區分出來。

l  使用mock作為還未編寫的相關代碼模塊的替身

 

BDD特性注入:一個公司可能有多個會帶來商業利益的不同願景,通常包括盈利、省錢或保護錢。一旦某個願景被開發小組確定為當前條件下的最佳願景,他們將需要更多的幫助來成功實現這個遠景。

然后確定該願景的主要利益相關者,會帶入其他的利益相關者。每個相關者要定義為了實現該願景他們需要完成的目標。例如,法務部門可能要求某些監管要得到滿足。市場營銷負責人可能要參加將使用該軟件的用戶的社區。安全專家需要確保該軟件不會受到SQL注入的攻擊。

通過這些目標,會定義出要實現這些目標所需要的大概的題目或者特性集合。例如,“允許用戶排序貢獻值”或“交易審計”。從這些主題,可以確定用戶功能以及用戶界面的第一批細節。

 

b)       優點

它是基於行為驅動開發實現的測試框架,它的語法非常貼近自然語言,簡單明了,容易理解。

能很方便的和Ant、Maven等進行集成進行自動化測試,也可以方便和Jekins等持續集成工具進行集成,可以生成測試結果的XMl文檔。

它有豐富的API,同時用戶也支持用戶擴展它的API,這一點很少有其它框架能夠做到。

使用方便簡單,只需要引入兩個js文件即可

不僅支持在瀏覽器中測試,還支持在Rhino和node.js等后端測試。

對於Ruby語言有特別的支持,能夠非常方便的集成到Ruby項目中去

c)        不足

在瀏覽器中的測試界面不如QUnit美觀、詳細。

d)       API

it(string, function)   一個測試Spec

string:測試名稱

function:測試函數

describe (string, function)    一個測試組開始於全局函數describe,一個describe是一個it的集合。describe包含n個it,一個it包含n個判斷斷言  Suite

string:測試組名稱

function:測試組函數

describe("測試add()函數", function() {
    it("1 + 1 = 2", function(){
        expect(add(1, 1)).toBe(2);
    });
});

beforeEach(function)   定義在一個describe的所有it執行前做的操作

afterEach(function)   定義在一個describe的所有it執行后做的操作

expect(a).matchFunction(b)

expect(a).not.matchFunction(b)   期望a和b滿足匹配方式matchFunction

matchFunctions

 

toBe   相當於===,處理簡單字面值和變量

    it("toBe相當於===", function(){
        var a = 12;
        var b = a;

        expect(a).toBe(b);
        expect(a).not.toBe(null);
        expect(false == 0).toBe(true);
    });
    it("toBe不能當==用", function(){
        expect(false).toBe(0);
    });

toEqual   處理簡單字面值和變量,而且可以處理對象,數組

    it("toEqual可以處理字面值,變量和對象", function(){
        var a = 12;
        expect(a).toEqual(12);

        var foo = {key : "key"};
        var bar = {key : "key"};
        expect(foo).toEqual(bar);

        var arr1 = [];
        arr1["p1"] = "string1";
        var arr2 = [];
        arr2["p1"] = "string1";
        var obj = {};
        obj["p1"] = "string1";
        expect(arr1).toEqual(arr2);
        expect(arr1).toEqual(obj);
    });

toMatch   按正則式檢索。

    it("toMatch匹配正則式", function(){
        var message = "foo bar baz";
        expect(message).toMatch(/bar/);
        expect(message).toMatch("bar");
        expect(message).not.toMatch(/quux/);
        expect(message).toMatch(/^f/);
        expect(message).not.toMatch(/f$/);
    });

toBeDefined   是否已聲明且賦值

    it("toBeDefined檢測變量非undefined", function(){
        var a = { key : "key"};

        expect(a.key).toBeDefined();
        expect(a.foo).not.toBeDefined();

        //expect(c).not.toBeDefined();  //未聲明出錯
        var b;
        expect(b).not.toBeDefined();
    });

       對象.未聲明屬性.not.toBeDefined();   通過

       未聲明變量.not.toBeDefined();       報錯

toBeUndefined      是否undefined

toBeNull   是否null

toBeTruthy   如果轉換為布爾值,是否為true

toBeFalsy    如果轉換為布爾值,是否為false

toContain   數組中是否包含元素(值)。只能用於數組,不能用於對象

    it("toContain檢驗數組中是否包含元素(值)", function(){
        var a = ["foo", "bar", "baz"];

        expect(a).toContain("bar");
    });

toBeLessThan   數值比較,小於

toBeGreaterThan   數值比較,大於

toBeCloseTo   數值比較時定義精度,先四舍五入后再比較

    it("toBeCloseTo數值比較,指定精度,先四舍五入再比較", function() {
        var pi = 3.1415926, e = 2.78;

        expect(pi).toBeCloseTo(e, 0);
        expect(pi).not.toBeCloseTo(e, 0.1);
    });

 

toThrow    檢驗一個函數是否會拋出一個錯誤

    it("toThrow檢驗一個函數是否會拋出一個錯誤", function() {
        var foo = function() {
          return 1 + 2;
        };
        var bar = function() {
          return a + 1;
        };

        expect(foo).not.toThrow();
        expect(bar).toThrow();
    });

 

注:describe可嵌套

       xdescribe 和 xit:路過不執行,結果不顯示。像display:none。點控制欄中skipped顯示

      

Spy   存儲函數的被調用情況和參數(函數監視器,記錄被調用情況,但函數並不真執行)

describe("對spy函數的測試", function() {
    var foo, bar = null;

    beforeEach(function() {
        foo = {
            setBar: function(value) {
                bar = value;
            }
        };

        spyOn(foo, 'setBar');  //foo為spy函數

        foo.setBar(123);
        foo.setBar(456, 'another param');
    });

    it("測試foo函數是否被調用過", function() {
        expect(foo.setBar).toHaveBeenCalled();
    });

    it("測試foo函數被調用的次數", function() {
        expect(foo.setBar.calls.length).toEqual(2);
    });

    it("測試foo函數被調用時傳入的參數", function() {
        expect(foo.setBar).toHaveBeenCalledWith(123);
        expect(foo.setBar).toHaveBeenCalledWith(456, 'another param');
    });

    it("上一次被調用的參數", function() {
        expect(foo.setBar.mostRecentCall.args[0]).toEqual(456);
    });

    it("所有被調用的情況存在一個數組里", function() {
        expect(foo.setBar.calls[0].args[0]).toEqual(123);
    });

    it("函數並未真的執行", function() {  
        expect(bar).toBeNull();  
    });
});

Spy addCallThrough  函數監視器,但函數真的執行

describe("對spy函數的測試,函數真的執行", function() {
    var foo, bar, fetchedBar;

    beforeEach(function() {
        foo = {
            setBar: function(value) {
                bar = value;
            },
            getBar: function() {
                return bar;
            }
        };

        //spyOn(foo, "setBar");    //如果加上這句,setBar不真的執行,后兩個spec不通過
        spyOn(foo, 'getBar').andCallThrough();

        foo.setBar(123);
        fetchedBar = foo.getBar();
    });

    it("測試foo中getBar函數是否被調用過", function() {
        expect(foo.getBar).toHaveBeenCalled();
    });

    it("foo中setBar函數真的執行了", function() {
        expect(bar).toEqual(123);
    });

    it("foo中getBar函數真的執行了", function() {
        expect(fetchedBar).toEqual(123);
    });
});

Spy andReturn  函數監視器,函數不真的執行。指定監視的函數的返回值

describe("A spy, when faking a return value", function() {
    var foo, bar, fetchedBar;

    beforeEach(function() {
        foo = {
            setBar: function(value) {
                bar = value;
            },
            getBar: function() {
                return bar;
            }
        };

        spyOn(foo, 'getBar').andReturn(745);  //指定getBar函數返回745

        foo.setBar(123);
        fetchedBar = foo.getBar();
    });

    it("測試foo中getBar函數是否被調用過", function() {
        expect(foo.getBar).toHaveBeenCalled();
    });

    it("不影響未被監視的其它函數", function() {
        expect(bar).toEqual(123);
    });

    it("指定的返回值745", function() {
        expect(fetchedBar).toEqual(745);
    });
});

Spy addCallFake  替代被監視的函數,原函數不執行

describe("替代被監視的函數,原函數不執行", function() {
    var foo, bar, fetchedBar;

    beforeEach(function() {
        foo = {
            setBar: function(value) {
                bar = value;
            },
            getBar: function() {
                alert("frostbelt");
                return bar;
            }
        };

        spyOn(foo, 'getBar').andCallFake(function() {
            return 1001;
        });

        foo.setBar(123);
        fetchedBar = foo.getBar();
    });

    it("測試foo中getBar函數是否被調用過", function() {
        expect(foo.getBar).toHaveBeenCalled();
    });

    it("不影響未被監視的其它函數", function() {
        expect(bar).toEqual(123);
    });

    it("getBar被addCallFake指定的匿名函數代替,getBar不執行", function() {
        expect(fetchedBar).toEqual(1001);
    });
});

如果你沒有什么可監視的又實在想監視一下,該咋辦?自己create一個被監視函數。。

jasmine.createSpy(functionId)

describe("自己造一個被監視函數。啊,我凌亂了。。", function() {
    var whatAmI;

    beforeEach(function() {
        whatAmI = jasmine.createSpy('whatAmI');

        whatAmI("I", "am", "a", "spy");
    });

    it("有個id,是createSpy的傳入函數,用於報錯", function() {
        expect(whatAmI.identity).toEqual('whatAmI')
    });

    it("是否被調用", function() {
        expect(whatAmI).toHaveBeenCalled();
    });

    it("被調用的次數", function() {
        expect(whatAmI.calls.length).toEqual(1);
    });

    it("被調用的參數", function() {
        expect(whatAmI).toHaveBeenCalledWith("I", "am", "a", "spy");
    });

    it("最近一次被調用", function() {
        expect(whatAmI.mostRecentCall.args[0]).toEqual("I");
    });
});

有時需要監視一個對象的很多方法,用createSpyObj添加方法數組

jasmine.createSpyObj(obj, methodArray)

describe("有時需要監視一個對象的很多個方法,用createSpyObj添加數組", function() {
    var tape;

    beforeEach(function() {
        tape = jasmine.createSpyObj('tape', ['play', 'pause', 'stop', 'rewind']);

        tape.play();
        tape.pause();
        tape.rewind(0);
    });

    it("tape對象的這四個方法已被定義", function() {
        expect(tape.play).toBeDefined();
        expect(tape.pause).toBeDefined();
        expect(tape.stop).toBeDefined();
        expect(tape.rewind).toBeDefined();
    });

    it("四個方法是否被調用", function() {
        expect(tape.play).toHaveBeenCalled();
        expect(tape.pause).toHaveBeenCalled();
        expect(tape.rewind).toHaveBeenCalled();
        expect(tape.stop).not.toHaveBeenCalled();
    });

    it("被調用時傳入的參數", function() {
        expect(tape.rewind).toHaveBeenCalledWith(0);
    });
});

 

jasmine.any   類型判斷。instanceof

describe("類型匹配", function() {
    it("相當於instanceof", function() {
        expect({}).toEqual(jasmine.any(Object));
        expect(12).toEqual(jasmine.any(Number));
    });

    it("也可以用於spy", function() {
        var foo = jasmine.createSpy('foo');
        foo(12, function() {
            return true
        });

        expect(foo).toHaveBeenCalledWith(jasmine.any(Number), jasmine.any(Function));
        //foo被調用時的參數 類型判斷
    });
});

jasmine.Clock.useMock()   jasmine自己控制時間,實現異步調試,減少等待

jasmine.Clock.tick(n:uint)   向前n毫秒

describe("jasmine自己控制時間,實現異步調試,減少等待", function() {
    var timerCallback; 

    beforeEach(function() {
        timerCallback = jasmine.createSpy('timerCallback');
        jasmine.Clock.useMock();
    }); 

    it("setTimeout", function() {
        setTimeout(function() {
            timerCallback();
        }, 100);

        expect(timerCallback).not.toHaveBeenCalled();

        jasmine.Clock.tick(101);

        expect(timerCallback).toHaveBeenCalled();
    });

    it("setInterval", function() {
        setInterval(function() {
            timerCallback();
        }, 100);

        expect(timerCallback).not.toHaveBeenCalled();

        jasmine.Clock.tick(101);
        expect(timerCallback.callCount).toEqual(1);

        jasmine.Clock.tick(50);
        expect(timerCallback.callCount).toEqual(1);

        jasmine.Clock.tick(50);
        expect(timerCallback.callCount).toEqual(2);
    });
});

注:在這種環境下setTimeout和setInterval的callback為同步的,系統時間不再影響執行

 

runs(function)  waitsFor(function, message, millisec)   Jasmine異步調試  按自己的理解寫個例子

describe("jasmine異步調試,對ajax結果的斷言", function(){
    var data, flag = false;

    it("ajax是否按時返回了正確結果", function(){
        runs(function(){
            $.post(
                url,
                {},
                function(data){
                    flag = true;
                    data = data.someAttr;
                }
            );
        });

        waitsFor(function(){  //flag為true或到2秒時執行  2秒內返回true則執行最后一個runs,到時未返回則本spec出錯,返回第二個參數錯誤信息
            return flag;
        }, "ajax在指定時間2秒內未返回", 2000);

        runs(function(){  //直到waitsFor返回true時執行
            expect(data).toEqual("someThing");
        })
    });
});

注:it是一個spec,包含

runs(function)

waitsFor(function, message, millsec)

runs(function)

第一個runs里有一些異步的代碼

waitsFor中的funciton如果在millsec內返回true,執行最后一個runs

       如果在millsec內不能返回true,spec不通過,顯示錯誤信息message

原文代碼:

 

e)        使用

在測試的頁面里加入以下代碼:

  <script type="text/javascript">

    (function() {
       var jasmineEnv = jasmine.getEnv();
       jasmineEnv.updateInterval = 1000;

       var trivialReporter = new jasmine.TrivialReporter();

       jasmineEnv.addReporter(trivialReporter);

       jasmineEnv.specFilter = function(spec) {
       	return trivialReporter.specFilter(spec);
       };

       var currentWindowOnload = window.onload;

       window.onload = function() {
        	if (currentWindowOnload) {
         		currentWindowOnload();
         	}
         	execJasmine();
       };

       function execJasmine() {
         	jasmineEnv.execute();
       }

    })();
  </script>

導入jasmine.css  jasmine.js  jasmine-html.js 

       src.js(源代碼)  test.js(存放describes)

 

參考:http://www.weakweb.com/articles/255.html

       http://pivotal.github.com/jasmine/

下載:https://github.com/pivotal/jasmine/downloads

 

 

l  JsTestDriver

a)        簡介

JsTestDriver是一個JavaScript單元測試工具,易於與持續構建系統相集成並能夠在多個瀏覽器上運行測試輕松實現TDD風格的開發。當在項目中配置好JsTestDriver以后,如同junit測試java文件一般,JsTestDriver可以直接通過運行js文件來進行單元測試。JsTestDriver框架本身就是JAVA的jar包,需要在本地運行並監聽一個端口。

b)       優點

可以一次測試多個瀏覽器,使用方法是在啟動服務時可以將多個瀏覽器的路徑作為參數傳進去。可以在多台機器上的瀏覽器中運行,包括移動設備。

測試運行得很快,因為不需要將結果添加到DOM中呈現出來,它們能夠同時在任意多的瀏覽器中運行,未修改的文件瀏覽器會從緩存提取。

不需要HTML配件文件,僅僅只需提供一個或多個腳本和測試腳本,測試運行器運行時會創建一個空文件。

能很方便的和Ant、Maven等進行集成進行自動化測試,也可以方便和Jekins等持續集成工具進行集成,可以生成測試結果的XML文檔。

有Eclipse和IntelliJ插件,可以很方便的在這兩個IDE中進行測試,和JUnit很像。

支持其它測試框架,可以測試其它測試框架寫的測試代碼,比如有對應的插件可以將QUnit和Jasmine測試代碼轉換成JsTestDriver的測試代碼。

c)        不足

不能在瀏覽器中測試,只能通過自動化工具或控制台運行。生成的結果不夠直觀。

安裝使用稍微有點麻煩,依賴於JAVA環境。

d)       API

assert(msg, value) 

assertTrue(msg, value) 

assertFalse(msg, value) 

assertEquals(msg, expected, actual) 

assertNotEquals(msg, expected, actual) 

assertSame(msg, expected, actual) 

assertNotSame(msg, expected, actual) 

assertNull(msg, value) 

assertNotNull(msg, value) 

assertUndefined(msg, value) 

assertNotUndefined(msg, value) 

assertNaN(msg, number) 

assertNotNaN(msg, number) 

assertException(msg, callback, type) 

assertNoException(msg, callback) 

assertArray(msg, arrayLike) 

assertTypeOf(msg, type, object) 

assertBoolean(msg, value) 

assertFunction(msg, value) 

assertNumber(msg, value) 

assertObject(msg, value) 

assertString(msg, value) 

assertMatch(msg, pattern, string) 

assertNoMatch(msg, pattern, string) 

assertTagName(msg, tagName, element) 

assertClassName(msg, className, element) 

assertElementId(msg, id, element) 

assertInstanceOf(msg, constructor, object) 

assertNotInstanceOf(msg, constructor, object)

API按字面意思理解即可,不一一標注

e)        使用

前提:

安裝java環境

下載JsTestDriver.jar

 

目錄:

JsTestDriver.jar

jsTestDriver.conf        //配置文件,默認名稱,如果用其它名稱,需要指定config參數

src

----src.js

test

----test.js

 

jsTestDriver.conf:

 

src.js:

test.js:

像java的JUnit一樣,測試方法名要以”test”開頭,如:”testXXXX”

測試步驟:

  1. cmd 進入目錄
  2. 運行命令”java –jar JsTestDriver.jar –port 9876”
  3. 打開頁面http://localhost:9876,點擊“捕獲瀏覽器”。
  4. 新打開一個終端,運行命令”java –jar JsTestDriver.jar –tests all”,運行所有測試用例
  5. 也可以單獨運行某一個用例,如運行命令” java –jar JsTestDriver.jar –tests addTest.testA”

 

安裝:http://code.google.com/p/js-test-driver/wiki/UsingTheEclipsePlugin

下載:http://code.google.com/p/js-test-driver/

參考:《測試驅動的JavaScript開發》

      

l  FireUnit

a)        簡介

FireUnit是一個基於Firebug的Javascript的單元測試框架。簡單說來,FireUnit給Firebug增加了一個標簽面板,並提供了一些簡單的JavaScript API來記錄和查看測試。

b)       優點

簡單易用

c)        不足

功能不多,測試代碼常常寫在源碼里,雖然可以實時地看到效果,但耦合太強,不易清理

只運行在Firefox下

d)       API

常用:

fireunit.ok(condition, message)   true/false

fireunit.compare(actual, expect, message)   是否相等

fireunit.reCompare(regexp, string, message)   字符串是否與正則式匹配

fireunit.testDone();   執行以上的測試,在Firebug的新標簽test中顯示結果

其它:

fireunit.runTests(“test2.html”, “test3.html”)   一起運行多個頁面的測試(每個文件都含有一些獨立的測試)

fireunit.log(message)   打印log,但似乎不管用

fireunit.id(id)   相當與document.getElementById

fireunit.click(element)   模擬觸發element的click事件

fireunit.focus(element)   模擬觸發element的focus事件

fireunit.mouseDown(element)   模擬觸發element的mouseDown事件

       但看代碼明明是click事件

       在FF下,只是執行了node.click();不會觸發onmousedown,只會觸發onclick。已驗證

fireunit.value(element)   修改element的value

fireunit.key(element, key)   模擬觸發element的key事件

       例:

       var input = document.getElementsByTagName(“input”)[0];

       fireunit.key(input, “a”);

沒用過:

fireunit.browser

fireunit.test

fireunit.forceHTTP

fireunit.registerPathHandler

fireunit.panel

fireunit.privilege

 

 

e)        使用

在FF下裝Firebug和FireUnit

直接在代碼里寫測試,就像console.log();

 

參考:https://github.com/jeresig/fireunit/wiki/internals

       http://hi.baidu.com/dearhwj/blog/item/1d1e0dbfc380f80219d81f35.html

 

4.      小結

QUnit框架簡單方便,測試界面直觀詳細

Jasmine功能強大,風格也簡單明了,符合前端開發者的編程習慣,推薦

JsTestDriver可以和QUnit等框架結合,可以同時測多個瀏覽器。但安裝復雜,只能在控制台顯示,不友好,不夠清晰

FireUnit小巧靈活,加上Firebug的人氣應該很受歡迎

如果需要進行自動化測試, 多了解一下Jasmine和JsTestDriver,本文未深入。

 

5.      附注

JavaScript單元測試框架詳細列表

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM