2016-07-20 24 views
0

Derzeit möchte ich überprüfen, ob das SUT (System im Test) die Anforderungen ordnungsgemäß an externe Systeme sendet. Ich verwende externe Mocks (über HTTP) und Message Queues in der QA-Umgebung (sie können von den realen Systemen über die Konfiguration ausgetauscht werden).Async-Test ohne Thread.sleep

Problem: Behauptungen mit diesen externen Systemen zu tun, verbreite ich thread.sleep() über die Integrationstests und das ist schlecht. Ich möchte dies für eine Anfrage mit Rückruf und eine Test-Suite, die einen Listener für diese Anfrage bietet ersetzen. Gibt es dafür schon eine Lösung?

+1

Zwei hilfreiche Tools - [Verfügbarkeit] (https://github.com/awaitility/awaitility) und [ConcurrentUnit] (https://github.com/jhalterman/concurrentunit). – Jonathan

Antwort

1

Für das Szenario, das Sie beschreiben, kann ich die Verwendung von Instanzen der Klasse java.util.concurrent.CyclicBarrier empfehlen - sehen Sie das folgende einfache Beispiel eines Threads, der eine Berechnung asynchron ausführt (hier reduziert auf die Einstellung eines "Prozentsatzes getan" -Wertes zuerst auf 50 und dann) zu 100) und erlauben, dass der "Prozentsatz fertig" -Wert vom Hauptthread abgerufen wird.

Hier ist der SUT-Klasse:

public class AsyncProcess implements Runnable { 

    private int percentageDone = 0; 

    public int getPercentageDone() { return percentageDone; } 

    public void doFirstHalf() { percentageDone = 50; } 

    public void doSecondHalf() { percentageDone = 100; } 

    public void run() { 
    doFirstHalf(); 
    doSecondHalf(); 
    } 

} 

Für Unit-Tests dieser, ich benutze JUnit 4, mit Mockito Spione die beiden Berechnungsmethoden meines SUT abzufangen (alternativ können Sie Ihre Lieblings AOP Framework verwenden).

import static org.hamcrest.Matchers.is; 
import static org.junit.Assert.assertThat; 
import static org.mockito.Mockito.doAnswer; 

import java.util.concurrent.CyclicBarrier; 

import org.junit.Test; 
import org.mockito.Mockito; 
import org.mockito.invocation.InvocationOnMock; 
import org.mockito.stubbing.Answer; 

public class AsyncProcessTest { 

    @Test 
    public void testExecute() throws Exception { 

    final CyclicBarrier firstHalfStarted = new CyclicBarrier(2); 
    final CyclicBarrier firstHalfFinished = new CyclicBarrier(2); 
    final CyclicBarrier secondHalfStarted = new CyclicBarrier(2); 
    final CyclicBarrier secondHalfFinished = new CyclicBarrier(2); 

    AsyncProcess process = Mockito.spy(new AsyncProcess()); 

    doAnswer(new Answer() { 
     public Object answer(InvocationOnMock invocation) throws Throwable { 
     firstHalfStarted.await(); 
     invocation.callRealMethod(); 
     firstHalfFinished.await(); 
     return null; 
     } 
    }).when(process).doFirstHalf(); 

    doAnswer(new Answer() { 
     public Object answer(InvocationOnMock invocation) throws Throwable { 
     secondHalfStarted.await(); 
     invocation.callRealMethod(); 
     secondHalfFinished.await(); 
     return null; 
     } 
    }).when(process).doSecondHalf(); 

    new Thread(process, "AsyncProcess").start(); 

    assertThat(process.getPercentageDone(), is(0)); 

    firstHalfStarted.await(); 
    firstHalfFinished.await(); 

    assertThat(process.getPercentageDone(), is(50)); 

    secondHalfStarted.await(); 
    secondHalfFinished.await(); 

    assertThat(process.getPercentageDone(), is(100)); 
    } 

} 

Dieser Ansatz ermöglicht eine sehr feinkörnige Kontrolle des Ausführungsflusses der beteiligten Threads. Es kann leicht zu einem "Mikro-Framework" erweitert werden, indem die CyclicBarrier-Paare eingekapselt und die Interception-Aufrufe sowie die Unterstützung für Timeouts und die korrekte Ausnahmebehandlung extrahiert werden.