您好,登錄后才能下訂單哦!
這篇“JUnit5基礎入門實例分析”文章的知識點大部分人都不太理解,所以小編給大家總結了以下內容,內容詳細,步驟清晰,具有一定的借鑒價值,希望大家閱讀完這篇文章能有所收獲,下面我們一起來看看這篇“JUnit5基礎入門實例分析”文章吧。
設計哲學
新的架構設計(這個我們日后聊),其關注點在高擴展性。如果后面出現了什么神之測試技術(至少對我們廣大 Java?來說很神的),它們也可能在 JUnit 5 的架構下被實現。
不過當前來說,涉及的基礎知識與 JUnit 4 是非常相似的。JUnit 5 的改動并不激進,相反它的優化歷程是小心翼翼,小步迭代的。因此,開發者應該會對新的 API 感到非常熟悉。至少我是這樣的,我相信你也不會感覺陌生:
class Lifecycle { @BeforeAll static void initializeExternalResources() { System.out.println("Initializing external resources..."); } @BeforeEach void initializeMockObjects() { System.out.println("Initializing mock objects..."); } @Test void someTest() { System.out.println("Running some test..."); assertTrue(true); } @Test void otherTest() { assumeTrue(true); System.out.println("Running another test..."); assertNotEquals(1, 42, "Why wouldn't these be the same?"); } @Test @Disabled void disabledTest() { System.exit(1); } @AfterEach void tearDown() { System.out.println("Tearing down..."); } @AfterAll static void freeExternalResources() { System.out.println("Freeing external resources..."); } }
是吧?這里并沒有很大的改動。
JUnit 5 預備
包可見性
JUnit 5 最明顯的變化應該是,不再需要手動將測試類與測試方法為 public 了。包可見的訪問級別就足夠了。當然,私有(private)訪問還是不行的。我認為這個變化是合理的,也符合我們對可見性的一般直覺。
這很好!至少可以少打幾個字母了。不過,我相信你也不是每次都手打這幾個字母的,是吧?盡管如此還是很好,少一些關鍵字,你在看測試的時候也少些切換。
測試的生命周期
@Test
JUnit 中最基本的注解非 @Test 莫屬了。它會標記方法為測試方法,以便構建工具和 IDE 能夠識別并執行它們。
它的 API 和作用并沒有變化,不過它不再接受任何參數了。若要測試是否拋出異常,你可以通過新的斷言 API 來做到;不過就我所知,目前還沒有超時選項timeout的替代品。
與 JUnit 4一樣,JUnit 5 會為每個測試方法創建一個新的實例。
Before 和 After
你可能需要執行一些代碼來在測試執行前后完成一些初始化或銷毀的操作。在 JUnit 5 中,有4個注解你可能會用于如此工作:
@BeforeAll
只執行一次,執行時機是在所有測試和 @BeforeEach 注解方法之前。
@BeforeEach
在每個測試執行之前執行。
@AfterEach
在每個測試執行之后執行。
@AfterAll
只執行一次,執行時機是在所有測試和 @AfterEach 注解方法之后。
因為框架會為每個測試創建一個單獨的實例,在 @BeforeAll/@AfterAll 方法執行時尚無任何測試實例誕生。因此,這兩個方法必須定義為靜態方法。
注解了同樣一個注解的不同方法,其執行次序是不可預知的,包括對繼承來的方法也適用。這是開發團隊經過審慎思考后的決定,即把單元測試與集成測試的關注點分開。集成測試可能需要方法間更緊密的協作,但一個單元測試不應該對其他的單元測試有所依賴。而對于集成測試——也叫場景測試——的支持,也已在團隊的計劃中。
除了名字有所不同,這幾個注解與 JUnit 4 中的注解工作方式完全一樣。無獨有偶,跟主流意見一致,我也覺得這個新的命名不能說服我其必要性。這個 issue 下有更多的討論。
禁用測試
今兒星期五,抬頭一看已經4點半,無心工作的你想回家了?完全理解,在測試上怒拍一個 @Disabled 注解即可。有良心的話寫個忽略測試的理由是極好的,不過也可以不帶此參數。
@Test @Disabled("你丫就是存心跑不過的是不?!") void failingTest() { assertTrue(false); }
測試類的生命周期
JUnit 團隊發布的***版原型中,包含了一個對 測試類的生命周期 的描述,有意思的是,這個特性在 alpha 版本的發布中未被加入。這個生命周期模型建議,在被測類的多個測試方法中使用一個同樣的實例,因為這樣我們就可以通過改變對象的狀態,進而實現在多個測試方法中的交互。(我也再說一遍,這更像是 場景測試 要管的事。)
正如我在***版公測時所說,這樣的特性99%的場景下是有害的,只有另外1%的場合下才有真正的用處。我只能說,還好這個特性被摒棄了。想想你的單元測試,如果它們必須靠在方法間維護狀態來工作,這畫面簡直太美我不敢看?。
斷言
如果說 @Test、@Before...、@After... 等注解是一個測試套件的骨架,那么斷言就是它的心臟。準備好測試實例、執行了被測類的方法以后,斷言能確保你得到了想要的結果。否則,就說明當前測試失敗了。
常規斷言
一般的斷言,無非是檢查一個實例的屬性(比如,判空與判非空等),或者對兩個實例進行比較(比如,檢查兩個實例對象是否相等)等。無論哪種檢查,斷言方法都可以接受一個字符串作為***一個可選參數,它會在斷言失敗時提供必要的描述信息。如果提供出錯信息的過程比較復雜,它也可以被包裝在一個 lambda 表達式中,這樣,只有到真正失敗的時候,消息才會真正被構造出來。
@Test void assertWithBoolean() { assertTrue(true); assertTrue(this::truism); assertFalse(false, () -> "Really " + "expensive " + "message" + "."); } boolean truism() { return true; } @Test void assertWithComparison() { List<String> expected = asList("element"); List<String> actual = new LinkedList<>(expected); assertEquals(expected, actual); assertEquals(expected, actual, "Should be equal."); assertEquals(expected, actual, () -> "Should " + "be " + "equal."); assertNotSame(expected, actual, "Obviously not the same instance."); }
如你所見,JUnit 5 的 API 并無太多變化。斷言方法的命名是一樣的,方法同樣接受兩個參數,分別是一個期望值與一個實際值。
期望值與實際值的傳入順序非常重要,無論是對于理解測試的內容,還是理解失敗時的錯誤信息,但有時還是很容易弄錯,這點很坑。不過仔細想想,也沒什么更好的辦法,除非你自己創建一個新的斷言框架。既然市面上已有對應的產品如 Hamcrest (ugh!) 和AssertJ (yeah!譯者表示:不太清楚這歡呼的梗在哪里)等,再浪費有限的時間去造輪子明顯不值得。畢竟最重要的是保證你的斷言庫專注于一件事,借鑒已有實現可以節省成本。
哦對了,失敗信息現在是作為***傳入的參數了。我很喜歡這個細節,因為,它讓你專注于真正重要之事——那兩個需被斷言的值。由于擁抱了 Java 8 的緣故,真值斷言方法現在也接受 supplier 參數了,又是一個暖心的小細節。
擴展斷言
除了那種一般的檢查特定實例或屬性的斷言外,還有一些其他類型的斷言。
這里要講的***個甚至都不是個真正的斷言,它做的事就是強行讓測試失敗,并提供一個失敗信息。
@Test void failTheTest() { fail("epicly"); }
還有 assertAll 方法,它接受可變數量的斷言作為參數,并保證它們全部得到執行,然后再把錯誤信息(如果有)一并匯報出來。
@Test void assertAllProperties() { Address address = new Address("New City", "Some Street", "No"); assertAll("address", () -> assertEquals("Neustadt", address.city), () -> assertEquals("Irgendeinestraße", address.street), () -> assertEquals("Nr", address.number) ); }
org.opentest4j.MultipleFailuresError: address (3 failures) expected: <Neustadt> but was: <New City> expected: <Irgendeinestraße> but was: <Some Street> expected: <Nr> but was: <No>
這個特性在檢查對象的多個屬性值時非常有用。按照一般的做法,測試在***個斷言失敗時就會掛掉了,此時只有***個出錯的地方得到提示,而你無法得知其他值的斷言是否成功,只好再跑一遍測試。
***,我們終于有了 assertThrows 和 expectThrows 方法。兩者均會在被測方法未拋出預期異常時失敗。而后者還會返回拋出的異常實例,以用于后續的驗證,比如,斷言異常信息包含正確的信息等。
@Test void assertExceptions() { assertThrows(Exception.class, this::throwing); Exception exception = expectThrows(Exception.class, this::throwing); assertEquals("Because I can!", exception.getMessage()); }
假言/判定(Assumptions)?
假言/判定允許你僅在特定條件滿足時才運行測試。這個特性能夠減少測試組件的運行時間和代碼重復,特別是在假言都不滿足的情況下。
@Test void exitIfFalseIsTrue() { assumeTrue(false); System.exit(1); } @Test void exitIfTrueIsFalse() { assumeFalse(this::truism); System.exit(1); } private boolean truism() { return true; } @Test void exitIfNullEqualsString() { assumingThat( "null".equals(null), () -> System.exit(1) ); }
假言/判定適用于兩種情形,要么是你希望在某些條件不滿足時中止測試,要么是你希望僅當某個條件滿足時才執行(部分)測試。主要的區別是,被中止的測試是以被禁用(disabled)的形式被報告,此時沒有測試任何內容,因為條件得不到滿足。
測試嵌套
在 JUnit 5 中,嵌套測試幾乎不費吹灰之力。你只需要在嵌套的類上添加 @Nested 注解,類中的所有方法即會被引擎執行:
package org.codefx.demo.junit5; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; class Nest { int count = Integer.MIN_VALUE; @BeforeEach void setCountToZero() { count = 0; } @Test void countIsZero() { assertEquals(0, count); } @Nested class CountGreaterZero { @BeforeEach void increaseCount() { count++; } @Test void countIsGreaterZero() { assertTrue(count > 0); } @Nested class CountMuchGreaterZero { @BeforeEach void increaseCount() { count += Integer.MAX_VALUE / 2; } @Test void countIsLarge() { assertTrue(count > Integer.MAX_VALUE / 2); } } } }
如你所見,嵌套類中的 @BeforeEach(及 @AfterEach )注解也工作良好。不過,構造順序似乎還未被寫入文檔,它們的初始化次序是從外向內的。這也讓你能疊加式地為內部類準備測試數據。
如果嵌套的內部測試想要存取外部測試類的字段,那么嵌套類本身不應該是靜態的。但這樣一來也就禁止了靜態方法的使用,因而這種場景下@BeforeAll 和 @AfterAll 方法也就無法使用了(還是說終有他法實現?)
你可能有疑惑,嵌套的內部測試類有什么用。個人而言,我用內部類來漸進測試接口,其他人則多用于保持測試類短小專注。后者同時也有一個經典的例子來說明,例子由 JUnit 團隊提供,它測試了一個棧:
class TestingAStack { Stack<Object> stack; boolean isRun = false; @Test void isInstantiatedWithNew() { new Stack<Object>(); } @Nested class WhenNew { @BeforeEach void init() { stack = new Stack<Object>(); } // some tests on 'stack', which is empty @Nested class AfterPushing { String anElement = "an element"; @BeforeEach void init() { stack.push(anElement); } // some tests on 'stack', which has one element... } } }
在上面的例子中,棧的狀態改變會反映到內層的測試類中,其中內部類又基于自身的場景執行了一些測試。
測試命名
JUnit 5 提供了一個注解 @DisplayName,它用以為開發者提供更可讀的測試類和測試方法信息。
上面的 stack 測試例子加上該注解以后就變成這樣:
@DisplayName("A stack") class TestingAStack { @Test @DisplayName("is instantiated with new Stack()") void isInstantiatedWithNew() { /*...*/ } @Nested @DisplayName("when new") class WhenNew { @Test @DisplayName("is empty") void isEmpty() { /*...*/ } @Test @DisplayName("throws EmptyStackException when popped") void throwsExceptionWhenPopped() { /*...*/ } @Test @DisplayName("throws EmptyStackException when peeked") void throwsExceptionWhenPeeked() { /*...*/ } @Nested @DisplayName("after pushing an element") class AfterPushing { @Test @DisplayName("it is no longer empty") void isEmpty() { /*...*/ } @Test @DisplayName("returns the element when popped and is empty") void returnElementWhenPopped() { /*...*/ } @Test @DisplayName( "returns the element when peeked but remains not empty") void returnElementWhenPeeked(){ /*...*/ } } } }
這是一份TDDer 看了會感動,BDDer 看了會流淚的測試結果輸出。
以上就是關于“JUnit5基礎入門實例分析”這篇文章的內容,相信大家都有了一定的了解,希望小編分享的內容對大家有幫助,若想了解更多相關的知識內容,請關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。