單元測試是編寫測試代碼,用來檢測特定的、明确的、細顆粒的功能。單元測試并不一定保證程式功能是正确的,更不保證整體業務是準備的。
單元測試不僅僅用來保證目前代碼的正确性,更重要的是用來保證代碼修複、改進或重構之後的正确性。
一般來說,單元測試任務包括
接口功能測試:用來保證接口功能的正确性。
局部資料結構測試(不常用):用來保證接口中的資料結構是正确的
比如變量有無初始值
變量是否溢出
邊界條件測試
變量沒有指派(即為null)
變量是數值(或字元)
主要邊界:最小值,最大值,無窮大(對于double等)
溢出邊界(期望異常或拒絕服務):最小值-1,最大值+1
臨近邊界:最小值+1,最大值-1
變量是字元串
引用“字元變量”的邊界
空字元串
對字元串長度應用“數值變量”的邊界
變量是集合
空集合
對集合的大小應用“數值變量”的邊界
調整次序:升序、降序
變量有規律
比如對于math.sqrt,給出n^2-1,和n^2+1的邊界
所有獨立執行通路測試:保證每一條代碼,每個分支都經過測試
代碼覆寫率
語句覆寫:保證每一個語句都執行到了
判定覆寫(分支覆寫):保證每一個分支都執行到
條件覆寫:保證每一個條件都覆寫到true和false(即if、while中的條件語句)
路徑覆寫:保證每一個路徑都覆寫到
相關軟體
cobertura:語句覆寫
emma: eclipse插件eclemma
各條錯誤處理通路測試:保證每一個異常都經過測試
junit是java單元測試架構,已經在eclipse中預設安裝。目前主流的有junit3和junit4。junit3中,測試用例需要繼承<code>testcase</code> 類。junit4中,測試用例無需繼承 <code>testcase</code> 類,隻需要使用@test等注解。
如果采用預設的testsuite,則測試方法必須是 <code>public void testxxx() [throws exception] {}</code> 的形式,并且不能存在依賴關系,因為測試方法的調用順序是不可預知的。
上例執行後,控制台會輸出
從中,可以猜測到,對于每個測試方法,調用的形式是:
運作測試方法
在eclipse中,可以直接在類名或測試方法上右擊,在彈出的右擊菜單中選擇 <code>run as -> junit test。</code>
在 maven 中,可以直接通過 <code>mvn test</code> 指令運作測試用例。
也可以通過java方式調用,建立一個 <code>testcase</code> 執行個體,然後重載 <code>runtest()</code> 方法,在其方法内調用測試方法(可以多個)。
更加便捷地,可以在建立 <code>testcase</code> 執行個體時直接傳入測試方法名稱,junit會自動調用此測試方法,如
junit testsuite
testsuite是測試用例套件,能夠運作過個測試方法。如果不指定testsuite,會建立一個預設的testsuite。預設testsuite會掃描目前内中的所有測試方法,然後運作。
如果不想采用預設的testsuite,則可以自定義testsuite。在testcase中,可以通過靜态方法 <code>suite()</code> 傳回自定義的suite。
允許上述方法,控制台輸出
并且隻運作了testmathpow測試方法,而沒有運作testmathmin測試方法。通過顯式指定測試方法,可以控制測試執行的順序。
也可以通過java的方式建立testsuite,然後調用 <code>testcase</code>,如:
testresult中儲存了很多測試資料,包括運作測試方法數目(runcount)等。
與junit3不同,junit4通過注解的方式來識别測試方法。目前支援的主要注解有:
<code>@beforeclass</code> 全局隻會執行一次,而且是第一個運作
<code>@before</code> 在測試方法運作之前運作
<code>@test</code> 測試方法
<code>@after</code> 在測試方法運作之後允許
<code>@afterclass</code> 全局隻會執行一次,而且是最後一個運作
<code>@ignore</code> 忽略此方法
下面舉一個樣例:
如果細心的話,會發現junit3的package是<code>junit.framework</code>,而junit4是<code>org.junit</code>。
執行此用例後,控制台會輸出
可以看到,執行次序是<code>@beforeclass -> @before -> @test -> @after -> @before -> @test -> @after ->@afterclass</code>。<code>@ignore</code>會被忽略。
與junit3類似,可以在eclipse中運作,也可以通過 <code>mvn test</code> 指令運作。
junit3和junit4都提供了一個assert類(雖然package不同,但是大緻差不多)。assert類中定義了很多靜态方法來進行斷言。清單如下:
asserttrue(string message, boolean condition) 要求condition == true
assertfalse(string message, boolean condition) 要求condition == false
fail(string message) 必然失敗,同樣要求代碼不可達
assertequals(string message, xxx expected,xxx actual) 要求expected.equals(actual)
assertarrayequals(string message, xxx[] expecteds,xxx [] actuals) 要求expected.equalsarray(actual)
assertnotnull(string message, object object) 要求object!=null
assertnull(string message, object object) 要求object==null
assertsame(string message, object expected, object actual) 要求expected == actual
assertnotsame(string message, object unexpected,object actual) 要求expected != actual
assertthat(string reason, t actual, matcher matcher) 要求matcher.matches(actual) == true
mock和stub是兩種測試代碼功能的方法。mock測重于對功能的模拟。stub測重于對功能的測試重制。比如對于list接口,mock會直接對list進行模拟,而stub會建立一個實作了list的testlist,在其中編寫測試的代碼。
強烈建議優先選擇mock方式,因為mock方式下,模拟代碼與測試代碼放在一起,易讀性好,而且擴充性、靈活性都比stub好。
比較流行的mock有:
<a href="http://jmock.org/">jmock</a>
<a href="http://www.easymock.org/">easymock</a>
<a href="http://blog.thihy.info/post/mockito.googlecode.com">mockito</a>
<a href="http://code.google.com/p/powermock/">powermock</a>
其中easymock和mockito對于java接口使用接口代理的方式來模拟,對于java類使用繼承的方式來模拟(也即會建立一個新的class類)。mockito支援spy方式,可以對執行個體進行模拟。但它們都不能對靜态方法和final類進行模拟,powermock通過修改位元組碼來支援了此功能。
easymock把測試過程分為三步:錄制、運作測試代碼、驗證期望。
錄制過程大概就是:期望method(params)執行times次(預設一次),傳回result(可選),抛出exception異常(可選)。
驗證期望過程将會檢查方法的調用次數。
一個簡單的樣例是:
easymock還支援嚴格的檢查,要求執行的方法次序與期望的完全一緻。
這裡從官方樣例中摘錄幾個典型的:
驗證調用行為
對mock對象進行stub
比較流行的工具是emma和jacoco,ecliplse插件有eclemma。eclemma2.0之前采用的是emma,之後采用的是jacoco。這裡主要介紹一下jacoco。eclmama由于是eclipse插件,是以非常易用,就不多做介紹了。
jacoco可以嵌入到ant、maven中,也可以使用java agent技術監控任意java程式,也可以使用java api來定制功能。
jacoco會監控jvm中的調用,生成監控結果(預設儲存在jacoco.exec檔案中),然後分析此結果,配合源代碼生成覆寫率報告。需要注意的是:監控和分析這兩步,必須使用相同的class檔案,否則由于class不同,而無法定位到具體的方法,導緻覆寫率均為0%。
java report
可以使用ant、mvn或eclipse來分析jacoco.exec檔案,也可以通過api來分析。