Einführung in Awaitlity

Einführung in die Erwartung

1. Einführung

Ein häufiges Problem bei asynchronen Systemen besteht darin, dass es schwierig ist, lesbare Tests für sie zu schreiben, die sich auf die Geschäftslogik konzentrieren und nicht durch Synchronisierungen, Zeitüberschreitungen und Parallelitätskontrolle verschmutzt sind.

In diesem Artikel werfen wir einen Blick aufAwaitility — a library which provides a simple domain-specific language (DSL) for asynchronous systems testing.

Mit Awaitility können wirexpress our expectations from the system in an easy-to-read DSL.

2. Abhängigkeiten

Wir müssen unserenpom.xml. Wartbarkeitsabhängigkeiten hinzufügen

Dieawaitility-Bibliothek ist für die meisten Anwendungsfälle ausreichend. Wenn wir proxybasierte Bedingungen, verwenden möchten, müssen wir auch die Bibliothekawaitility-proxy bereitstellen:


    org.awaitility
    awaitility
    3.0.0
    test


    org.awaitility
    awaitility-proxy
    3.0.0
    test

Sie finden die neueste Version der Bibliothekenawaitility undawaitility-proxy in Maven Central.

3. Erstellen eines asynchronen Dienstes

Schreiben wir einen einfachen asynchronen Dienst und testen ihn:

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. Testen mit Wartbarkeit

Erstellen wir nun die Testklasse:

public class AsyncServiceLongRunningManualTest {
    private AsyncService asyncService;

    @Before
    public void setUp() {
        asyncService = new AsyncService();
    }

    //...
}

Unser Test prüft, ob die Initialisierung unseres Dienstes innerhalb eines bestimmten Zeitlimits (Standard 10s) nach dem Aufrufen der Methodeinitializeerfolgt.

Dieser Testfall wartet lediglich darauf, dass sich der Dienstinitialisierungsstatus ändert, oder löst einConditionTimeoutException aus, wenn die Statusänderung nicht auftritt.

Der Status wird durch einCallable erhalten, das unseren Service in definierten Intervallen (Standard 100 ms) nach einer bestimmten anfänglichen Verzögerung (Standard 100 ms) abfragt. Hier verwenden wir die Standardeinstellungen für Timeout, Intervall und Verzögerung:

asyncService.initialize();
await()
  .until(asyncService::isInitialized);

Hier verwenden wirawait - eine der statischen Methoden der KlasseAwaitility. Es gibt eine Instanz der KlasseConditionFactoryzurück. Wir können auch andere Methoden wiegiven verwenden, um die Lesbarkeit zu verbessern.

Die Standard-Timing-Parameter können mit statischen Methoden aus der KlasseAwaitilitygeändert werden:

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

Hier sehen wir die Verwendung der KlasseDuration, die nützliche Konstanten für die am häufigsten verwendeten Zeiträume liefert.

Wir können auchprovide custom timing values for each await call. Hier erwarten wir, dass die Initialisierung spätestens nach fünf Sekunden und mindestens nach 100 ms mit Abfrageintervallen von 100 ms erfolgt:

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

Es ist erwähnenswert, dassConditionFactory zusätzliche Methoden wiewith,then,and,given. enthält. Diese Methoden tun nichts und geben nurthiszurück ) s, aber sie könnten nützlich sein, um die Lesbarkeit der Testbedingungen zu verbessern.

5. Matcher verwenden

Die Wartbarkeit ermöglicht auch die Verwendung vonhamcrest Matchern, um das Ergebnis eines Ausdrucks zu überprüfen. Zum Beispiel können wir überprüfen, ob sich der Wert vonlongnach dem Aufruf der MethodeaddValuewie erwartet ändert:

asyncService.initialize();
await()
  .until(asyncService::isInitialized);
long value = 5;
asyncService.addValue(value);
await()
  .until(asyncService::getValue, equalTo(value));

Beachten Sie, dass wir in diesem Beispiel den ersten Aufruf vonawaitverwendet haben, um zu warten, bis der Dienst initialisiert ist. Andernfalls würde die MethodegetValueIllegalStateException auslösen.

6. Ausnahmen ignorieren

Manchmal tritt eine Situation auf, in der eine Methode eine Ausnahme auslöst, bevor ein asynchroner Job ausgeführt wird. In unserem Service kann es sich um einen Aufruf dergetValue-Methode handeln, bevor der Service initialisiert wird.

Awaitility bietet die Möglichkeit, diese Ausnahme zu ignorieren, ohne dass ein Test fehlschlägt.

Überprüfen Sie beispielsweise, ob das Ergebnis vongetValuedirekt nach der Initialisierung gleich Null ist, und ignorieren SieIllegalStateException:

asyncService.initialize();
given().ignoreException(IllegalStateException.class)
  .await().atMost(Duration.FIVE_SECONDS)
  .atLeast(Duration.FIVE_HUNDRED_MILLISECONDS)
  .until(asyncService::getValue, equalTo(0L));

7. Proxy verwenden

Wie in Abschnitt 2 beschrieben, müssen wirawaitility-proxy einschließen, um Proxy-basierte Bedingungen zu verwenden. Die Idee des Proxys besteht darin, echte Methodenaufrufe für Bedingungen ohne Implementierung einesCallable- oder Lambda-Ausdrucks bereitzustellen.

Verwenden Sie die statische MethodeAwaitilityClassProxy.to, um zu überprüfen, obAsyncService initialisiert ist:

asyncService.initialize();
await()
  .untilCall(to(asyncService).isInitialized(), equalTo(true));

8. Zugriff auf Felder

Awaitility kann sogar auf private Felder zugreifen, um darauf Aussagen zu treffen. Im folgenden Beispiel sehen wir einen anderen Weg, um den Initialisierungsstatus unseres Dienstes zu ermitteln:

asyncService.initialize();
await()
  .until(fieldIn(asyncService)
  .ofType(boolean.class)
  .andWithName("initialized"), equalTo(true));

9. Fazit

In diesem kurzen Tutorial stellten wir die Awaitility-Bibliothek vor, machten uns mit den grundlegenden DSL-Funktionen zum Testen von asynchronen Systemen vertraut und sahen einige erweiterte Funktionen, die die Bibliothek in realen Projekten flexibel und benutzerfreundlich machen.

Wie immer sind alle Codebeispieleon Github verfügbar.