1前書き
非同期システムに関する一般的な問題は、ビジネスロジックに焦点を当てており、同期、タイムアウト、および同時実行制御に汚染されていない、読み取り可能なテストを書くのが難しいということです。
この記事では、
Awaitility
– 非同期システムテスト用の単純なドメイン固有言語(DSL)を提供するライブラリ
を見ていきます。
Awaitilityを使用すると、システムからの期待を読みやすいDSLで表現できます。
2依存関係
__pom.xmlに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
Maven Centralのライブラリ。
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 Awaitility
によるテスト
それでは、テストクラスを作成しましょう。
public class AsyncServiceTest {
private AsyncService asyncService;
@Before
public void setUp() {
asyncService = new AsyncService();
}
//...
}
私たちのテストは
initialize
メソッドを呼び出した後、私たちのサービスの初期化が指定されたタイムアウト期間(デフォルト10秒)以内に起こるかどうかチェックします。
このテストケースは単にサービスの初期化状態が変化するのを待つか、または状態の変化が起こらない場合は__ConditionTimeoutExceptionをスローします。
ステータスは
Callable
によって取得され、指定された初期遅延(デフォルト100ms)の後、定義された間隔(100msデフォルト)でサービスにポーリングします。ここでは、タイムアウト、間隔、および遅延のデフォルト設定を使用しています。
asyncService.initialize();
await()
.until(asyncService::isInitialized);
ここでは、
await
–
Awaitility
クラスの静的メソッドの1つを使用します。
ConditionFactory
クラスのインスタンスを返します。読みやすさを増すために
given
のような他の方法を使うこともできます。
デフォルトのタイミングパラメータは、
Awaitility
クラスの静的メソッドを使用して変更できます。
Awaitility.setDefaultPollInterval(10, TimeUnit.MILLISECONDS);
Awaitility.setDefaultPollDelay(Duration.ZERO);
Awaitility.setDefaultTimeout(Duration.ONE__MINUTE);
ここでは、最も頻繁に使用される期間に有用な定数を提供する
Duration
クラスの使用方法を見ることができます。
-
それぞれの
await
呼び出しに** カスタムタイミング値を提供することもできます。ここでは、初期化が最大で5秒後、少なくとも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などの追加のメソッドが含まれていることを言及する価値があります。これらのメソッドは何もしないで、単に
this__を返しますが、テスト条件の読みやすさを向上させるのに役立ちます。
5 Matcherを使う
待ち時間は式の結果をチェックするための
hamcrest
matcherの使用も可能にします。たとえば、
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
またはλ式を実装せずに、条件に対する実際のメソッド呼び出しを提供することです。
AsyncService
が初期化されていることを確認するには、
AwaitilityClassProxy.to
静的メソッドを使用します。
asyncService.initialize();
await()
.untilCall(to(asyncService).isInitialized(), equalTo(true));
8フィールドへのアクセス
待機はプライベートフィールドにアクセスしてアサーションを実行することもできます。
次の例では、サービスの初期化ステータスを取得するための別の方法を見ることができます。
asyncService.initialize();
await()
.until(fieldIn(asyncService)
.ofType(boolean.class)
.andWithName("initialized"), equalTo(true));
9結論
このクイックチュートリアルでは、Awaitilityライブラリを紹介し、非同期システムをテストするための基本的なDSLについて理解し、実際のプロジェクトでライブラリを柔軟かつ使いやすくする高度な機能について説明しました。
いつものように、すべてのコード例はhttps://github.com/eugenp/tutorials/tree/master/libraries[on Github]から入手できます。