Awaitlityの紹介

Awaitlityの概要

1. 前書き

非同期システムの一般的な問題は、ビジネスロジックに焦点を当て、同期、タイムアウト、同時実行制御で汚染されていない、読み取り可能なテストを作成するのが難しいことです。

この記事では、Awaitility — a library which provides a simple domain-specific language (DSL) for asynchronous systems testingを見ていきます。

Awaitilityを使用すると、express our expectations from the system in an easy-to-read DSL.

2. 依存関係

pom.xml.にAwaitility依存関係を追加する必要があります

ほとんどのユースケースでは、awaitilityライブラリで十分です。 プロキシベースの条件,を使用する場合は、awaitility-proxyライブラリも提供する必要があります。


    org.awaitility
    awaitility
    3.0.0
    test


    org.awaitility
    awaitility-proxy
    3.0.0
    test

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);

ここでは、Awaitilityクラスの静的メソッドの1つであるawaitを使用します。 ConditionFactoryクラスのインスタンスを返します。 読みやすさを向上させるために、givenなどの他の方法を使用することもできます。

デフォルトのタイミングパラメータは、Awaitilityクラスの静的メソッドを使用して変更できます。

Awaitility.setDefaultPollInterval(10, TimeUnit.MILLISECONDS);
Awaitility.setDefaultPollDelay(Duration.ZERO);
Awaitility.setDefaultTimeout(Duration.ONE_MINUTE);

ここでは、Durationクラスの使用法を確認できます。これは、最も頻繁に使用される期間に役立つ定数を提供します。

provide custom timing values for each await callも可能です。 ここでは、ポーリング間隔が100ミリ秒で、最大で5秒後、少なくとも100ミリ秒後に初期化が行われると予想しています。

asyncService.initialize();
await()
    .atLeast(Duration.ONE_HUNDRED_MILLISECONDS)
    .atMost(Duration.FIVE_SECONDS)
  .with()
    .pollInterval(Duration.ONE_HUNDRED_MILLISECONDS)
    .until(asyncService::isInitialized);

ConditionFactoryには、withthenandgiven.などの追加のメソッドが含まれていることに注意してください。これらのメソッドは何もせず、%(t5 )sですが、テスト条件の読みやすさを向上させるのに役立つ可能性があります。

5. マッチャーの使用

待機性により、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静的メソッドを使用して、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に精通し、ライブラリを実際のプロジェクトで柔軟かつ使いやすくするいくつかの高度な機能を見ました。

いつものように、すべてのコード例は利用可能なon Githubです。