Verwendung von JUnit zum Testen asynchroner Prozesse

Lesezeit: 7 Minuten

Benutzer-Avatar
Sam

Wie testet man mit JUnit Methoden, die asynchrone Prozesse auslösen?

Ich weiß nicht, wie ich meinen Test auf das Ende des Prozesses warten lassen soll (es ist nicht gerade ein Komponententest, sondern eher ein Integrationstest, da er mehrere Klassen und nicht nur eine umfasst).

  • Sie könnten JAT (Java Asynchronous Test) ausprobieren: bitbucket.org/csolar/jat

    – cs0lar

    19. Januar 2013 um 15:20 Uhr

  • JAT hat 1 Beobachter und wurde seit 1,5 Jahren nicht aktualisiert. Awaitility wurde erst vor einem Monat aktualisiert und befindet sich zum Zeitpunkt der Erstellung dieses Artikels auf Version 1.6. Ich bin mit keinem der beiden Projekte verbunden, aber wenn ich in eine Erweiterung meines Projekts investieren würde, würde ich Awaitility zu diesem Zeitpunkt mehr Glauben schenken.

    – Les Hazlewood

    3. September 2014 um 20:14 Uhr


  • JAT hat noch keine Updates: “Last updated 2013-01-19”. Sparen Sie sich einfach die Zeit, um dem Link zu folgen.

    – Dämon

    17. Oktober 2017 um 8:25 Uhr

  • @LesHazlewood, ein Beobachter ist schlecht für JAT, aber seit Jahren keine Updates … Nur ein Beispiel. Wie oft aktualisieren Sie den Low-Level-TCP-Stack Ihres Betriebssystems, wenn es nur funktioniert? Eine Alternative zu JAT wird unter stackoverflow.com/questions/631598/… beantwortet.

    – Benutzer1742529

    6. August 2019 um 8:36 Uhr


Benutzer-Avatar
Martin

TL;DR; Leider gibt es keine eingebaute Lösung noch (zum Zeitpunkt des Schreibens 2022), daher steht es Ihnen frei, alles zu verwenden und / oder zu implementieren, was zu Ihrer Situation passt.

Beispiel

Eine Alternative ist die Verwendung von CountDownLatch Klasse.

public class DatabaseTest {

    /**
     * Data limit
     */
    private static final int DATA_LIMIT = 5;

    /**
     * Countdown latch
     */
    private CountDownLatch lock = new CountDownLatch(1);

    /**
     * Received data
     */
    private List<Data> receiveddata;

    @Test
    public void testDataRetrieval() throws Exception {
        Database db = new MockDatabaseImpl();
        db.getData(DATA_LIMIT, new DataCallback() {
            @Override
            public void onSuccess(List<Data> data) {
                receiveddata = data;
                lock.countDown();
            }
        });

        lock.await(2000, TimeUnit.MILLISECONDS);

        assertNotNull(receiveddata);
        assertEquals(DATA_LIMIT, receiveddata.size());
    }
}

HINWEIS Sie können nicht einfach verwendet werden synchronisiert mit einem regulären Objekt als Sperre, da schnelle Rückrufe die Sperre freigeben können, bevor die Wait-Methode der Sperre aufgerufen wird. Sehen Dies Blogbeitrag von Joe Walnes.

BEARBEITEN Dank der Kommentare von @jtahlborn und @Ring wurden synchronisierte Blöcke um CountDownLatch entfernt

  • Schöne Beschreibung von CountDownLatch @Raz

    – Martin

    6. August 2014 um 17:58 Uhr

  • Wenn Sie überprüfen, ob onSuccess aufgerufen wurde, sollten Sie sicherstellen, dass lock.await true zurückgibt.

    – Gilbert

    17. September 2014 um 8:14 Uhr

  • Ich glaube nicht, dass diese Antwort sein Problem wirklich löst. Diese Antwort Es ist eine Alternative, wenn der Entwickler einen CountDownLatch als Parameter an einen anderen Thread übergeben kann, um den CountDown () zu machen, was nicht immer möglich ist.

    – Dherik

    11. April 2017 um 13:04 Uhr


  • Ich würde das Timeout aus dem entfernen await() Anruf. Verwenden CountDownLatch sollte garantieren, dass Sie das Ergebnis “so schnell wie möglich” erhalten, was es unnötig macht.

    – Georg Aristy

    11. April 2018 um 15:27 Uhr

  • @Martin das wäre richtig, aber es würde bedeuten, dass Sie ein anderes Problem haben, das behoben werden muss.

    – Georg Aristy

    11. April 2018 um 17:31 Uhr

Benutzer-Avatar
Johann

Sie können versuchen, die zu verwenden Erwartung Bibliothek. Es macht es einfach, die Systeme zu testen, von denen Sie sprechen.

  • Ein freundlicher Haftungsausschluss: Johan ist der Hauptverantwortliche für das Projekt.

    – dbm

    18. August 2017 um 8:04 Uhr


  • Leidet unter dem grundsätzlichen Problem des Müssens Warten (Einheitentests müssen ausgeführt werden schnell). Idealerweise möchten Sie wirklich keine Millisekunde länger als nötig warten, also denke ich mit CountDownLatch (siehe Antwort von @Martin) ist diesbezüglich besser.

    – Georg Aristy

    11. April 2018 um 15:25 Uhr

  • Wirklich unglaublich.

    – R. Karlus

    20. März 2019 um 20:08 Uhr

  • Dies ist die perfekte Bibliothek, die meine Testanforderungen für die asynchrone Prozessintegration erfüllt. Wirklich unglaublich. Die Bibliothek scheint gut gepflegt zu sein und verfügt über grundlegende bis fortgeschrittene Funktionen, die meiner Meinung nach für die meisten Szenarien ausreichen. Danke für den tollen Hinweis!

    – Tanvir

    18. September 2019 um 23:32 Uhr

  • Wirklich toller Vorschlag. Vielen Dank

    – Königstiger

    12. Februar 2020 um 11:32 Uhr

