junit单元测试断言_简而言之,JUnit:单元测试断言
junit單元測試斷言
簡而言之,本章涵蓋了各種單元測試聲明技術(shù)。 它詳細(xì)說明了內(nèi)置機(jī)制, Hamcrest匹配器和AssertJ斷言的優(yōu)缺點 。 正在進(jìn)行的示例擴(kuò)大了主題,并說明了如何創(chuàng)建和使用自定義匹配器/斷言。
單元測試斷言
信任但要驗證
羅納德·里根(Ronald Reagan)
后期測試結(jié)構(gòu)解釋了為什么單元測試通常分階段進(jìn)行。 它澄清說, 真正的測試即結(jié)果驗證在第三階段進(jìn)行。 但是到目前為止,我們只看到了一些簡單的示例,主要使用了JUnit的內(nèi)置機(jī)制。
如Hello World所示,驗證基于錯誤類型AssertionError 。 這是編寫所謂的自檢測試的基礎(chǔ)。 單元測試斷言將謂詞評估為true或false 。 如果為false ,則拋出AssertionError 。 JUnit運行時捕獲此錯誤并將測試報告為失敗。
以下各節(jié)將介紹三種較流行的單元測試斷言變體。
斷言
JUnit的內(nèi)置斷言機(jī)制由類org.junit.Assert 。 它提供了兩種靜態(tài)方法來簡化測試驗證。 以下代碼片段概述了可用方法模式的用法:
fail(); fail( "Houston, We've Got a Problem." );assertNull( actual ); assertNull( "Identifier must not be null.",actual );assertTrue( counter.hasNext() ); assertTrue( "Counter should have a successor.",counter.hasNext() );assertEquals( LOWER_BOUND, actual ); assertEquals( "Number should be lower bound value.", LOWER_BOUND,actual );所有這些類型的方法都提供帶有String參數(shù)的重載版本。 如果發(fā)生故障,此參數(shù)將合并到斷言錯誤消息中。 許多人認(rèn)為這有助于更清楚地指定失敗原因。 其他人則認(rèn)為此類消息混亂,使測試更難閱讀。
乍一看,這種單元測試斷言似乎很直觀。 這就是為什么我在前面的章節(jié)中使用它進(jìn)行入門的原因。 此外,它仍然非常流行,并且工具很好地支持故障報告。 但是,在需要更復(fù)雜的謂詞的斷言的表達(dá)性方面也受到一定限制。
Hamcrest
Hamcrest是一個旨在提供用于創(chuàng)建靈活的意圖表達(dá)的API的庫。 該實用程序提供了稱為Matcher的可嵌套謂詞。 這些允許以某種方式編寫復(fù)雜的驗證條件,許多開發(fā)人員認(rèn)為比布爾運算符更易于閱讀。
MatcherAssert類支持單元測試斷言。 為此,它提供了靜態(tài)的assertThat(T, Matcher )方法。 傳遞的第一個參數(shù)是要驗證的值或?qū)ο蟆?第二個謂詞用于評估第一個謂詞。
assertThat( actual, equalTo( IN_RANGE_NUMBER ) );如您所見,匹配器方法模仿自然語言的流程以提高可讀性。 以下代碼片段更加清楚了此意圖。 這使用is(Matcher )方法來修飾實際的表達(dá)式。
assertThat( actual, is( equalTo( IN_RANGE_NUMBER ) ) );MatcherAssert.assertThat(...)存在另外兩個簽名。 首先,有一個采用布爾參數(shù)而不是Matcher參數(shù)的變量。 它的行為與Assert.assertTrue(boolean) 。
第二個變體將一個附加的String傳遞給該方法。 這可以用來提高故障消息的表達(dá)能力:
assertThat( "Actual number must not be equals to lower bound value.", actual, is( not( equalTo( LOWER_BOUND ) ) ) );在失敗的情況下,給定驗證的錯誤消息如下所示:
Hamcrest帶有一組有用的匹配器。 圖書館在線文檔的“常見匹配項”部分中列出了最重要的部分。 但是對于特定于域的問題,如果有合適的匹配器,通常可以提高單元測試斷言的可讀性。
因此,該庫允許編寫自定義匹配器。
讓我們返回教程的示例來討論該主題。 首先,我們對該場景進(jìn)行調(diào)整以使其更合理。 假設(shè)NumberRangeCounter.next()返回的是RangeNumber類型,而不是簡單的int值:
public class RangeNumber {private final String rangeIdentifier;private final int value;RangeNumber( String rangeIdentifier, int value ) {this.rangeIdentifier = rangeIdentifier;this.value = value;}public String getRangeIdentifier() {return rangeIdentifier;}public int getValue() {return value;} }我們可以使用自定義匹配器來檢查NumberRangeCounter#next()的返回值是否在計數(shù)器的定義數(shù)字范圍內(nèi):
RangeNumber actual = counter.next();assertThat( actual, is( inRangeOf( LOWER_BOUND, RANGE ) ) );適當(dāng)?shù)淖远x匹配器可以擴(kuò)展抽象類TypeSafeMatcher<T> 。 該基類處理null檢查和類型安全。 可能的實現(xiàn)如下所示。 請注意如何添加工廠方法inRangeOf(int,int)以便于使用:
public class InRangeMatcher extends TypeSafeMatcher<RangeNumber> {private final int lowerBound;private final int upperBound;InRangeMatcher( int lowerBound, int range ) {this.lowerBound = lowerBound;this.upperBound = lowerBound + range;}@Overridepublic void describeTo( Description description ) {String text = format( "between <%s> and <%s>.", lowerBound, upperBound );description.appendText( text );}@Overrideprotected void describeMismatchSafely(RangeNumber item, Description description ){description.appendText( "was " ).appendValue( item.getValue() );}@Overrideprotected boolean matchesSafely( RangeNumber toMatch ) {return lowerBound <= toMatch.getValue() && upperBound > toMatch.getValue();}public static Matcher<RangeNumber> inRangeOf( int lowerBound, int range ) {return new InRangeMatcher( lowerBound, range );} }對于給定的示例,工作量可能會有些夸大。 但它顯示了如何使用自定義匹配器消除先前帖子中有點神奇的IN_RANGE_NUMBER常量。 除了新類型外,還強制聲明語句的編譯時類型安全。 這意味著例如String參數(shù)將不被接受進(jìn)行驗證。
下圖顯示了使用我們的自定義匹配器時測試結(jié)果失敗的樣子:
很容易看出describeTo和describeMismatchSafely的實現(xiàn)以哪種方式影響故障消息。 它表示期望值應(yīng)該在指定的下限和(計算的)上限1之間 ,并跟在實際值之后。
有點不幸的是,JUnit擴(kuò)展了其Assert類的API,以提供一組assertThat(…)方法。 這些方法實際上復(fù)制了MatcherAssert提供的API。 實際上,這些方法的實現(xiàn)委托給這種類型的相應(yīng)方法。
盡管這可能只是個小問題,但我認(rèn)為值得一提。 由于這種方法,JUnit與Hamcrest庫牢固地聯(lián)系在一起。 這種依賴性有時會導(dǎo)致問題。 特別是與其他庫一起使用時,通過合并自己的hamcrest版本的副本,情況甚至更糟……
Hamcrest的單元測試主張并非沒有競爭。 雖然關(guān)于每次測試一個確定與每個測試 一個概念的討論超出了本文的討論范圍,但后一種觀點的支持者可能認(rèn)為該庫的驗證聲明過于嘈雜。 尤其是當(dāng)一個概念需要多個斷言時。
這就是為什么我必須在本章中添加另一部分!
斷言
在“ 測試跑步者”中,示例片段之一使用了兩個assertXXX語句。 這些驗證期望的異常是IllegalArgumentException的實例并提供特定的錯誤消息。 該段看起來像這樣:
Throwable actual = ...assertTrue( actual instanceof IllegalArgumentException ); assertEquals( EXPECTED_ERROR_MESSAGE, actual.getMessage() );上一節(jié)教我們?nèi)绾问褂肏amcrest改進(jìn)代碼。 但是,如果您碰巧是該庫的新手,您可能會想知道要使用哪個表達(dá)式。 或打字可能會感到不舒服。 無論如何,多個assertThat語句會加在一起。
AssertJ庫通過為Java提供流暢的斷言來努力改善這一點。 流暢的接口 API的目的是提供一種易于閱讀的,富有表現(xiàn)力的編程風(fēng)格,從而減少膠合代碼并簡化鍵入。
那么如何使用這種方法來重構(gòu)上面的代碼?
import static org.assertj.core.api.Assertions.assertThat;與其他方法類似,AssertJ提供了一個實用程序類,該類提供了一組靜態(tài)assertThat方法。 但是這些方法針對給定的參數(shù)類型返回特定的斷言實現(xiàn)。 這就是所謂的語句鏈接的起點。
Throwable actual = ...assertThat( actual ).isInstanceOf( IllegalArgumentException.class ).hasMessage( EXPECTED_ERROR_MESSAGE );旁觀者認(rèn)為可讀性在某種程度上得到了擴(kuò)展,但無論如何都可以用更緊湊的樣式來寫斷言。 了解如何流暢地添加與被測特定概念相關(guān)的各種驗證方面。 這種編程方法支持有效的類型輸入,因為IDE的內(nèi)容輔助可以提供給定值類型的可用謂詞列表。
因此,您想向后世提供表現(xiàn)力的失敗消息嗎? 一種可能是使用describedAs作為鏈中的第一個鏈接來注釋整個塊:
Throwable actual = ...assertThat( actual ).describedAs( "Expected exception does not match specification." ).hasMessage( EXPECTED_ERROR_MESSAGE ).isInstanceOf( NullPointerException.class );該代碼段期望使用NPE,但假設(shè)在運行時拋出了IAE。 然后失敗的測試運行將提供如下消息:
也許您希望根據(jù)給定的失敗原因使您的消息更加細(xì)微。 在這種情況下,您可以在每個驗證規(guī)范之前添加一條describedAs語句:
Throwable actual = ...assertThat( actual ).describedAs( "Message does not match specification." ).hasMessage( EXPECTED_ERROR_MESSAGE ).describedAs( "Exception type does not match specification." ).isInstanceOf( NullPointerException.class );還有更多的AssertJ功能可供探索。 但是,要使該帖子保持在范圍內(nèi),請參閱實用程序的在線文檔以獲取更多信息。 但是,在結(jié)束之前,讓我們再次看一下范圍內(nèi)驗證示例。 這可以通過自定義斷言來解決:
public class RangeCounterAssertionextends AbstractAssert<RangeCounterAssertion, RangeCounter> {private static final String ERR_IN_RANGE_OF = "Expected value to be between <%s> and <%s>, but was <%s>";private static final String ERR_RANGE_ID = "Expected range identifier to be <%s>, but was <%s>";public static RangeCounterAssertion assertThat( RangeCounter actual ) {return new RangeCounterAssertion( actual );}public InRangeAssertion hasRangeIdentifier( String expected ) {isNotNull();if( !actual.getRangeIdentifier().equals( expected ) ) {failWithMessage( ERR_RANGE_ID, expected, actual.getRangeIdentifier() );}return this;}public RangeCounterAssertion isInRangeOf( int lowerBound, int range ) {isNotNull();int upperBound = lowerBound + range;if( !isInInterval( lowerBound, upperBound ) ) {int actualValue = actual.getValue();failWithMessage( ERR_IN_RANGE_OF, lowerBound, upperBound, actualValue );}return this;}private boolean isInInterval( int lowerBound, int upperBound ) {return actual.getValue() >= lowerBound && actual.getValue() < upperBound;}private RangeCounterAssertion( Integer actual ) {super( actual, RangeCounterAssertion.class );} }自定義斷言是擴(kuò)展AbstractAssert常見做法。 第一個通用參數(shù)是斷言的類型本身。 流利的鏈接樣式需要它。 第二種是斷言所基于的類型。
該實現(xiàn)提供了兩種附加的驗證方法,可以按照以下示例進(jìn)行鏈接。 因此,這些方法將返回斷言實例本身。 請注意, isNotNull()的調(diào)用如何確保我們要聲明的實際RangeNumber不為null 。
定制斷言由其工廠方法assertThat(RangeNumber) 。 由于它繼承了可用的基本檢查,因此斷言可以開箱即用地驗證非常復(fù)雜的規(guī)范。
RangeNumber first = ... RangeNumber second = ...assertThat( first ).isInRangeOf( LOWER_BOUND, RANGE ).hasRangeIdentifier( EXPECTED_RANGE_ID ).isNotSameAs( second );為了完整RangNumberAssertion ,以下是RangNumberAssertion的實際運行方式:
不幸的是,不可能在同一測試用例中使用兩種不同的斷言類型和靜態(tài)導(dǎo)入。 當(dāng)然,假定這些類型遵循assertThat(...)命名約定。 為了避免這種情況,文檔建議擴(kuò)展實用程序類Assertions 。
這樣的擴(kuò)展可用于提供靜態(tài)的assertThat方法,作為所有項目自定義斷言的入口。 通過在整個項目中使用此自定義實用程序類,不會發(fā)生導(dǎo)入沖突。 在為所有斷言提供單一入口點的部分中,可以找到詳細(xì)的描述:在線文檔中有關(guān)定制斷言的 yours + AssertJ 。
流利的API的另一個問題是單行鏈接的語句可能更難調(diào)試。 這是因為調(diào)試器可能無法在鏈中設(shè)置斷點。 此外,可能不清楚哪個方法調(diào)用已引起異常。
但是,正如Wikipedia所說的那樣,可以通過將語句分成多行來克服這些問題,如上面的示例所示。 這樣,用戶可以在鏈中設(shè)置斷點,輕松地逐行瀏覽代碼。
結(jié)論
簡而言之,JUnit的這一章介紹了不同的單元測試斷言方法,例如該工具的內(nèi)置機(jī)制,Hamcrest匹配器和AssertJ斷言。 它概述了一些優(yōu)缺點,并通過本教程的持續(xù)示例對主題進(jìn)行了擴(kuò)展。 此外,還展示了如何創(chuàng)建和使用自定義匹配器和斷言。
盡管基于Assert的機(jī)制肯定是過時的且不太面向?qū)ο?#xff0c;但它仍然具有它的提倡者。 Hamcrest匹配器提供斷言和謂詞定義的清晰分隔,而AssertJ斷言以緊湊且易于使用的編程樣式進(jìn)行評分。 所以現(xiàn)在您選擇太多了……
請注意,這將是本教程有關(guān)JUnit測試要點的最后一章。 這并不意味著沒有更多要說的了。 恰恰相反! 但這將超出此迷你系列量身定制的范圍。 而且您知道他們在說什么: 總是讓他們想要更多…
翻譯自: https://www.javacodegeeks.com/2014/09/junit-in-a-nutshell-unit-test-assertion.html
junit單元測試斷言
創(chuàng)作挑戰(zhàn)賽新人創(chuàng)作獎勵來咯,堅持創(chuàng)作打卡瓜分現(xiàn)金大獎總結(jié)
以上是生活随笔為你收集整理的junit单元测试断言_简而言之,JUnit:单元测试断言的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 3000元玩绝地求生的电脑配置?
- 下一篇: REST /使用提要发布事件