待機性の概要
1. 序章
非同期システムの一般的な問題は、ビジネスロジックに焦点を当て、同期、タイムアウト、および同時実行制御で汚染されていない、読み取り可能なテストを作成するのが難しいことです。
この記事では、 Awaitility —非同期システムテスト用の単純なドメイン固有言語(DSL)を提供するライブラリについて見ていきます。
Awaitilityを使用すると、システムからの期待を読みやすいDSLで表現できます。
2. 依存関係
Awaitilityの依存関係を追加する必要があります
ほとんどのユースケースでは、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>
awaitilityおよびawaitility-proxyライブラリの最新バージョンはMavenCentralにあります。
3. 非同期サービスの作成
簡単な非同期サービスを作成してテストしてみましょう。
public class AsyncService {
private final int DELAY = 1000;
private final int INIT_DELAY = 2000;
private AtomicLong value = new AtomicLong(0);
private Executor executor = Executors.newFixedThreadPool(4);
private volatile boolean initialized = false;
void initialize() {
executor.execute(() -> {
sleep(INIT_DELAY);
initialized = true;
});
}
boolean isInitialized() {
return initialized;
}
void addValue(long val) {
throwIfNotInitialized();
executor.execute(() -> {
sleep(DELAY);
value.addAndGet(val);
});
}
public long getValue() {
throwIfNotInitialized();
return value.longValue();
}
private void sleep(int delay) {
try {
Thread.sleep(delay);
} catch (InterruptedException e) {
}
}
private void throwIfNotInitialized() {
if (!initialized) {
throw new IllegalStateException("Service is not initialized");
}
}
}
4. 待ち合わせによるテスト
それでは、テストクラスを作成しましょう。
public class AsyncServiceLongRunningManualTest {
private AsyncService asyncService;
@Before
public void setUp() {
asyncService = new AsyncService();
}
//...
}
テストでは、 initialize メソッドを呼び出した後、指定されたタイムアウト期間(デフォルトは10秒)内にサービスの初期化が発生するかどうかを確認します。
このテストケースは、サービスの初期化状態が変化するのを待つか、状態変化が発生しない場合はConditionTimeoutExceptionをスローするだけです。
ステータスは、指定された初期遅延(デフォルトは100ms)の後、定義された間隔(デフォルトは100ms)でサービスをポーリングするCallableによって取得されます。 ここでは、タイムアウト、間隔、および遅延のデフォルト設定を使用しています。
asyncService.initialize();
await()
.until(asyncService::isInitialized);
ここでは、 await — Awaitilityクラスの静的メソッドの1つを使用します。 ConditionFactoryクラスのインスタンスを返します。 読みやすさを向上させるために、与えられたのような他の方法を使用することもできます。
デフォルトのタイミングパラメータは、Awaitilityクラスの静的メソッドを使用して変更できます。
Awaitility.setDefaultPollInterval(10, TimeUnit.MILLISECONDS);
Awaitility.setDefaultPollDelay(Duration.ZERO);
Awaitility.setDefaultTimeout(Duration.ONE_MINUTE);
ここでは、 duration クラスの使用法を確認できます。このクラスは、最も頻繁に使用される期間に役立つ定数を提供します。
各awaitcallにカスタムタイミング値を提供することもできます。 ここでは、初期化は最大で5秒後、少なくとも100ミリ秒後に100ミリ秒のポーリング間隔で発生すると予想されます。
asyncService.initialize();
await()
.atLeast(Duration.ONE_HUNDRED_MILLISECONDS)
.atMost(Duration.FIVE_SECONDS)
.with()
.pollInterval(Duration.ONE_HUNDRED_MILLISECONDS)
.until(asyncService::isInitialized);
言及する価値があります ConditionFactory 次のような追加のメソッドが含まれていますと 、 それから 、 と 、
5. マッチャーの使用
Awaitilityでは、 hamcrest マッチャーを使用して、式の結果を確認することもできます。 たとえば、 addValue メソッドを呼び出した後、long値が期待どおりに変更されていることを確認できます。
asyncService.initialize();
await()
.until(asyncService::isInitialized);
long value = 5;
asyncService.addValue(value);
await()
.until(asyncService::getValue, equalTo(value));
この例では、最初の await 呼び出しを使用して、サービスが初期化されるまで待機していることに注意してください。 それ以外の場合、getValueメソッドはIllegalStateExceptionをスローします。
6. 例外を無視する
場合によっては、非同期ジョブが実行される前にメソッドが例外をスローする状況があります。 私たちのサービスでは、サービスが初期化される前にgetValueメソッドを呼び出すことができます。
待機性は、テストに失敗することなくこの例外を無視する可能性を提供します。
たとえば、 IllegalStateException を無視して、初期化直後にgetValueの結果がゼロに等しいことを確認しましょう。
asyncService.initialize();
given().ignoreException(IllegalStateException.class)
.await().atMost(Duration.FIVE_SECONDS)
.atLeast(Duration.FIVE_HUNDRED_MILLISECONDS)
.until(asyncService::getValue, equalTo(0L));
7. プロキシの使用
セクション2で説明したように、プロキシベースの条件を使用するには、awaitility-proxyを含める必要があります。 プロキシの考え方は、 Callable またはラムダ式を実装せずに、条件に対して実際のメソッド呼び出しを提供することです。
AwaitilityClassProxy.to staticメソッドを使用して、AsyncServiceが初期化されていることを確認しましょう。
asyncService.initialize();
await()
.untilCall(to(asyncService).isInitialized(), equalTo(true));
8. フィールドへのアクセス
Awaitilityは、プライベートフィールドにアクセスして、それらに対してアサーションを実行することもできます。 次の例では、サービスの初期化ステータスを取得する別の方法を確認できます。
asyncService.initialize();
await()
.until(fieldIn(asyncService)
.ofType(boolean.class)
.andWithName("initialized"), equalTo(true));
9. 結論
このクイックチュートリアルでは、Awaitilityライブラリを紹介し、非同期システムのテスト用の基本的なDSLに精通し、ライブラリを柔軟で実際のプロジェクトで簡単に使用できるようにするいくつかの高度な機能を確認しました。
いつものように、すべてのコード例はGithubで利用できます。