單元測(cè)試之道Java------使用JUnit------

 

1 序言

 

·什么是單元測(cè)試

單元測(cè)試是開發(fā)者編寫的一小段代碼,用于檢驗(yàn)被測(cè)代碼的一個(gè)很小的、很明確的功能是否正確。通常而言,一個(gè)單元測(cè)試是用于判斷某個(gè)特定條件(或者場(chǎng)景)下某個(gè)特定函數(shù)的行為。

 

·為什么要使用單元測(cè)試

單元測(cè)試不但會(huì)使你的工作完成得更輕松,而且會(huì)令你的設(shè)計(jì)變得更好。甚至大大減少你花在調(diào)試上面的時(shí)間。

 

·不寫測(cè)試的借口

編寫單元測(cè)試太花時(shí)間了

運(yùn)行測(cè)試的時(shí)間太長(zhǎng)了

測(cè)試代碼并不是我的工作

我并不清楚代碼的行為,所以也就無(wú)從測(cè)試

但是這些代碼都能夠編譯通過(guò)

公司請(qǐng)我來(lái)試我是為了寫代碼,而不是寫測(cè)試

如果我讓測(cè)試員或者QA人員沒有工作,那么我會(huì)覺得很內(nèi)疚

我的公司并不會(huì)讓我在真實(shí)系統(tǒng)中運(yùn)行單元測(cè)試

 

3 使用JUnit編寫測(cè)試

 

3.1 構(gòu)建單元測(cè)試

  測(cè)試代碼必須要做以下幾件事情:

·準(zhǔn)備測(cè)試所需要的各種條件(創(chuàng)建所有必須的對(duì)象,分配必要的資源等等)。

·調(diào)用要測(cè)試的方法。

·驗(yàn)證被測(cè)試方法的行為和期望是否一致。

·完成后清理各種資源。

 

3.2 JUnit的各種斷言

  JUnit提供了一些輔助函數(shù),用于幫助你確定某個(gè)被測(cè)試函數(shù)是否工作正常。通常而言,我們把所有這些函數(shù)統(tǒng)稱為斷言。斷言是單元測(cè)試最基本的組成部分。

  當(dāng)一個(gè)失敗或錯(cuò)誤出現(xiàn)的時(shí)候,當(dāng)前測(cè)試方法的執(zhí)行流程將會(huì)被種植,但是(位于同一個(gè)測(cè)試類中的)其他測(cè)試將會(huì)繼續(xù)運(yùn)行。

 

 · assertEquals

assertEquals([String message],

expected,

actual)

 

這是使用得最多的斷言形式。在上面的參數(shù)中,expected是你的期望值(通常都是硬編碼的),actual是被測(cè)試代碼實(shí)際產(chǎn)生的值,message是一個(gè)可選的消息,如果提供的話,將會(huì)在發(fā)生錯(cuò)誤的時(shí)候報(bào)告這個(gè)消息。當(dāng)然,你完全可以不提供這個(gè)message參數(shù),而只提供expectedvalue這兩個(gè)值。

任何對(duì)象都可以拿來(lái)做相等性測(cè)試:適當(dāng)?shù)南嗟刃耘袛喾椒〞?huì)被用來(lái)做這樣的比較。值得注意的是使用原生數(shù)組的equals方法時(shí),它并不是比較數(shù)組的內(nèi)容,而只是比較數(shù)組引用本身,而這大概不是你希望的吧。

計(jì)算機(jī)并不能精確地表示所有的浮點(diǎn)數(shù),通常都會(huì)有一些偏差。因而,如果你想用斷言來(lái)比較浮點(diǎn)數(shù)(在Java中,是類型為float或者double的數(shù)),則需要制定一個(gè)額外的誤差參數(shù)。它表明你需要多接近才能認(rèn)為兩數(shù)“相等”。

assertEquals([String message],

expected,

actual,

tolerance)

 

 

·assertNull

assertNull([String message], java.lang.Object object)

