Blog

Latching und Mocking in gleichzeitigen Tests

Aktualisiert Oktober 23, 2025
4 Minuten

Gleichzeitige Tests sind schwierig, aber nicht so schwierig, wie Sie denken. Wenn Sie die richtigen Tricks anwenden, ist es machbar. Dieser Blog zeigt Ihnen einen besonderen Trick, bei dem ein Latch und ein Mock verwendet werden, um sicherzustellen, dass ein Testszenario abgeschlossen ist, bevor die Verifizierungen ausgeführt werden. Während meiner Arbeit an Spring Integration in Action habe ich mit einer sauberen Lösung für gleichzeitige Tests experimentiert. Als ich sie einigen Kollegen vorstellte, war ich angenehm überrascht von der Reaktion, die ich erhielt. Beurteilen Sie selbst, ob es den Blog wert ist.Die Hauptidee ist, ein Latch zu verwenden und es von Ihrem Mock herunterzählen zu lassen. Das hört sich trivial an (und ist es auch, um ehrlich zu sein).

Lassen Sie mich Ihnen zunächst zeigen, wie man es ohne Spott machen kann: [sourcecode language="java"] @KontextKonfiguration @RunWith(SpringJUnit4ClassRunner.class) public class ConcurrentTest { @Autowired @Qualifier("in") MessageChannel in; @Autowired @Qualifier("out") PollableChannel out; @Autowired Service-Dienst; @Test(timeout = 5000) public void shouldGoThroughPipeline() throws Exception { in.send(MessageBuilder.withPayload("test").build()); service.invoked.await(); out.receive(); } public static class Service { private CountDownLatch invoked = new CountDownLatch(1); public String serve(String input) { invoked.countDown(); Eingabe zurückgeben; } } } [/sourcecode] Eine Nachricht wird an den Kanal "in" gesendet, was zu einem asynchronen Aufruf des Dienstes führt. Ich verwende Spring Integration, um den Test asynchron zu machen, aber ich hätte genauso gut @Async, Camel, JMS oder was auch immer verwenden können. Der Grundgedanke bleibt derselbe.Dies ist bereits weitgehend schmerzlos, aber wir machen den Test noch ein bisschen sauberer, indem wir ein Mock verwenden. Sie können ein Mock mit Hilfe einer Factory-Methode in Ihren Kontext einbinden: [sourcecode language="xml"] <bean id="service" class="org.mockito.Mockito" factory-method="mock"> <constructor-arg value="iwein.samples.test.concurrent.ConcurrentTest$Service"/> </bean> [/sourcecode] Jetzt wird dieser nachgebildete Dienst automatisch in den Test eingebunden und Sie können ihn Tricks anwenden lassen: [sourcecode language="java"] @Test(timeout = 5000) public void shouldGoThroughPipeline() throws Exception { final CountDownLatch serviceInvoked = new CountDownLatch(1); given(service.serve("test")).willAnswer(new Answer(){ public Object answer(InvocationOnMock invocationOnMock) throws Throwable { serviceInvoked.countDown(); return "test"; } }); in.send(MessageBuilder.withPayload("test").build()); serviceInvoked.await(); out.receive(); } [/sourcecode] Die Antwort, die in der Methode willAnswer() übergeben wird, zählt zunächst den Latch herunter und gibt dann "test" zurück. Das funktioniert, aber es ist nicht der lesbarste Code. Bringen wir ein wenig Ordnung in den Code: [sourcecode language="java"] @KontextKonfiguration @RunWith(SpringJUnit4ClassRunner.class) public class ConcurrentTest { @Autowired @Qualifier("in") MessageChannel in; @Autowired @Qualifier("out") PollableChannel out; @Autowired Service-Dienst; @Test(timeout = 5000) public void shouldGoThroughPipeline() throws Exception { //given final CountDownLatch serviceInvoked = new CountDownLatch(1); given(service.serve("test")).willAnswer(latchedAnswer("test", serviceInvoked)); //when in.send(MessageBuilder.withPayload("test").build()); serviceInvoked.await(); //verify Nachricht<?> Nachricht = out.receive(); assertThat((String) message.getPayload(), is("test")); } privat <T> Antwort<T> latchedAntwort(final T returning, final CountDownLatch latch) { return new Antwort(){ public Object answer(InvocationOnMock invocationOnMock) throws Throwable { latch.countDown(); Rückkehr zurück; } }; } //In main hätten Sie eine Klasse zum Nachahmen, aber hier dient eine Schnittstelle als Beispiel public static interface Service { public String serve(String input); } } [/sourcecode] Das ist ein wirklich höflicher Code für einen gleichzeitigen Test, finden Sie nicht? Ich überlege, die latchedAnswer-Methode in ein Testprogramm aufzunehmen, das mit Spring Integration geliefert wird, aber wie Sie sehen können, passt sie auch ganz gut in Ihren Kopierpuffer. Der Punkt ist, dass auch ohne die Verwendung eines Concurrent-Test-Frameworks (ich weiß, dass es sie gibt) einfache Concurrent-Tests so einfach sein können, wie sie sein sollten. Caveats:

  • Wenn Sie eine Instanz des falschen Typs an latchedAnswer übergeben, erhalten Sie aufgrund eines generischen Problems mit Mockito keine Compiler-Warnung.
  • Wenn Sie einen Dienst durch ein Mock ersetzen, könnten Frameworks, die Reflection verwenden, ein wenig durcheinander kommen. Für dieses Beispiel musste ich dem Service Activator-Element ein Methodenattribut hinzufügen, das ohne den Mock nicht nötig gewesen wäre.
  • Legen Sie immer eine Zeitüberschreitung fest. Sie möchten nicht, dass der Test ewig auf Ihrem Build-Server läuft, wenn Sie das Pech haben, ihn zu zerstören.

Den Quellcode für dieses Beispiel finden Sie auf github. Viel Spaß!

Contact

Let’s discuss how we can support your journey.