Jak testujesz metody uruchamiające procesy asynchroniczne za pomocą JUnit?
Nie wiem, jak sprawić, by mój test czekał na zakończenie procesu (nie jest to dokładnie test jednostkowy, bardziej przypomina test integracyjny, ponieważ obejmuje kilka klas, a nie tylko jedną).
Odpowiedzi:
IMHO to zła praktyka, aby testy jednostkowe tworzyły wątki lub czekały na nich itp. Chcesz, aby testy te były uruchamiane w ułamkach sekund. Dlatego chciałbym zaproponować 2-etapowe podejście do testowania procesów asynchronicznych.
źródło
Alternatywą jest użycie klasy CountDownLatch .
UWAGA: nie można użyć synchronizacji ze zwykłym obiektem jako zamkiem, ponieważ szybkie wywołania zwrotne mogą zwolnić blokadę przed wywołaniem metody oczekiwania zamka. Zobacz ten post na blogu autorstwa Joe Walnesa.
EDYCJA Usunięto zsynchronizowane bloki wokół CountDownLatch dzięki komentarzom z @jtahlborn i @Ring
źródło
Możesz spróbować użyć biblioteki Awaitility . Ułatwia to testowanie systemów, o których mówisz.
źródło
CountDownLatch
(patrz odpowiedź @Martin) jest lepsze pod tym względem.Jeśli korzystasz z CompletableFuture (wprowadzony w Javie 8) lub SettableFuture (z Google Guava ), możesz zakończyć test, gdy tylko zostanie on ukończony, zamiast czekać na wcześniej ustalony czas. Twój test wyglądałby mniej więcej tak:
źródło
Rozpocznij proces i poczekaj na wynik za pomocą
Future
.źródło
Jedną z metod, którą uważam za przydatną do testowania metod asynchronicznych, jest wstrzyknięcie
Executor
instancji do konstruktora obiekt-test. W środowisku produkcyjnym instancja modułu wykonującego jest skonfigurowana do działania asynchronicznego, podczas gdy w teście można ją wyśmiewać, aby działała synchronicznie.Załóżmy, że próbuję przetestować metodę asynchroniczną
Foo#doAsync(Callback c)
,Podczas produkcji konstruowałbym
Foo
za pomocąExecutors.newSingleThreadExecutor()
instancji Executora, podczas gdy w testach prawdopodobnie konstruowałbym go za pomocą synchronicznego executora, który wykonuje następujące czynności -Teraz mój test JUnit metody asynchronicznej jest całkiem czysty -
źródło
WebClient
Nie ma nic z natury błędnego w testowaniu kodu wątkowego / asynchronicznego, szczególnie jeśli wątek jest punktem testowanego kodu. Ogólne podejście do testowania tych rzeczy polega na:
Ale to dużo płyty kotłowej na jeden test. Lepszym / prostszym podejściem jest po prostu użycie ConcurrentUnit :
Zaletą tego
CountdownLatch
podejścia jest to, że jest mniej gadatliwy, ponieważ awarie asercji występujące w dowolnym wątku są odpowiednio zgłaszane do głównego wątku, co oznacza, że test kończy się niepowodzeniem, kiedy powinien. Zapis, który porównujeCountdownLatch
podejście do ConcurrentUnit jest tutaj .Napisałem też post na blogu na ten temat dla tych, którzy chcą dowiedzieć się nieco więcej.
źródło
A może zadzwonisz
SomeObject.wait
inotifyAll
jak opisano tutaj LUB przy użyciu metody RobotiumSolo.waitForCondition(...)
LUB LUB skorzystaj z klasy, którą napisałem, aby to zrobić (zobacz komentarze i klasy testowe, jak używać)źródło
Znajduję bibliotekę socket.io do testowania logiki asynchronicznej. Wygląda to w prosty i krótki sposób za pomocą LinkedBlockingQueue . Oto przykład :
Za pomocą LinkedBlockingQueue weź API do blokowania, aż uzyskasz wynik tak samo, jak w sposób synchroniczny. I ustaw limit czasu, aby uniknąć zakładania zbyt wiele czasu na oczekiwanie na wynik.
źródło
Warto wspomnieć, że jest bardzo przydatny rozdział
Testing Concurrent Programs
w „ Współbieżności w praktyce”, który opisuje niektóre podejścia do testowania jednostkowego i podaje rozwiązania problemów.źródło
Tego właśnie używam obecnie, jeśli wynik testu jest generowany asynchronicznie.
Przy użyciu importu statycznego test brzmi całkiem nieźle. (uwaga, w tym przykładzie zaczynam wątek ilustrujący pomysł)
Jeśli
f.complete
nie zostanie wywołany, test zakończy się niepowodzeniem po upływie limitu czasu. Możesz także użyćf.completeExceptionally
do wczesnego niepowodzenia.źródło
Tutaj jest wiele odpowiedzi, ale najprostszym jest po prostu utworzenie kompletnej przyszłości i użycie jej:
Więc w moim teście:
Po prostu upewniam się, że i tak wszystkie te rzeczy zostaną nazwane. Ta technika działa, jeśli używasz tego kodu:
Przebije się przez nią, gdy wszystkie CompletableFutures są gotowe!
źródło
Unikaj testowania z równoległymi wątkami, kiedy tylko możesz (co jest przez większość czasu). Spowoduje to jedynie, że twoje testy będą łuszczące się (czasem zdają, czasem kończą się niepowodzeniem).
Tylko wtedy, gdy musisz zadzwonić do innej biblioteki / systemu, być może będziesz musiał czekać na inne wątki, w takim przypadku zawsze używaj biblioteki Awaitility zamiast
Thread.sleep()
.Nigdy nie wywoływaj
get()
ani niejoin()
przeprowadzaj testów, w przeciwnym razie testy mogą działać wiecznie na serwerze CI, na wypadek gdyby przyszłość się nigdy nie zakończyła. ZawszeisDone()
sprawdzaj w testach, zanim zadzwoniszget()
. To znaczy dla CompletionStage.toCompletableFuture().isDone()
.Podczas testowania takiej metody nieblokującej:
nie powinieneś po prostu testować wyniku, przekazując w teście ukończoną Przyszłość, powinieneś także upewnić się, że twoja metoda
doSomething()
nie blokuje się przez wywołaniejoin()
lubget()
. Jest to szczególnie ważne, jeśli używasz frameworka nieblokującego.Aby to zrobić, przetestuj z niekompletną przyszłością, którą ustawiłeś na ukończenie ręcznie:
W ten sposób, jeśli dodasz
future.join()
do doSomething (), test się nie powiedzie.Jeśli Twoja usługa korzysta z usługi ExecutorService, takiej jak in
thenApplyAsync(..., executorService)
, wówczas w swoich testach wstrzyknij jedno-wątkową usługę ExecutorService, taką jak ta z guava:Jeśli Twój kod korzysta z forkJoinPool, takiego jak
thenApplyAsync(...)
kod, aby użyć usługi ExecutorService (istnieje wiele dobrych powodów) lub użyj funkcji Oczekiwanie.Aby skrócić ten przykład, uczyniłem BarService argumentem metody zaimplementowanym w teście jako lambda Java8, zwykle będzie to wstrzykiwane odniesienie, które wyśmiewałbyś.
źródło
Wolę używać czekaj i powiadamiaj. To jest proste i jasne.
Zasadniczo musimy utworzyć ostateczne odwołanie do tablicy, które będzie używane wewnątrz anonimowej klasy wewnętrznej. Wolałbym stworzyć wartość logiczną [], ponieważ mogę ustawić wartość do kontroli, jeśli musimy czekać (). Kiedy wszystko jest zrobione, po prostu wypuszczamy asyncExecuted.
źródło
Dla wszystkich użytkowników Spring w ten sposób zwykle przeprowadzam obecnie testy integracyjne, w których występuje zachowanie asynchroniczne:
Uruchom zdarzenie aplikacji w kodzie produkcyjnym, gdy zakończy się zadanie asynchroniczne (takie jak wywołanie We / Wy). W większości przypadków to zdarzenie jest niezbędne do obsługi odpowiedzi operacji asynchronicznej podczas produkcji.
Po wdrożeniu tego zdarzenia możesz następnie użyć następującej strategii w przypadku testowym:
Aby rozwiązać ten problem, musisz najpierw uruchomić jakieś zdarzenie domeny. Korzystam z identyfikatora UUID, aby zidentyfikować ukończone zadanie, ale oczywiście możesz swobodnie korzystać z czegoś innego, o ile jest on unikalny.
(Uwaga: poniższe fragmenty kodu również wykorzystują adnotacje Lombok, aby pozbyć się kodu płyty kotła)
Sam kod produkcyjny zwykle wygląda następująco:
Następnie mogę użyć sprężyny,
@EventListener
aby złapać opublikowane zdarzenie w kodzie testowym. Nasłuchiwanie zdarzeń jest nieco bardziej zaangażowane, ponieważ musi obsługiwać dwa przypadki w sposób bezpieczny dla wątków:A
CountDownLatch
jest używane w drugim przypadku, jak wspomniano w innych odpowiedziach tutaj. Należy również zauważyć, że@Order
adnotacja w metodzie obsługi zdarzeń zapewnia, że ta metoda obsługi zdarzeń zostanie wywołana po każdym innym detektorze zdarzeń używanym w produkcji.Ostatnim krokiem jest wykonanie testowanego systemu w przypadku testowym. Używam tutaj testu SpringBoot z JUnit 5, ale powinien on działać tak samo dla wszystkich testów z kontekstem Spring.
Zauważ, że w przeciwieństwie do innych odpowiedzi tutaj, to rozwiązanie będzie również działać, jeśli wykonasz testy równolegle, a wiele wątków jednocześnie wykona kod asynchroniczny.
źródło
Jeśli chcesz przetestować logikę, nie testuj jej asynchronicznie.
Na przykład, aby przetestować ten kod, który działa na wynikach metody asynchronicznej.
W teście próbnym zależność z implementacją synchroniczną. Test jednostkowy jest całkowicie synchroniczny i trwa 150 ms.
Nie testujesz zachowania asynchronicznego, ale możesz sprawdzić, czy logika jest poprawna.
źródło