[跟著官方文檔學JUnit5][七][WritingTests][學習筆記]
1.測試模板
@TestTemplate方法不是常規的測試用例,而是測試用例的模板。因此,它被設計為根據注冊提供者返回的調用上下文的數量多次調用。因此,它必須與已注冊的TestTemplateInvocationContextProvider擴展一起使用。測試模板方法的每次調用都像執行常規@Test方法一樣,完全支持相同的生命周期回調和擴展。重復測試和參數化測試是內置特殊的測試模板。
2.動態測試
JUnit Jupiter描述注解的@Test和JUnit 4中的@Test很相似。這些測試用例是靜態的,因為它們是在編譯時完全指定的,并且它們的行為不能被運行時發生的任何事情改變。 假設提供了動態行為的基本形式,但有意限制了它們的表達能力。
除了這些標準測試之外,JUnit Jupiter中還引入了一種全新的測試編程模型。這種新類型的測試是一種動態測試,它在運行時由帶有@TestFactory注解的工廠方法生成。
與@Test方法相比,@TestFactory方法本身不是測試用例,而是測試用例的工廠。因此,動態測試是工廠的產品。從技術上講,@TestFactory方法必須返回單個DynamicNode或Stream、Collection、Iterable、Iterator或DynamicNode實例數組。DynamicNode的可實例化子類是DynamicContainer和DynamicTest。DynamicContainer實例由顯示名稱和動態子節點列表組成,可以創建任意嵌套的動態節點層次結構。DynamicTest實例將被延遲執行,從而實現動態甚至非確定性的測試用例生成。
@TestFactory返回的任何Stream都將通過調用stream.close()正確關閉,從而可以安全地使用Files.lines()等資源。
與@Test方法一樣,@TestFactory方法不能是私有的或靜態的,并且可以選擇聲明要由ParameterResolvers解析的參數。
DynamicTest是在運行時生成的測試用例。它由顯示名稱和可執行文件組成。Executable是一個@FunctionalInterface,這意味著動態測試的實現可以作為lambda表達式或方法引用提供。
動態測試實例
以下DynamicTestsDemo類演示了測試工廠和動態測試的幾個示例。
第一種方法返回無效的返回類型。 由于在編譯時無法檢測到無效的返回類型,因此在運行時檢測到它時會拋出 JUnitException。
接下來的六個方法是非常簡單的示例,演示了DynamicTest實例的Collection、Iterable、Iterator、數組或Stream的生成。這些示例中的大多數并沒有真正展示動態行為,而只是展示了原則上支持的返回類型。但是,dynamicTestsFromStream()和dynamicTestsFromIntStream()展示了為給定的字符串集或輸入數字范圍生成動態測試是多么容易。
下一種方法本質上是真正動態的。generateRandomNumberOfTests()實現了一個生成隨機數的迭代器、一個顯示名稱生成器和一個測試執行器,然后將所有三者提供給DynamicTest.stream()。盡管generateRandomNumberOfTests()的非確定性行為當然與測試可重復性相沖突,因此應謹慎使用,但它有助于展示動態測試的表現力。
next方法在靈活性方面類似于generateRandomNumberOfTests();但是,dynamicTestsFromStreamFactoryMethod()通過DynamicTest.stream()工廠方法從現有Stream生成動態測試流。
出于演示目的,dynamicNodeSingleTest()方法生成單個DynamicTest而不是流,dynamicNodeSingleContainer()方法使用DynamicContainer生成動態測試的嵌套層次結構。
import static org.junit.jupiter.api.Assertions.*; import static org.junit.jupiter.api.Named.named; import static org.junit.jupiter.api.DynamicContainer.dynamicContainer; import static org.junit.jupiter.api.DynamicTest.dynamicTest; import com.example.util.Calculator; import com.example.util.StringUtils; import org.junit.jupiter.api.*; import org.junit.jupiter.api.function.ThrowingConsumer; import java.util.*; import java.util.function.Function; import java.util.stream.IntStream; import java.util.stream.Stream; class DynamicTestsDemo { private final Calculator calculator = new Calculator(); //This will result in a JUnitException! @TestFactory List
動態測試的URI測試源
JUnit平臺提供了TestSource,表示測試或容器的來源,用于通過IDE和構建工具導航到其位置。
動態測試或動態容器的TestSource可以從java.net.URI構造,該java.net.URI可以分別通過DynamicTest.dynamicTest(String, URI, Executable)或DynamicContainer.dynamicContainer(String, URI, Stream)工廠方法提供。URI將被轉換為以下TestSource實現之一。
ClasspathResourceSource 如果URI包含classpath。例如:classpath:/test/foo.xml?line=20,column=2 DirectorySource 如果URI表示文件系統存在的目錄 FileSource 如果URI表示文件系統存在的文件 MethodSource 如果URI包含方法和完全限定方法名稱(FQMN),例如,method:org.junit.Foo#bar(java.lang.String,java.lang.String[])。 ClassSource 如果URI包含類和完全限定類名稱,例如,class:org.junit.Foo?line=42 UriSource 如果上述TestSource實現均不適用。
3.超時
@Timeout注解允許人們聲明如果測試、測試工廠、測試模板或生命周期方法的執行時間超過給定的持續時間,它應該失敗。持續時間的時間單位默認為秒,但可配置。
以下示例顯示了如何將@Timeout應用于生命周期和測試方法。
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Timeout; import java.util.concurrent.TimeUnit; class TimeoutDemo { @BeforeEach @Timeout(5) void setUp() { //fails if execution time exceeds 5 seconds } @Test @Timeout(value = 100, unit = TimeUnit.MILLISECONDS) void failsIfExecutionTimeExceeds100Milliseconds() { // fails if execution time exceeds 100 milliseconds } }
與assertTimeoutPreemptively()斷言相反,帶注解的方法的執行在測試的主線程中進行。如果超時,主線程會被另一個線程中斷。這樣做是為了確保與Spring等框架的互操作性,這些框架使用對當前正在運行的線程敏感的機制——例如,ThreadLocal事務管理。
要將相同的超時應用于測試類及其所有@Nested類中的所有測試方法,可以在類級別聲明@Timeout注解。然后它將應用于該類及其@Nested類中的所有測試、測試工廠和測試模板方法,除非被特定方法或@Nested類上的@Timeout注解覆蓋。注意,在類級別聲明的@Timeout注解不適用于生命周期方法。
在@TestFactory方法上聲明@Timeout會檢查工廠方法是否在指定的持續時間內返回,但不會驗證工廠生成的每個單獨DynamicTest的執行時間。請為此目的使用assertTimeout()或assertTimeoutPreemptively()。
如果@Timeout出現在@TestTemplate方法上,例如,@RepeatedTest或@ParameterizedTest,每次調用都將應用給定的超時。
以下配置參數可用于為某個類別的所有方法指定全局超時,除非它們或封閉的測試類使用@Timeout注解:
junit.jupiter.execution.timeout.default 所有可測試和生命周期方法的默認超時 junit.jupiter.execution.timeout.testable.method.default 所有可測試方法的默認超時 junit.jupiter.execution.timeout.test.method.default @Test方法的默認超時 junit.jupiter.execution.timeout.testtemplate.method.default @TestTemplate方法的默認超時 junit.jupiter.execution.timeout.testfactory.method.default @TestFactory方法的默認超時 junit.jupiter.execution.timeout.lifecycle.method.default 所有生命周期方法的默認超時 junit.jupiter.execution.timeout.beforeall.method.default @BeforeAll方法的默認超時 junit.jupiter.execution.timeout.beforeeach.method.default @BeforeEach方法的默認超時 junit.jupiter.execution.timeout.aftereach.method.default @AfterEach方法的默認超時 junit.jupiter.execution.timeout.afterall.method.default @AfterAll方法的默認超時
更具體的配置參數會覆蓋不太具體的配置參數。例如:
junit.jupiter.execution.timeout.test.method.default 重寫 junit.jupiter.execution.timeout.testable.method.default 重寫 junit.jupiter.execution.timeout.default
此類配置參數的值必須采用以下不區分大小寫的格式:
使用@Timeout進行輪詢測試
在處理異步代碼時,通常會編寫在執行任何斷言之前等待某事發生時輪詢的測試。在某些情況下,可以重寫邏輯以使用CountDownLatch或其他同步機制,但有時這是不可能的。例如,如果被測主體向外部消息代理中的通道發送消息,并且斷言無法執行,直到消息已通過通道成功發送。像這樣的異步測試需要某種形式的超時來確保它們不會因為無限期地執行而掛起測試套件,就像異步消息永遠不會成功傳遞的情況一樣。
通過為輪詢的異步測試配置超時,可以確保測試不會無限期地執行。以下示例演示了如何使用JUnit Jupiter的@Timeout注解來實現這一點。這種技術可以很容易地用于實現“輪詢直到”邏輯。
@Test @Timeout(5) // Poll at most 5 seconds void pollUntil() throws InterruptedException { while (asynchronousResultNotAvailable()) { Thread.sleep(250); // custom poll interval } // Obtain the asynchronous result and perform assertions }
禁用全局@Timeout
在調試會話中單步執行代碼時,固定的超時限制可能會影響測試結果,例如,盡管滿足所有斷言,但將測試標記為失敗。
JUnit Jupiter支持junit.jupiter.execution.timeout.mode配置參數來配置何時應用超時。共有三種模式:啟用、禁用和在debug時禁用。默認模式已啟用。當其輸入參數之一以-agentlib:jdwp開頭時,VM運行時被視為在調試模式下運行。此啟發式由disabled_on_debug模式查詢。
4.并行執行
默認情況下,JUnit Jupiter測試在單個線程中按順序運行。并行運行測試?可以加快執行速度,自5.3版起可作為可選功能使用。要啟用并行執行,請將junit.jupiter.execution.parallel.enabled配置參數設置為true。例如,在junit-platform.properties中(有關其他選項,請參閱配置參數)。
請注意,啟用此屬性只是并行執行測試所需的第一步。 如果啟用,默認情況下測試類和方法仍將按順序執行。測試樹中的節點是否并發執行由其執行模式控制。可以使用以下兩種模式。
SAME_THREAD 強制在父級使用的同一線程中執行。例如,當用于測試方法時,該測試方法將與包含測試類的任何@BeforeAll或@AfterAll方法在同一線程中執行。
CONCURRENT 并發執行,除非資源鎖定強制在同一線程中執行。
默認情況下,測試樹中的節點使用SAME_THREAD執行模式。可以通過設置junit.jupiter.execution.parallel.mode.default配置參數來更改默認值。或者,可以使用@Execution注解來更改帶注釋的元素及其子元素(如果有)的執行模式,這允許您逐個激活各個測試類的并行執行。
junit.jupiter.execution.parallel.enabled = true junit.jupiter.execution.parallel.mode.default = concurrent
junit.jupiter.execution.parallel.enabled = true junit.jupiter.execution.parallel.mode.default = same_thread junit.jupiter.execution.parallel.mode.classes.default = concurrent
相反的組合將并行運行一個類中的所有方法,但頂級類將按順序運行:
junit.jupiter.execution.parallel.enabled = true junit.jupiter.execution.parallel.mode.default = concurrent junit.jupiter.execution.parallel.mode.classes.default = same_thread
下圖說明了對于junit.jupiter.execution.parallel.mode.default和junit.jupiter.execution.parallel.mode.classes.default這四個組合,執行兩個頂級測試類A和B以及每個類兩個測試方法的行為(請參閱第一列中的標簽)。
默認執行模式配置組合
如果未顯式設置junit.jupiter.execution.parallel.mode.classes.default配置參數,則將改用junit.jupiter.execution.parallel.mode.default的值。
4.1.配置
可以使用ParallelExecutionConfigurationStrategy配置所需的并行度和最大池大小等屬性。JUnit平臺提供了兩種開箱即用的實現:dynamic實現和fixed實現。或者,可以實施自定義策略。
要選擇策略,請將junit.jupiter.execution.parallel.config.strategy配置參數設置為以下選項之一。
dynamic 根據可用處理器/內核數乘以junit.jupiter.execution.parallel.config.dynamic.factor配置參數(默認為 1)計算所需的并行度。
fixed 使用必需的junit.jupiter.execution.parallel.config.fixed.parallelism配置參數作為所需的并行度。
custom 允許通過強制的junit.jupiter.execution.parallel.config.custom.class配置參數來指定自定義ParallelExecutionConfigurationStrategy實現,以確定所需的配置。
如果未設置配置策略,則JUnit Jupiter將使用系數為1的動態配置策略。因此,所需的并行度將等于可用處理器/內核的數量。
4.2.同步
除了使用@Execution注解控制執行模式之外,JUnit Jupiter還提供了另一種基于注解的聲明性同步機制。@ResourceLock注解允許聲明測試類或方法使用需要同步訪問以確保可靠測試執行的特定共享資源。共享資源由唯一名稱(字符串)標識。該名稱可以是用戶定義的,也可以是資源中的預定義常量之一:SYSTEM_PROPERTIES、SYSTEM_OUT、SYSTEM_ERR、LOCALE 或 TIME_ZONE。
如果以下示例中的測試在不使用@ResourceLock的情況下并行運行,則它們將是片狀的。有時它們會通過,而在其他時候,由于寫入然后讀取相同的JVM系統屬性的固有爭用條件,它們會失敗。
當使用@ResourceLock注解聲明對共享資源的訪問權限時,JUnit Jupiter引擎將使用此信息來確保不會并行運行沖突的測試。
除了唯一標識共享資源的字符串之外,還可以指定訪問模式。需要對共享資源的READ訪問權限的兩個測試可以彼此并行運行,但當任何其他需要對同一共享資源READ_WRITE訪問權限的測試運行時,則不會并行運行。
import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.parallel.Execution; import org.junit.jupiter.api.parallel.ExecutionMode; import org.junit.jupiter.api.parallel.ResourceAccessMode; import org.junit.jupiter.api.parallel.ResourceLock; import java.util.Properties; import static org.junit.jupiter.api.Assertions.*; import static org.junit.jupiter.api.parallel.ResourceAccessMode.READ_WRITE; import static org.junit.jupiter.api.parallel.Resources.SYSTEM_PROPERTIES; @Execution(ExecutionMode.CONCURRENT) class SharedResourcesDemo { private Properties backup; @BeforeEach void backup() { backup = new Properties(); backup.putAll(System.getProperties()); } @AfterEach void restore() { System.setProperties(backup); } @Test @ResourceLock(value = SYSTEM_PROPERTIES, mode = ResourceAccessMode.READ) void customPropertyIsNotSetByDefault() { assertNull(System.getProperty("my.prop")); } @Test @ResourceLock(value = SYSTEM_PROPERTIES, mode = READ_WRITE) void canSetCustomPropertyToApple() { System.setProperty("my.prop", "apple"); assertEquals("apple", System.getProperty("my.prop")); } @Test @ResourceLock(value = SYSTEM_PROPERTIES, mode = READ_WRITE) void canSetCustomPropertyToBanana() { System.setProperty("my.prop", "banana"); assertEquals("banana", System.getProperty("my.prop")); } }
5.內置拓展
雖然JUnit團隊鼓勵在單獨的庫中打包和維護可重用的擴展,但JUnit Jupiter API工件包括一些面向用戶的擴展實現,這些實現被認為非常有用,用戶不必添加其他依賴項。
5.1.The TempDirectory Extension
內置的TempDirectory擴展用于為測試類中的單個測試或所有測試創建和清理臨時目錄。默認情況下,它處于注冊狀態。要使用它,請為@TempDir類型為java.nio.file.Path或java.io.File的字段添加注解,或者添加java.nio.file.Path或java.io.File類型的參數,這些參數用@TempDir注解為生命周期方法或測試方法。
例如,以下測試為單個測試方法聲明了一個用@TempDir進行批注的參數,創建并寫入臨時目錄中的文件,并檢查其內容。
@Test void writeItemsToFile(@TempDir Path tempDir) throws IOException { Path file = tempDir.resolve("test.txt"); new ListWriter(file).write("a", "b", "c"); assertEquals(singletonList("a,b,c"), Files.readAllLines(file)); }
可以通過指定多個帶注解的參數來注入多個臨時目錄。
@Test void copyFileFromSourceToTarget(@TempDir Path source, @TempDir Path target) throws IOException { Path sourceFile = source.resolve("test.txt"); new ListWriter(sourceFile).write("a", "b", "c"); Path targetFile = Files.copy(sourceFile, target.resolve("test.txt")); assertNotEquals(sourceFile, targetFile); assertEquals(singletonList("a,b,c"), Files.readAllLines(targetFile)); }
構造函數參數不支持@TempDir。如果希望跨生命周期方法和當前測試方法保留對臨時目錄的單個引用,請使用字段注入,方法是使用@TempDir注釋實例字段。
下面的示例將共享臨時目錄存儲在靜態字段中。這允許在測試類的所有生命周期方法和測試方法中使用相同的共享TempDir。為了更好地隔離,應使用實例字段,以便每個測試方法使用單獨的目錄。
class SharedTempDirectoryDemo { @TempDir static Path sharedTempDir; @Test void writeItemsToFile() throws IOException { Path file = sharedTempDir.resolve("test.txt"); new ListWriter(file).write("a", "b", "c"); assertEquals(singletonList("a,b,c"), Files.readAllLines(file)); } @Test void anotherTestThatUsesTheSameTempDir() { // use sharedTempDir } }
Java junit
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。