assertNotNull([String message], java.lang.Object object)

 

驗(yàn)證一個(gè)給定的對(duì)象是否為null(或者為非null),如果答案為否,則將會(huì)失敗。message參數(shù)是可選的。

 

 

·assertSame

assertSame([String message], expected, actual)

 

驗(yàn)證expected參數(shù)和actual參數(shù)所引用的是否為同一個(gè)對(duì)象,如果不是的話,將會(huì)失敗。message參數(shù)是可選的。

 

assertNotSame([String message], expected, actual)

 

驗(yàn)證expected參數(shù)和actual參數(shù)所應(yīng)用的是否為不同的對(duì)象,如果是相同的話,將會(huì)失敗。message參數(shù)是可選的。

 

 

·assertTrue

assertTrue([String message], Boolean condition)

 

驗(yàn)證給定的二元條件是否為真,如果為假的話,將會(huì)失敗。meesage參數(shù)是可選的。

如果你發(fā)現(xiàn)測(cè)試代碼像下面這樣,宛如廢話一般:

asserTrue(true);

那么你就該好好想想這些代碼了。對(duì)于這種寫法,除非是被用于確認(rèn)某個(gè)分支,或者一場(chǎng)邏輯才有可能是正確的選擇;否則的話,很可能就是一個(gè)糟糕的主意。

 

assertFalse([String message], Boolean condition)

上面的代碼用于驗(yàn)證給定的二元條件是否為假。如果不是的話,該測(cè)試將會(huì)失敗。

 

 

·fail

fail([String message])

上面的斷言將會(huì)使測(cè)試立即失敗,其中message參數(shù)是可選的。這種斷言通常被用于標(biāo)記某個(gè)不應(yīng)該被到達(dá)的分支(譬如,在一個(gè)與預(yù)期發(fā)生的異常之后)。

 

 

·使用斷言

一般而言,一個(gè)測(cè)試方法會(huì)包含有多個(gè)斷言,因?yàn)槟阈枰?yàn)證該方法的多個(gè)方面以及內(nèi)在的多種聯(lián)系。

當(dāng)有測(cè)試失敗的時(shí)候,無(wú)論如何都不能給原有代碼再添加新的特性!此時(shí)你應(yīng)該盡快地修復(fù)這個(gè)錯(cuò)誤,直到讓所有的測(cè)試都能順利通過(guò)。

 

 

3.3 JUnit框架

每個(gè)包含測(cè)試的類都必須如所示那樣由TestCase繼承而來(lái)。基類TestCase提供了我們所需的大部分單元測(cè)試功能,包括所有在前面講述過(guò)的斷言方法。

基類需要一個(gè)以String為參數(shù)的構(gòu)造函數(shù),因而我們必須調(diào)用super以傳遞這么一個(gè)名字。

測(cè)試類包含了名為test…的方法。而所有以test開頭的方法都會(huì)被JUnit自動(dòng)運(yùn)行。你還可以通過(guò)定義suite方法制定特殊的函數(shù)來(lái)運(yùn)行。

 

 

3.4 JUnit測(cè)試的組成

一個(gè)測(cè)試類包含一些測(cè)試方法;每個(gè)方法包含一個(gè)或者多個(gè)斷言語(yǔ)句。但是測(cè)試類也能調(diào)用其它測(cè)試類:?jiǎn)为?dú)的類、包、甚至完整的一個(gè)系統(tǒng)。可以通過(guò)創(chuàng)建test suite來(lái)取得。任何測(cè)試類都能包含一個(gè)名為suite的靜態(tài)方法。

public static Test suite();

你可以提供suite()方法來(lái)返回任何你想要的測(cè)試集合(沒有suite()方法,JUnit會(huì)自動(dòng)運(yùn)行所有的test…方法)。但是你可能需要手工添加特殊的測(cè)試,包括其他suite

 

 

· Per-methodSetupTear-down

