junit:junit_简而言之,JUnit:测试结构
junit:junit
盡管存在有關JUnit測試的書籍和文章,但我仍然經常遇到程序員,他們至多對這個工具及其正確用法都不甚了解。 因此,我想到了編寫多部分教程的想法,從我的角度解釋了要點。
也許在本微型系列文章中采用的動手方法可能適合使一兩個額外的開發人員對單元測試感興趣,這將使工作值得。
上次我介紹了測試的基本知識–測試的編寫,執行和評估方式。 在這樣做的同時,我概述了測試不僅僅是一個簡單的驗證機,而且還可以用作一種低級規范。 因此,應該以人們可能想到的最高編碼標準來開發它。
這篇文章將繼續本教程的示例,并使用Meszaros在xUnit Test Patterns [MES]中定義的命名法,得出表征良好編寫的單元測試的通用結構。
測試的四個階段
整潔的房子,整潔的頭腦
老格言
本教程的示例是關于編寫一個簡單的數字范圍計數器,該計數器從給定值開始提供一定數量的連續整數。 從快樂的路徑開始,最后一個帖子的結果是一個測試,該測試已驗證, NumberRangeCounter在后續調用next方法時返回連續數字:
@Testpublic void subsequentNumber() { NumberRangeCounter counter = new NumberRangeCounter();int first = counter.next();int second = counter.next();assertEquals( first + 1, second );}請注意,本章將堅持使用JUnit內置功能進行驗證。 我將在另一篇文章中介紹特定匹配器庫( Hamcrest , AssertJ )的優缺點。
細心的讀者可能已經注意到,我使用空行將測試分為不同的部分,并且可能想知道為什么。 為了回答這個問題,讓我們更仔細地研究三個部分:
這種測試結構非常普遍,并已被多位作者描述。 它被標記為排列,執行,聲明 [KAC] –或構建,操作,檢查 [MAR2] –模式。 但是,對于本教程,我想精確一點并堅持使用Meszaros的[MES]這四個階段,分別是 設置(1),練習(2),驗證(3)和拆卸(4) 。
普通單元測試很少使用持久性夾具??,因此拆卸階段(如我們的示例所示)通常被省略。 而且,由于它與規范角度完全無關,因此無論如何我們都希望將其排除在測試方法之外。 一分鐘內將介紹如何實現此目標。
由于這篇文章的范圍,我避免了單元測試的精確定義。 但是,我堅持Tomek Kaczanowski在使用JUnit和Mockito進行實用單元測試中描述的三種類型的開發人員測試 ,可以概括為:
- 單元測試可確保您的代碼正常運行,并且必須經常運行,因此運行速度非常快。 基本上,這就是本教程的全部內容。
- 集成測試關注于不同模塊的正確集成,包括開發人員無法控制的代碼。 這通常需要一些資源(例如數據庫,文件系統),因此測試運行速度較慢。
- 端到端測試從客戶端的角度驗證您的代碼是否有效,并將系統作為一個整體進行測試,以模仿用戶的使用方式。 他們通常需要大量時間才能執行自己。
- 對于如何有效地組合這些測試類型的深入示例,您可以看看Steve Freeman和Nat Pryce的 Tests指導的Growinging Oriented Oriented Software 。
但是在繼續進行示例之前,還有一個問題需要討論:
為什么這很重要?
閱讀(代碼)與寫作所花費的時間比例遠遠超過10:1…
羅伯特·C·馬丁,清潔法規
四個階段模式的目的是使您易于理解測試正在驗證的行為。 安裝程序總是定義測試的前提條件,練習實際上會調用測試的行為,驗證是否指定了預期的結果,而拆除工作完全與內部維護有關,正如梅薩羅斯(Meszaros)所說的那樣。
這種干凈的相分離清楚地表明了單個測試的意圖,并提高了可讀性。 該方法意味著測試一次只驗證給定輸入狀態的一種行為,因此通常沒有條件塊等(單條件測試)。
試圖避免繁瑣的夾具設置并在單一方法中測試盡可能多的功能雖然很誘人,但這通常會導致某種性質的混淆 。 因此,請始終記住:如果不小心編寫測試,可能會在維護和進步方面帶來痛苦。
但是現在是時候進行示例了,看看這種新知識可以為我們做什么!
角落案例測試
完成快樂路徑測試后,我們將繼續指定極端情況行為。 對數字范圍計數器的描述指出,數字序列應從給定值開始。 這一點很重要,因為它定義了計數器范圍的下限(一個角…)。
將該值作為配置參數傳遞給NumberRangeCounter的構造函數似乎很合理。 適當的測試可以驗證next返回的第一個數字是否等于此初始化:
@Testpublic void lowerBound() {NumberRangeCounter counter = new NumberRangeCounter( 1000 );int actual = counter.next();assertEquals( 1000, actual );}再次,我們的測試類不會編譯。 通過將lowerBound參數引入計數器的構造函數來解決此問題,則會在subsequentNumber測試中導致編譯錯誤。 幸運的是,后一個測試被編寫為獨立于下限定義,因此該測試的夾具也可以使用該參數。
但是,測試中的原義數字是多余的,沒有明確指出其目的。 后者通常表示為幻數 。 為了改善這種情況,我們可以引入一個常量LOWER_BOUND并替換所有文字值。 以下是測試類的樣子:
public class NumberRangeCounterTest {private static final int LOWER_BOUND = 1000;@Testpublic void subsequentNumber() {NumberRangeCounter counter = new NumberRangeCounter( LOWER_BOUND );int first = counter.next();int second = counter.next();assertEquals( first + 1, second );}@Testpublic void lowerBound() {NumberRangeCounter counter = new NumberRangeCounter( LOWER_BOUND );int actual = counter.next();assertEquals( LOWER_BOUND, actual );} }查看代碼,您可能會注意到夾具的在線設置對于兩種測試都是相同的。 通常,內聯設置由多個語句組成,但是測試之間通常存在共同點。 為了避免冗余,可以將共同之處委托給設置方法:
public class NumberRangeCounterTest {private static final int LOWER_BOUND = 1000;@Testpublic void subsequentNumber() {NumberRangeCounter counter = setUp();int first = counter.next();int second = counter.next();assertEquals( first + 1, second );}@Testpublic void lowerBound() {NumberRangeCounter counter = setUp();int actual = counter.next();assertEquals( LOWER_BOUND, actual );}private NumberRangeCounter setUp() {return new NumberRangeCounter( LOWER_BOUND );} }如果委托設置方法可以提高給定情況的可讀性,這是有爭議的,但它會導致JUnit的一個有趣功能:可以隱式執行公共測試設置。 這可以通過將@Before注釋應用于不帶返回值和參數的公共非靜態方法來實現。
這意味著此功能是有代價的。 如果要消除測試中的多余setUp調用,則必須引入一個采用NumberRangeCounter實例的字段:
public class NumberRangeCounterTest {private static final int LOWER_BOUND = 1000;private NumberRangeCounter counter;@Beforepublic void setUp() {counter = new NumberRangeCounter( LOWER_BOUND );}@Testpublic void subsequentNumber() {int first = counter.next();int second = counter.next();assertEquals( first + 1, second );}@Testpublic void lowerBound() {int actual = counter.next();assertEquals( LOWER_BOUND, actual );} }顯而易見, 隱式設置可以消除很多代碼重復。 但是從測試的角度來看,它也引入了一種魔術,這會使閱讀變得困難。 因此,對于“我應該使用哪種設置類型?”這個問題,答案很明確。 是:這取決于…
由于我通常會注意保持較小的單元/測試,因此折衷似乎可以接受。 因此,我經常使用隱式設置來定義公共/快樂路徑輸入,并為每個極端案例測試通過小的內聯/代理設置相應地對其進行補充。 否則,由于特別是初學者傾向于讓測試變得更大,因此最好堅持使用內聯和委托設置。
JUnit運行時確保在測試類的新實例上調用每個測試。 這意味著在我們的示例中,僅構造函數的燈具可以完全省略setUp方法。 該轉讓counter用新鮮的夾具字段可以隱式進行:
private NumberRangeCounter counter = new NumberRangeCounter( LOWER_BOUND );雖然有些人@Before使用它,但其他人則認為@Before注釋方法會使意圖更加明確。 好吧,我不會就此進行戰爭,讓您自己決定的決定……
隱式拆解
想象一下,無論出于何種原因都需要處理NumberRangeCounter 。 這意味著我們必須在測試中添加拆卸階段。 根據我們的最新代碼片段,使用JUnit可以輕松實現,因為它支持使用@After注釋進行隱式拆卸 。 我們只需要添加以下方法:
@Afterpublic void tearDown() {counter.dispose();}如上所述,拆解全部與內部管理有關,完全不向特定測試添加任何信息。 因此,隱式執行此操作通常很方便。 或者,即使測試失敗,也必須使用try-finally結構來處理,以確保執行拆解。 但是后者通常不會提高可讀性。
預期的例外
一個特殊的極端情況是測試預期的異常。 出于示例考慮,如果next的調用超出給定范圍的值量,則NumberRangeCalculator應該拋出IllegalStateException 。 同樣,通過構造函數參數配置范圍可能是合理的。 使用try-catch構造,我們可以編寫:
@Testpublic void exeedsRange() {NumberRangeCounter counter = new NumberRangeCounter( LOWER_BOUND, 0 );try {counter.next();fail();} catch( IllegalStateException expected ) {}}好吧,這看起來有些丑陋,因為它模糊了測試階段的分離,并且不是很可讀。 但是由于Assert.fail()會引發AssertionError因此可以確保在沒有引發異常的情況下測試失敗。 并且catch塊可確保在拋出預期異常的情況下成功完成測試。
使用Java 8,可以使用lambda表達式編寫結構清晰的異常測試。 有關更多信息,請參閱
使用Java 8 Lambdas清潔JUnit Throwable-Tests 。
如果足以驗證是否已拋出某種類型的異常,則JUnit通過@Test批注的expected方法提供隱式驗證 。 上面的測試可以寫成:
@Test( expected = IllegalStateException.class )public void exeedsRange() {new NumberRangeCounter( LOWER_BOUND, ZERO_RANGE ).next();}盡管此方法非常緊湊,但也很危險。 這是因為不能區分是在測試的設置階段還是在測試的執行階段拋出了給定的異常。 因此,如果構造函數意外IllegalStateException則測試將是綠色的,因此毫無價值。
JUnit為更清晰地測試預期異常提供了第三種可能性,即ExpectedException規則。 由于我們還沒有涵蓋規則 ,并且該方法有點扭曲了四個階段的結構,因此我將對該主題的明確討論推遲到有關規則和運行者的后續文章上,并且僅提供摘要作為預告片:
public class NumberRangeCounterTest {private static final int LOWER_BOUND = 1000; @Rulepublic ExpectedException thrown = ExpectedException.none();@Testpublic void exeedsRange() {thrown.expect( IllegalStateException.class );new NumberRangeCounter( LOWER_BOUND, 0 ).next();}[...] }但是,如果您不想等待,可以在Rafa?Borowiec的 《 JUNIT EXPECTEDEXCEPTION RULE:BEYOND BASICS 》一文中詳細了解一下。
結論
簡而言之,JUnit的這一章解釋了通常用于編寫單元測試的四個階段結構-設置,練習,驗證和拆卸。 它描述了每個階段的目的,并著重強調了在連續使用時如何提高測試用例的可讀性。 該示例在極端案例測試的上下文中加深了該學習材料。 希望它具有足夠的平衡性,可以提供容易理解的介紹而又不瑣碎。 改進建議當然受到高度贊賞。
本教程的下一章將繼續該示例,并介紹如何處理單元依賴性和測試隔離,敬請關注。
參考資料
- [MES] xUnit測試模式,第19章,四階段測試,Gerard Meszaros,2007年
- [MAR1]清潔代碼,第9章:單元測試,第130頁及以下,Robert C. Martin,2009年
- [KAC]使用JUnit和Mockito進行的實用單元測試,3.9。 單元測試的階段,Tomek Kaczanowski,2013年
- [MAR2]清潔代碼,第9章:單元測試,第127頁,Robert C. Martin,2009年
翻譯自: https://www.javacodegeeks.com/2014/08/junit-in-a-nutshell-test-structure.html
junit:junit
總結
以上是生活随笔為你收集整理的junit:junit_简而言之,JUnit:测试结构的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 魔术贴不粘了小窍门 魔术贴不粘如何解决
- 下一篇: 手机什么时候买便宜