隨著當前 Java 開發的越發成熟,Agile 和 TDD 的越發流行,自動化測試的呼聲也越來越高。若想將單元測試變得自動化,自然 JUnit 這把利器必不可少,這也是 JUnit 自 1997 年誕生以來在 Java 開發業界一直相當流行的原因。
JUnit 是針對 Java 語言的一個單元測試框架,它被認為是迄今為止所開發的最重要的第三方 Java 庫。 JUnit 的優點是整個測試過程無需人的參與,無需分析和判斷最終測試結果是否正確,而且可以很容易地一次性運行多個測試。 JUnit 的出現促進了測試的盛行,它使得 Java 代碼更健壯,更可靠,Bug 比以前更少。
JUnit 自從問世以來一直在不停的推出新版本,目前最新的版本是 2007 年 7 月發布的 JUnit 4.4,它是繼 JUnit4 以來最大的發行版,提供了很多有用的新特性。本文將假設讀者已經具有 JUnit 4 的使用經驗。
JUnit 4.4 概述
JUnit 設計的目的就是有效地抓住編程人員寫代碼的意圖,然後快速檢查他們的代碼是否與他們的意圖相匹配。 JUnit 發展至今,版本不停的翻新,但是所有版本都一致致力於解決一個問題,那就是如何發現編程人員的代碼意圖,並且如何使得編程人員更加容易地表達他們的代碼意圖。JUnit 4.4 也是為了如何能夠更好的達到這個目的而出現的。
JUnit 4.4 主要提供了以下三個大方面的新特性來更好的抓住編程人員的代碼意圖:
提供了新的斷言語法(Assertion syntax)——assertThat
提供了假設機制(Assumption)
提供了理論機制(Theory)
新的斷言語法(Assertion syntax)—— assertThat
JUnit 4.4 學習 JMock,引入了 Hamcrest 匹配機制,使得程序員在編寫單元測試的 assert 語句時,可以具有更強的可讀性,而且也更加靈活。
Hamcrest 是一個測試的框架,它提供了一套通用的匹配符 Matcher,靈活使用這些匹配符定義的規則,程序員可以更加精確的表達自己的測試思想,指定所想設定的測試條件。比如,有時候定義的測試數據范圍太精確,往往是若干個固定的確定值,這時會導致測試非常脆弱,因為接下來的測試數據只要稍稍有變化,就可能導致測試失敗(比如 assertEquals( x, 10 ); 只能判斷 x 是否等於 10,如果 x 不等於 10,測試失敗);有時候指定的測試數據范圍又不夠太精確,這時有可能會造成某些本該會導致測試不通過的數據,仍然會通過接下來的測試,這樣就會降低測試的價值。 Hamcrest 的出現,給程序員編寫測試用例提供了一套規則和方法,使用其可以更加精確的表達程序員所期望的測試的行為。(具體 Hamcrest 的使用,請參閱 參考資料)
JUnit 4.4 結合 Hamcrest 提供了一個全新的斷言語法——assertThat。程序員可以只使用 assertThat 一個斷言語句,結合 Hamcrest 提供的匹配符,就可以表達全部的測試思想。
assertThat 的基本語法如下:
清單 1 assertThat 基本語法
assertThat( [value], [matcher statement] );
value 是接下來想要測試的變量值;
matcher statement 是使用 Hamcrest 匹配符來表達的對前面變量所期望的值的聲明,如果 value 值與 matcher statement 所表達的期望值相符,則測試成功,否則測試失敗。
assertThat 的優點
優點 1:以前 JUnit 提供了很多的 assertion 語句,如:assertEquals,assertNotSame,assertFalse,assertTrue,assertNotNull,assertNull 等,現在有了 JUnit 4.4,一條 assertThat 即可以替代所有的 assertion 語句,這樣可以在所有的單元測試中只使用一個斷言方法,使得編寫測試用例變得簡單,代碼風格變得統一,測試代碼也更容易維護。
優點 2:assertThat 使用了 Hamcrest 的 Matcher 匹配符,用戶可以使用匹配符規定的匹配准則精確的指定一些想設定滿足的條件,具有很強的易讀性,而且使用起來更加靈活。如清單 2 所示:
清單 2 使用匹配符 Matcher 和不使用之間的比較
// 想判斷某個字符串 s 是否含有子字符串 "developer" 或 "Works" 中間的一個
// JUnit 4.4 以前的版本:assertTrue(s.indexOf("developer")>-1||s.indexOf("Works")>-1 );
// JUnit 4.4:
assertThat(s, anyOf(containsString("developer"), containsString("Works")));
// 匹配符 anyOf 表示任何一個條件滿足則成立,類似於邏輯或 "||", 匹配符 containsString 表示是否含有參數子
// 字符串,文章接下來會對匹配符進行具體介紹
優點 3:assertThat 不再像 assertEquals 那樣,使用比較難懂的“謂賓主”語法模式(如:assertEquals(3, x);),相反,assertThat 使用了類似於“主謂賓”的易讀語法模式(如:assertThat(x,is(3));),使得代碼更加直觀、易讀。
優點 4:可以將這些 Matcher 匹配符聯合起來靈活使用,達到更多目的。如清單 3 所示:
清單 3 Matcher 匹配符聯合使用
// 聯合匹配符not和equalTo表示“不等於”
assertThat( something, not( equalTo( "developer" ) ) );
// 聯合匹配符not和containsString表示“不包含子字符串”
assertThat( something, not( containsString( "Works" ) ) );
// 聯合匹配符anyOf和containsString表示“包含任何一個子字符串”
assertThat(something, anyOf(containsString("developer"), containsString("Works")));
優點 5:錯誤信息更加易懂、可讀且具有描述性(descriptive)。
JUnit 4.4 以前的版本默認出錯後不會拋出額外提示信息,如:
assertTrue( s.indexOf("developer") > -1 || s.indexOf("Works") > -1 );
如果該斷言出錯,只會拋出無用的錯誤信息,如:junit.framework.AssertionFailedError:null。
如果想在出錯時想打印出一些有用的提示信息,必須得程序員另外手動寫,如:assertTrue( "Expected a string containing 'developer' or 'Works'",
s.indexOf("developer") > -1 || s.indexOf("Works") > -1 );
非常的不方便,而且需要額外代碼。
JUnit 4.4 會默認自動提供一些可讀的描述信息,如清單 4 所示:
清單 4 JUnit 4.4 默認提供一些可讀的描述性錯誤信息
String s = "hello world!";
assertThat( s, anyOf( containsString("developer"), containsString("Works") ) );
// 如果出錯後,系統會自動拋出以下提示信息:
java.lang.AssertionError:
Expected: (a string containing "developer" or a string containing "Works")
got: "hello world!"
優點 6:開發人員可以通過實現 Matcher 接口,定制自己想要的匹配符。當開發人員發現自己的某些測試代碼在不同的測試中重復出現,經常被使用,這時用戶就可以自定義匹配符,將這些代碼綁定在一個斷言語句中,從而可以達到減少重復代碼並且更加易讀的目的。(具體怎麼實現自定義可配置的匹配符,請參閱參考資料)
如何使用 assertThat
JUnit 4.4 自帶了一些 Hamcrest 的匹配符 Matcher,但是只有有限的幾個,在類 org.hamcrest.CoreMatchers 中定義,要想使用他們,必須導入包 org.hamcrest.CoreMatchers.*。
如果想使用一些其他更多的匹配符 Matcher,可以從 Hamcrest 網頁下載 hamcrest-library-1.1.jar 和 hamcrest-core-1.1.jar(請參閱 參考資料),並將其加入到工程庫中,所有的匹配符都在類 org.hamcrest.Matchers 中定義,要想使用,必須得在代碼中 import static org.hamcrest.Matchers.*;。如果使用外部的匹配符,最好就不要再使用 JUnit 4.4 自帶的匹配符了,因為這樣容易導致匹配符 Matcher 重復定義,編譯可能會出錯(ambiguous for the type)。 JUnit 4.4 允許使用 Hamcrest 來使用更多的匹配符,這還是 JUnit 第一次允許在自己的工程中使用第三方類。
注意:
assertThat 仍然是斷言語句,所以要想使用,必須還得 import static org.junit.Assert.*;;
雖然 assertThat 可以代替以前所有的斷言語句,但是以前的所有 assert 語句仍然可以繼續使用;
清單 5 列舉了大部分 assertThat 的使用例子:
清單 5 assertThat 使用舉例
//一般匹配符
// allOf匹配符表明如果接下來的所有條件必須都成立測試才通過,相當於“與”(&&)
assertThat( testedNumber, allOf( greaterThan(8), lessThan(16) ) );
// anyOf匹配符表明如果接下來的所有條件只要有一個成立則測試通過,相當於“或”(||)
assertThat( testedNumber, anyOf( greaterThan(16), lessThan(8) ) );
// anything匹配符表明無論什麼條件,永遠為true
assertThat( testedNumber, anything() );
// is匹配符表明如果前面待測的object等於後面給出的object,則測試通過
assertThat( testedString, is( "developerWorks" ) );
// not匹配符和is匹配符正好相反,表明如果前面待測的object不等於後面給出的object,則測試通過
assertThat( testedString, not( "developerWorks" ) );
//字符串相關匹配符
// containsString匹配符表明如果測試的字符串testedString包含子字符串"developerWorks"則測試通過
assertThat( testedString, containsString( "developerWorks" ) );
// endsWith匹配符表明如果測試的字符串testedString以子字符串"developerWorks"結尾則測試通過
assertThat( testedString, endsWith( "developerWorks" ) );
// startsWith匹配符表明如果測試的字符串testedString以子字符串"developerWorks"開始則測試通過
assertThat( testedString, startsWith( "developerWorks" ) );
// equalTo匹配符表明如果測試的testedValue等於expectedValue則測試通過,equalTo可以測試數值之間,字
//符串之間和對象之間是否相等,相當於Object的equals方法
assertThat( testedValue, equalTo( expectedValue ) );
// equalToIgnoringCase匹配符表明如果測試的字符串testedString在忽略大小寫的情況下等於
//"developerWorks"則測試通過
assertThat( testedString, equalToIgnoringCase( "developerWorks" ) );
// equalToIgnoringWhiteSpace匹配符表明如果測試的字符串testedString在忽略頭尾的任意個空格的情況下等
//於"developerWorks"則測試通過,注意:字符串中的空格不能被忽略
assertThat( testedString, equalToIgnoringWhiteSpace( "developerWorks" ) );
//數值相關匹配符
// closeTo匹配符表明如果所測試的浮點型數testedDouble在20.0±0.5范圍之內則測試通過
assertThat( testedDouble, closeTo( 20.0, 0.5 ) );
// greaterThan匹配符表明如果所測試的數值testedNumber大於16.0則測試通過
assertThat( testedNumber, greaterThan(16.0) );
// lessThan匹配符表明如果所測試的數值testedNumber小於16.0則測試通過
assertThat( testedNumber, lessThan (16.0) );
// greaterThanOrEqualTo匹配符表明如果所測試的數值testedNumber大於等於16.0則測試通過
assertThat( testedNumber, greaterThanOrEqualTo (16.0) );
// lessThanOrEqualTo匹配符表明如果所測試的數值testedNumber小於等於16.0則測試通過
assertThat( testedNumber, lessThanOrEqualTo (16.0) );
//collection相關匹配符
// hasEntry匹配符表明如果測試的Map對象mapObject含有一個鍵值為"key"對應元素值為"value"的Entry項則
//測試通過
assertThat( mapObject, hasEntry( "key", "value" ) );
// hasItem匹配符表明如果測試的迭代對象iterableObject含有元素“element”項則測試通過
assertThat( iterableObject, hasItem ( "element" ) );
// hasKey匹配符表明如果測試的Map對象mapObject含有鍵值“key”則測試通過
assertThat( mapObject, hasKey ( "key" ) );
// hasValue匹配符表明如果測試的Map對象mapObject含有元素值“value”則測試通過
assertThat( mapObject, hasValue ( "key" ) );
假設機制(Assumption)
理想情況下,寫測試用例的開發人員可以明確的知道所有導致他們所寫的測試用例不通過的地方,但是有的時候,這些導致測試用例不通過的地方並不是很容易的被發現,可能隱藏得很深,從而導致開發人員在寫測試用例時很難預測到這些因素,而且往往這些因素並不是開發人員當初設計測試用例時真正目的,他們的測試點是希望測試出被測代碼中別的出錯地方。
比如,一個測試用例運行的 locale(如:Locale.US)與之前開發人員設計該測試用例時所設想的不同(如:Locale.UK),這樣會導致測試不通過,但是這可能並不是開發人員之前設計測試用例時所設想的測試出來的有用的失敗結果(測試點並不是此,比如測試的真正目的是想判斷函數的返回值是否為 true,返回 false 則測試失敗),這時開發人員可以通過編寫一些額外的代碼來消除這些影響(比如將 locale 作為參數傳入到測試用例中,每次運行測試用例時,明確指定 locale),但是花費時間和精力來編寫這些不是測試用例根本目的的額外代碼其實是種浪費,這時就可以使用 Assumption 假設機制來輕松達到額外代碼的目的。編寫該測試用例時,首先假設 locale 必須是 Locale.UK,如果運行時 locale 是 Locale.UK,則繼續執行該測試用例函數,如果是其它的 locale,則跳過該測試用例函數,執行該測試用例函數以外的代碼,這樣就不會因為 locale 的問題導致測試出錯。
JUnit 4.4 結合 Hamcrest 庫提供了 assumeThat 語句,開發人員可以使用其配合匹配符 Matcher 設計所有的假設條件(語法和 assertThat 一樣)。同樣為了方便使用,JUnit 4.4 還專門提供了 assumeTrue,assumeNotNull 和 assumeNoException 語句。
假設機制(Assumption)的優點
優點 1:通過對 runtime 變量進行取值假設,從而不會因為一個測試用例的不通過而導致整個測試失敗而中斷(the test passes),使得測試更加連貫。
開發人員編寫單元測試時,經常會在一個測試中包含若干個測試用例函數,這時若是遇到某個測試用例函數不通過,整個單元測試就會終止。這將導致測試不連貫,因為開發人員往往希望一次能運行多個測試用例函數,不通過的測試用例函數不要影響到剩下的測試用例函數的運行,否則會給 debug 調試帶來很大的難度。
開發人員編寫單元測試時,有時是預測不了傳入到單元測試方法中的變量值的,而且這些值有時並不是開發人員所期望的,因為他們會導致測試用例不通過並中斷整個測試,所以開發人員需要跳過這些導致測試用例函數不通過的異常情況。
清單 6 假設機制優點 1 舉例
//@Test 注釋表明接下來的函數是 JUnit4 及其以後版本的測試用例函數
@Test
public void testAssumptions() {
//假設進入testAssumptions時,變量i的值為10,如果該假設不滿足,程序不會執行assumeThat後面的語句
assumeThat( i, is(10) );
//如果之前的假設成立,會打印"assumption is true!"到控制台,否則直接調出,執行下一個測試用例函數
System.out.println( "assumption is true!" );
}
優點 2:利用假設可以控制某個測試用例的運行時間,讓其在自己期望的時候運行(run at a given time)。
清單 7 假設機制優點 2 舉例
@Test
//測試用例函數veryLongTest()執行需要很長時間,所以開發人員不是每次都想運行它,可以通過判斷是否定義了
//”DEV”環境變量來選擇性地執行該測試用例
public void veryLongTest() throws Exception {
//假設環境變量”DEV”為空,即如果之前通過System.setProperty定義過”DEV”環境變量(不為空),則自動跳過
//veryLongTest中假設後剩下的語句,去執行下一個JUnit測試用例,否則執行假設後接下來的語句
assumeThat( System.getProperty( "DEV" ), nullValue() );
System.out.println("running a long test");
Thread.sleep( 90 * 1000 );
}
如何使用 Assumption 假設機制
開發人員可以使用 assumeThat 並配合 hamcrest 的匹配符 Matcher,對即將被傳入到單元測試用例函數中的 runtime 變量值做精確的假設,如果假設不正確(即當前 runtime 變量的取值不滿足所假設的條件),則不會將該變量傳給該測試用例中假設後面的語句,即程序會從該 assumeThat 所在的 @Test 測試函數中直接自動跳出(test automatically quietly passes,values that violate assumptions are quietly ignored),去執行下一個 @Test 函數,使得本來會中斷的測試現在不會中斷。
使用假設機制必須得注意以下幾點:
由於 JUnit 4.4 引用了 Hamcrest 匹配符庫,所以使用 assumeThat 就可以編寫所有的假設語句。但是為了方便使用,JUnit 4.4 除 assumeThat 之外,還提供了 assumeTrue,assumeNotNull 和 assumeNoException 語句。
要使用 assume* 假設語句,必須得 import static org.junit.Assume.*;。
如果引用了第三方 hamcrest 的匹配符庫,必須得 import static org.hamcrest.Matchers.*;,如果引用 JUnit 4.4 自帶的匹配符庫,需要 import static org.hamcrest.CoreMatchers.*;。
清單 8 假設機制使用舉例
例1:
@Test
public void filenameIncludesString() {
//如果文件分隔符不是’/’(forward slash),則不執行assertThat斷言測試,直接跳過該測試用例函數
assumeThat(File.separatorChar, is('/'));
//判斷文件名fileName是否含有字符串"developerWorks"
assertThat( fileName, containsString( "developerWorks" ) );
}
例2:
@Test
public void filenameIncludesString() {
//bugFixed不是JUnit4.4的函數,是開發人員自己工程中定義的函數,表示判斷指定的defect是否
//被修正了,如果被修正,則返回true,否則返回false。這裡假設缺陷13356被修正後才進行余下單元測試
assumeTrue( bugFixed("13356") );
//判斷文件名fileName是否含有字符串"developerWorks"
assertThat( fileName, containsString( "developerWorks" ) );
}
理論機制(Theory)
為什麼要引用理論機制(Theory)
當今軟件開發中,測試驅動開發(TDD — Test-driven development)越發流行。為什麼 TDD 會如此流行呢?因為它確實擁有很多優點,它允許開發人員通過簡單的例子來指定和表明他們代碼的行為意圖。
TDD 的優點:
使得開發人員對即將編寫的軟件任務具有更清晰的認識,使得他們在思考如何編寫代碼之前先仔細思考如何設計軟件。
對測試開發人員所實現的代碼提供了快速和自動化的支持。
提供了一系列可以重用的回歸測試用例(regression test case),這些測試用例可以用來檢測未來添加的新代碼是否改變了以前系統定義的行為(測試代碼兼容性)。
然而,TDD 也同樣具有一定的局限性。對於開發人員來說,只用一些具體有限的簡單例子來表達程序的行為往往遠遠不夠。有很多代碼行為可以很容易而且精確的用語言來描述,卻很難用一些簡單的例子來表達清楚,因為他們需要大量的甚至無限的具體例子才可以達到被描述清楚的目的,而且有時有限的例子根本不能覆蓋所有的代碼行為。
以下列出的代碼行為反映了 TDD 的局限性:
將十進制整數轉換成羅馬數字,然後再將其轉換回十進制數,並保持原有的數值。(需要大量的測試用例,有限的測試數據可能測不出所實現的代碼的錯誤)。
對一個對象進行操作,希望結果仍然等於原來的對象。(需要考慮各種各樣類型的對象)
在任何一個貨幣的 collection 中添加一個對象 dollar,需要產生出另外一個新的與以前不同的 collection 。(需要考慮所有的 collection 類型的對象)。
理論(Theory)的出現就是為了解決 TDD 這個問題。 TDD 為組織規劃開發流程提供了一個方法,先用一些具體的例子(測試用例 test case)來描述系統代碼的行為,然後再將這些行為用代碼語句進行概括性的總的陳述(代碼實現 implementation)。而 Theory 就是對傳統的 TDD 進行一個延伸和擴展,它使得開發人員從開始的定義測試用例的階段就可以通過參數集(理論上是無限個參數)對代碼行為進行概括性的總的陳述,我們叫這些陳述為理論。理論就是對那些需要無窮個測試用例才能正確描述的代碼行為的概括性陳述。結合理論(Theory)和測試一起,可以輕松的描述代碼的行為並發現 BUG 。開發人員都知道他們代碼所想要實現的概括性的總的目的,理論使得他們只需要在一個地方就可以快速的指定這些目的,而不要將這些目的翻譯成大量的獨立的測試用例。
理論機制的優點
優點 1:理論(Theory)使得開發完全抽象的接口(Interface)更加容易。
優點 2:理論仍然可以重用以前的測試用例,因為以前的許多傳統的具體的測試用例仍然可以被輕松的改寫成理論(Theory)測試實例。
優點 3:理論(Theory)可以測試出一些原本測試用例沒測出來的 bugs 。
優點 4:理論允許配合自動化測試工具進行使用,自動化工具通過大量的數據點來測試一個理論,從而可以放大增強理論的效果。利用自動化工具來分析代碼,找出可以證明理論錯誤的值。
下面通過一個簡單的例子來逐步介紹理論的優點。
比如設計一個專門用來貨幣計算的計算器,首先需要給代碼行為編寫測試用例(這裡以英鎊 Pound 的乘法為例),如清單 9 所示:
清單 9 英鎊 Pound 乘法的一個測試用例
@Test
public void multiplyPoundsByInteger() {
assertEquals( 10, new Pound(5).times(2).getAmount() );
}
這時很自然的就會想到一個測試用例可能不夠,需要再多一個,如清單 10 所示:
清單 10 英鎊 Pound 乘法的兩個測試用例
@Test
public void multiplyPoundsByInteger () {
assertEquals( 10, new Pound(5).times(2).getAmount() );
assertEquals( 15, new Pound(5).times(3).getAmount() );
}
但是此時您可能又會發現這兩個測試用例還是很有限,您所希望的是測試所有的整數,而不只是 2,3 和 5,這些只是您所想要的測試的數據的子集,兩個測試用例並不能完全與您所想要測試的代碼的行為相等價,您需要更多的測試用例,此時就會發現需要很多的額外工作來編寫這些測試用例,更可怕的是,您會發現您需要測試用例的並不只是簡單的幾個,可能是成千上萬個甚至無窮個測試用例才能滿足等價您的代碼行為的目的。
很自然的,您會想到用清單 11 所示的代碼來表達您的測試思想。
清單 11 使用變量輔助編寫測試用例
//利用變量來代替具體數據表達測試思想
public void multiplyAnyAmountByInteger(int amount, int multiplier) {
assertEquals( amount * multiplier,
new Pound( amount ).times( multiplier ).getAmount() );
}
利用清單 11 的 multiplyAnyAmountByInteger 方法,可以輕松將測試用例改寫成如清單 12 所示:
清單 12 改寫的英鎊 Pound 乘法的測試用例
@Test
public void multiplyPoundsByInteger () {
multiplyAnyAmountByInteger(5, 2);
multiplyAnyAmountByInteger(5, 3);
}
如清單 12 所示,以後若想增加測試用例,只要不停調用 multiplyAnyAmountByInteger 方法並賦予參數值即可。
方法 multiplyAnyAmountByInteger 就是一個理論的簡單例子,理論就是一個帶有參數的方法,其行為就是對任何參數都是正常的返回,不會拋出斷言錯誤和其它異常。理論就是對一組數據進行概括性的陳述,就像一個科學理論一樣,如果沒有對所有可能出現的情況都進行實驗,是不能證明該理論是正確的,但是只要有一種錯誤情況出現,該理論就不成立。相反地,一個測試就是對一個單獨數據的單獨陳述,就像是一個科學理論的實驗一樣。
如何使用理論機制
在 JUnit 4.4 的理論機制中,每個測試方法不再是由注釋 @Test 指定的無參測試函數,而是由注釋 @Theory 指定的帶參數的測試函數,這些參數來自一個數據集(data sets),數據集通過注釋 @DataPoint 指定。
JUnit 4.4 會自動將數據集中定義的數據類型和理論測試方法定義的參數類型進行比較,如果類型相同,會將數據集中的數據通過參數一一傳入到測試方法中。數據集中的每一個數據都會被傳入到每個相同類型的參數中。這時有人會問了,如果參數有多個,而且類型都和數據集中定義的數據相同,怎麼辦?答案是,JUnit 4.4 會將這些數據集中的數據進行一一配對組合(所有的組合情況都會被考慮到),然後將這些數據組合統統通過參數,一一傳入到理論的測試方法中,但是用戶可以通過假設機制(assumption)在斷言函數(assertion)執行這些參數之前,對這些通過參數傳進來的數據集中的數據進行限制和過濾,達到有目的地部分地將自己想要的參數傳給斷言函數(assertion)來測試。只有滿足所有假設的數據才會執行接下來的測試用例,任何一個假設不滿足的數據,都會自動跳過該理論測試函數(假設 assumption 不滿足的數據會被忽略,不再執行接下來的斷言測試),如果所有的假設都滿足,測試用例斷言函數不通過才代表著該理論測試不通過。
清單 13 理論機制舉例
import static org.hamcrest.Matchers.*; //指定接下來要使用的Matcher匹配符
import static org.junit.Assume.*; //指定需要使用假設assume*來輔助理論Theory
import static org.junit.Assert.*; //指定需要使用斷言assert*來判斷測試是否通過
import org.junit.experimental.theories.DataPoint; //需要使用注釋@DataPoint來指定數據集
import org.junit.experimental.theories.Theories; //接下來@RunWith要指定Theories.class
import org.junit.experimental.theories.Theory; //注釋@Theory指定理論的測試函數
import org.junit.runner.RunWith; //需要使用@RunWith指定接下來運行測試的類
import org.junit.Test;
//注意:必須得使用@RunWith指定Theories.class
@RunWith(Theories.class)
public class TheoryTest {
//利用注釋@DataPoint來指定一組數據集,這些數據集中的數據用來證明或反駁接下來定義的Theory理論,
//testNames1和testNames2這兩個理論Theory測試函數的參數都是String,所以Junit4.4會將這5個
//@DataPoint定義的String進行兩兩組合,統統一一傳入到testNames1和testNames2中,所以參數名year
//和name是不起任何作用的,"2007"同樣有機會會傳給參數name,"Works"也同樣有機會傳給參數year
@DataPoint public static String YEAR_2007 = "2007";
@DataPoint public static String YEAR_2008 = "2008";
@DataPoint public static String NAME1 = "developer";
@DataPoint public static String NAME2 = "Works";
@DataPoint public static String NAME3 = "developerWorks";
//注意:使用@Theory來指定測試函數,而不是@Test
@Theory
public void testNames1( String year, String name ) {
assumeThat( year, is("2007") ); //year必須是"2007",否則跳過該測試函數
System.out.println( year + "-" + name );
assertThat( year, is("2007") ); //這裡的斷言語句沒有實際意義,這裡舉此例只是為了不中斷測試
}
//注意:使用@Theory來指定測試函數,而不是@Test
@Theory
public void testNames2( String year, String name ) {
assumeThat(year, is("2007")); //year必須是"2007",否則跳過該測試函數
//name必須既不是"2007"也不是"2008",否則跳過該測試函數
assumeThat(name, allOf( not(is("2007")), not(is("2008"))));
System.out.println( year + "-" + name );
assertThat( year, is("2007") ); //這裡的斷言語句沒有實際意義,這裡舉此例只是為了不中斷測試
}
結果輸出:
第一個Theory打印出:
2007-2007
2007-2008
2007-developer
2007-Works
2007-developerWorks
第二個Theory打印出:
2007-developer
2007-Works
2007-developerWorks
結束語
本文通過詳細深入的理論介紹和簡單易懂的實例全面剖析了 JUnit 4.4 的三個新特性:
提供了新的斷言語法(assertion syntax)——assertThat
提供了假設機制(assumptions)
提供了理論機制(Theories)
相信讀者看完後一定會對 JUnit 4.4 有著非常深入的了解並可以輕松將其運用到自己的開發工程中。