JUnitTestCase基類提供兩個(gè)方法供你改寫,分別用于環(huán)境的建立和清理:

protected void setup();

protected void teardown();

 

 

· Per-suite SetupTear-down

一般而言,你只須針對(duì)每個(gè)方法設(shè)置運(yùn)行環(huán)境;但是在某些情況下,你須為整個(gè)test suite設(shè)置一些環(huán)境,以及在test suite中的所有方法都執(zhí)行完成后做一些清理工作。要達(dá)到這種效果,你需要per-suite setupper-suite teardown

Per-suitesetup要復(fù)雜一些。你需要提供所需測(cè)試的一個(gè)suite(無(wú)論通過(guò)什么樣的方式)并且把它包裝進(jìn)一個(gè)TestSetup對(duì)象。

注意你可以在同一個(gè)類中同時(shí)使用per-sutieper-testsetup()teardown

 

 

3.5 自定義JUnit斷言

如果你有需要在整個(gè)項(xiàng)目中共享的斷言或者公共代碼,你也許需要考慮從TestCase繼承一個(gè)類并且使用這個(gè)字類來(lái)進(jìn)行所有的測(cè)試。

事實(shí)上,開始新項(xiàng)目時(shí)總是從自己的自定義基類繼承而不直接從JUnit的類繼承通常是一個(gè)好主意——即便你的基類在一開始沒有添加任何額外的功能。這樣做的好處是當(dāng)你需要添加一個(gè)所有測(cè)試類都需要的方法或者能力時(shí),可以簡(jiǎn)單地編輯你的基類而不需要改動(dòng)項(xiàng)目中的所有test case

 

 

3.6 JUnit和異常

對(duì)于測(cè)試而言,下面兩種異常是我們可能會(huì)感興趣的:

1.  從測(cè)試代碼拋出的可預(yù)測(cè)異常。

2.  由于某個(gè)模塊(或代碼)發(fā)生嚴(yán)重錯(cuò)誤,而拋出的不可預(yù)測(cè)異常。

 

任何對(duì)assertTrue(true)的使用都應(yīng)該被翻譯為“我預(yù)期控制流程會(huì)達(dá)到這個(gè)地方”。這對(duì)將來(lái)可能的誤解來(lái)說(shuō)會(huì)起到強(qiáng)有力的文檔的作用。然而,不要忘記一個(gè)assertTrue(true)沒有被調(diào)用不會(huì)產(chǎn)生任何錯(cuò)誤的。

通常而言,對(duì)于方法中每個(gè)被期望的異常,你都應(yīng)該寫一個(gè)專門的測(cè)試來(lái)確認(rèn)該方法在應(yīng)該拋出異常的時(shí)候確實(shí)會(huì)拋出異常。

對(duì)于處于出乎意料的異常,你最好簡(jiǎn)單的改變你的測(cè)試方法的聲明讓它能拋出可能的異常。JUnit框架可以捕獲任何異常,并且把它報(bào)告為一個(gè)錯(cuò)誤,這些都不需要你的參與。

 

 

3.7 關(guān)于命名的更多說(shuō)明

如果編寫了一個(gè)測(cè)試,但是實(shí)現(xiàn)代碼還沒有準(zhǔn)備好,可以將以“test”打頭的測(cè)試方法名米功能為別的,譬如把“test”去掉,然后等準(zhǔn)備好了要來(lái)運(yùn)行測(cè)試的時(shí)候再改回來(lái)。

無(wú)論如何,你要避免養(yǎng)成忽略“失敗的測(cè)試結(jié)果”的習(xí)慣。

 

 

3.8 JUnit測(cè)試骨架

JUnit寫測(cè)試真正所需要的就三件事:

1.  一個(gè)import語(yǔ)句引入所有junit.framework.*下的類。

2.  一個(gè)extends語(yǔ)句讓你的類從TestCase繼承。

3.  一個(gè)調(diào)用super(string)的構(gòu)造函數(shù)。