Benutzer-Avatar
Matt Sgarlata

Wenn Sie eine verwenden ErgänzbarZukunft (eingeführt in Java 8) oder a SettableFuture (aus Google Guave), können Sie Ihren Test sofort beenden, anstatt eine voreingestellte Zeit zu warten. Ihr Test würde in etwa so aussehen:

CompletableFuture<String> future = new CompletableFuture<>();
executorService.submit(new Runnable() {         
    @Override
    public void run() {
        future.complete("Hello World!");                
    }
});
assertEquals("Hello World!", future.get());

Notiz dass es eine Bibliothek gibt, die bietet CompletableFuture für Prä-Java-8, das sogar dieselben Namen verwendet (und alle zugehörigen Java-8-Klassen bereitstellt), wie:

net.sourceforge.streamsupport:streamsupport-minifuture:1.7.4

Dies ist nützlich für die Android-Entwicklung, wo wir Codes mit Geräten vor Android-7 kompatibel halten wollen, selbst wenn wir mit JDK-v11 bauen.

  • … und wenn Sie mit Java-weniger als acht nicht weiterkommen, versuchen Sie es mit Guaven SettableFuture was so ziemlich das gleiche macht

    – Markus T

    19. Juli 2014 um 20:44 Uhr

IMHO ist es eine schlechte Praxis, Unit-Tests zu erstellen oder auf Threads zu warten usw. Sie möchten, dass diese Tests in Sekundenbruchteilen ausgeführt werden. Aus diesem Grund möchte ich einen zweistufigen Ansatz zum Testen asynchroner Prozesse vorschlagen.

  1. Testen Sie, ob Ihr asynchroner Prozess ordnungsgemäß übermittelt wird. Sie können das Objekt verspotten, das Ihre asynchronen Anfragen akzeptiert, und sicherstellen, dass der übermittelte Job die richtigen Eigenschaften hat usw.
  2. Testen Sie, ob Ihre asynchronen Rückrufe die richtigen Dinge tun. Hier können Sie den ursprünglich gesendeten Job nachahmen und davon ausgehen, dass er ordnungsgemäß initialisiert wurde, und überprüfen, ob Ihre Rückrufe korrekt sind.

Starten Sie den Vorgang und warten Sie mit a auf das Ergebnis Future.

Eine Methode, die ich zum Testen asynchroner Methoden als ziemlich nützlich empfunden habe, ist das Einfügen von an Executor -Instanz im Konstruktor des zu testenden Objekts. In der Produktion ist die Executor-Instanz so konfiguriert, dass sie asynchron ausgeführt wird, während sie im Test so simuliert werden kann, dass sie synchron ausgeführt wird.

Angenommen, ich versuche, die asynchrone Methode zu testen Foo#doAsync(Callback c),

class Foo {
  private final Executor executor;
  public Foo(Executor executor) {
    this.executor = executor;
  }

  public void doAsync(Callback c) {
    executor.execute(new Runnable() {
      @Override public void run() {
        // Do stuff here
        c.onComplete(data);
      }
    });
  }
}

In der Produktion würde ich konstruieren Foo mit einem Executors.newSingleThreadExecutor() Executor-Instanz während des Tests Ich würde sie wahrscheinlich mit einem synchronen Executor erstellen, der Folgendes tut –

class SynchronousExecutor implements Executor {
  @Override public void execute(Runnable r) {
    r.run();
  }
}

Jetzt ist mein JUnit-Test der asynchronen Methode ziemlich sauber —

@Test public void testDoAsync() {
  Executor executor = new SynchronousExecutor();
  Foo objectToTest = new Foo(executor);

  Callback callback = mock(Callback.class);
  objectToTest.doAsync(callback);

  // Verify that Callback#onComplete was called using Mockito.
  verify(callback).onComplete(any(Data.class));

  // Assert that we got back the data that we expected.
  assertEquals(expectedData, callback.getData());
}

Es ist grundsätzlich nichts falsch daran, Threaded/Async-Code zu testen, insbesondere wenn Threading dies ist Der Punkt des Codes, den Sie testen. Der allgemeine Ansatz zum Testen dieses Zeugs ist:

  • Blockieren Sie den Haupttest-Thread
  • Erfassen Sie fehlgeschlagene Behauptungen von anderen Threads
  • Entsperren Sie den Haupttest-Thread
  • Werfen Sie alle Fehler erneut aus

Aber das ist eine Menge Boilerplate für einen Test. Ein besserer/einfacherer Ansatz ist einfach zu verwenden ConcurrentUnit:

  final Waiter waiter = new Waiter();

  new Thread(() -> {
    doSomeWork();
    waiter.assertTrue(true);
    waiter.resume();
  }).start();

  // Wait for resume() to be called
  waiter.await(1000);

Der Vorteil davon gegenüber der CountdownLatch Ansatz ist, dass es weniger ausführlich ist, da Behauptungsfehler, die in einem Thread auftreten, ordnungsgemäß an den Haupt-Thread gemeldet werden, was bedeutet, dass der Test fehlschlägt, wenn er sollte. Eine Beschreibung, die die vergleicht CountdownLatch Ansatz für ConcurrentUnit ist hier.

Ich habe auch eine geschrieben Blogeintrag zum Thema für diejenigen, die etwas mehr Details erfahren möchten.

1016040cookie-checkVerwendung von JUnit zum Testen asynchroner Prozesse

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy