非同步程式設計測試Awaitlity簡介| Baeldung
非同步系統的一個常見問題是,很難為那些專注於業務邏輯並且不會受到同步,超時和併發控制汙染的可編寫測試。
在本文中,我們將介紹Awaitility - 一個為非同步系統測試提供簡單的特定於域的語言(DSL)的庫。
通過Awaitility,我們可以通過易於閱讀的DSL表達我們對系統的期望。
我們需要將Awaitility依賴項新增到我們的pom.xml中。
該awaitility庫將足以滿足大多數使用情況。如果我們想要使用基於代理的條件,我們還需要提供awaitility-proxy庫:
<dependency> <groupId>org.awaitility</groupId> <artifactId>awaitility</artifactId> <version>3.0.0</version> <scope>test</scope> </dependency> <dependency> <groupId>org.awaitility</groupId> <artifactId>awaitility-proxy</artifactId> <version>3.0.0</version> <scope>test</scope> </dependency>
您可以在Maven Central上找到最新版本的awaitility 和awaitility-proxy 庫。
建立非同步服務
讓我們編寫一個簡單的非同步服務並測試它:
<b>public</b> <b>class</b> AsyncService { <b>private</b> <b>final</b> <b>int</b> DELAY = 1000; <b>private</b> <b>final</b> <b>int</b> INIT_DELAY = 2000; <b>private</b> AtomicLong value = <b>new</b> AtomicLong(0); <b>private</b> Executor executor = Executors.newFixedThreadPool(4); <b>private</b> <b>volatile</b> <b>boolean</b> initialized = false; <b>void</b> initialize() { executor.execute(() -> { sleep(INIT_DELAY); initialized = <b>true</b>; }); } <b>boolean</b> isInitialized() { <b>return</b> initialized; } <b>void</b> addValue(<b>long</b> val) { throwIfNotInitialized(); executor.execute(() -> { sleep(DELAY); value.addAndGet(val); }); } <b>public</b> <b>long</b> getValue() { throwIfNotInitialized(); <b>return</b> value.longValue(); } <b>private</b> <b>void</b> sleep(<b>int</b> delay) { <b>try</b> { Thread.sleep(delay); } <b>catch</b> (InterruptedException e) { } } <b>private</b> <b>void</b> throwIfNotInitialized() { <b>if</b> (!initialized) { <b>throw</b> <b>new</b> IllegalStateException(<font>"Service is not initialized"</font><font>); } } } </font>
現在,讓我們建立測試類:
<b>public</b> <b>class</b> AsyncServiceTest { <b>private</b> AsyncService asyncService; @Before <b>public</b> <b>void</b> setUp() { asyncService = <b>new</b> AsyncService(); } <font><i>//...</i></font><font> } </font>
我們的測試在呼叫initialize方法後檢查我們的服務初始化是否在指定的超時時間(預設為10秒)內發生。
此測試用例僅等待服務初始化狀態更改或在未發生狀態更改時丟擲ConditionTimeoutException。
狀態由Callable獲得,Callable在指定的初始延遲(預設為100毫秒)後以定義的時間間隔(預設為100毫秒)輪詢我們的服務。這裡我們使用超時,間隔和延遲的預設設定:
asyncService.initialize(); await() .until(asyncService::isInitialized);
在這裡,我們使用await - Awaitility類的靜態方法之一。它返回ConditionFactory類的例項。我們也可以用其他的方法,如給予增加可讀性的原因。
可以使用Awaitility類中的靜態方法更改預設計時引數:
Awaitility.setDefaultPollInterval(10, TimeUnit.MILLISECONDS); Awaitility.setDefaultPollDelay(Duration.ZERO); Awaitility.setDefaultTimeout(Duration.ONE_MINUTE);
在這裡我們可以看到Duration類的使用,它為最常用的時間段提供了有用的常量。
我們還可以為每個等待呼叫提供自定義時間值。在這裡,我們期望初始化將在五秒後發生,並且至少在100ms之後,輪詢間隔為100ms:
asyncService.initialize(); await() .atLeast(Duration.ONE_HUNDRED_MILLISECONDS) .atMost(Duration.FIVE_SECONDS) .with() .pollInterval(Duration.ONE_HUNDRED_MILLISECONDS) .until(asyncService::isInitialized);
值得一提的是,ConditionFactory包含像其他方法with, then, and, given。這些方法沒有做任何事情,只是返回它,但它們可能有助於增強測試條件的可讀性。
使用匹配器:
Awaitility還允許使用hamcrest匹配器來檢查表示式的結果。例如,我們可以在呼叫addValue方法後檢查我們的long值是否按預期更改:
asyncService.initialize(); await() .until(asyncService::isInitialized); <b>long</b> value = 5; asyncService.addValue(value); await() .until(asyncService::getValue, equalTo(value));
請注意,在此示例中,我們使用第一個await呼叫等待服務初始化。否則,getValue方法將丟擲IllegalStateException。
忽略例外:
有時,我們遇到一種情況,即方法在非同步作業完成之前丟擲異常。在我們的服務中,它可以是在初始化服務之前呼叫getValue方法。
Awaitility提供了在不失敗測試的情況下忽略此異常的可能性。
例如,讓我們在初始化之後檢查getValue結果是否等於零,忽略IllegalStateException:
asyncService.initialize(); given().ignoreException(IllegalStateException.<b>class</b>) .await().atMost(Duration.FIVE_SECONDS) .atLeast(Duration.FIVE_HUNDRED_MILLISECONDS) .until(asyncService::getValue, equalTo(0L));
使用代理:
我們使用awaitility-proxy以實現基於代理的條件判斷。代理的想法是為條件提供真正的方法呼叫,而不實現Callable或lambda表示式。
讓我們使用AwaitilityClassProxy.to靜態方法來檢查AsyncService是否已初始化:
asyncService.initialize(); await() .untilCall(to(asyncService).isInitialized(), equalTo(<b>true</b>));
訪問欄位:
Awaitility甚至可以訪問私有欄位來對它們執行斷言。在以下示例中,我們可以看到另一種獲取服務初始化狀態的方法:
asyncService.initialize(); await() .until(fieldIn(asyncService) .ofType(<b>boolean</b>.<b>class</b>) .andWithName(<font>"initialized"</font><font>), equalTo(<b>true</b>)); </font>
如何測試@Scheduled
Spring Framework中的一個可用註釋是@Scheduled。我們可以使用此批註以預定的方式執行任務 。
讓我們開始建立一個簡單的Counter類:
@Component <b>public</b> <b>class</b> Counter { <b>private</b> AtomicInteger count = <b>new</b> AtomicInteger(0); @Scheduled(fixedDelay = 5) <b>public</b> <b>void</b> scheduled() { <b>this</b>.count.incrementAndGet(); } <b>public</b> <b>int</b> getInvocationCount() { <b>return</b> <b>this</b>.count.get(); } }
我們將使用預定的方法來增加計數。請注意,我們還添加了@Scheduled註釋,以在5毫秒的固定時間內執行它。
另外,讓我們建立一個ScheduledConfig類,使用@EnableScheduling註釋啟用計劃任務:
@Configuration @EnableScheduling @ComponentScan(<font>"com.baeldung.scheduled"</font><font>) <b>public</b> <b>class</b> ScheduledConfig { } </font>
我們可以使用Awaitility DSL使我們的測試更具說明性:
@SpringJUnitConfig(ScheduledConfig.<b>class</b>) <b>public</b> <b>class</b> ScheduledAwaitilityIntegrationTest { @SpyBean <b>private</b> Counter counter; @Test <b>public</b> <b>void</b> whenWaitOneSecond_thenScheduledIsCalledAtLeastTenTimes() { await() .atMost(Duration.ONE_SECOND) .untilAsserted(() -> verify(counter, atLeast(10)).scheduled()); } }
在這種情況下,我們使用@SpyBean 註釋注入bean,以檢查在 一秒 的時間內呼叫排程方法的次數。
完整原始碼GitHub上獲得