Java

Testowanie metod asynchronicznych w Javie

Jeśli w swoim życiu napisaliście już setki klas i tysiące metod, a nie stworzyliście jeszcze żadnego testu, to marni z Was developerzy. Testy jednostkowe są naprawdę pożytecznym narzędziem, bo choć wydłużają proces produkcji, jednocześnie powodują, że wartość końcowego produktu jest wielokrotnie większa, a Wy jesteście pewni, że Wasz produkt działa. Pisanie testów metod pobierających argumenty i zwracających wyniki, które można na podstawie tych argumentów zweryfikować, to kaszka z mleczkiem. Znacznie trudniej testować metody, które uruchamiają zadania w tle, a wyniki zwracają przez tzw. callbacki. Pokaże Wam, jak uporałem się z tym problemem.

Z pewnością zetknęliście się kiedyś z modułem, który wystawiał dwa publiczne interfejsy. Jeden z nich służył do zarządzania komponentem, natomiast drugi implementowaliście w celu uzyskania informacji zwrotnych z tego modułu. Takie API można znaleźć w  kodzie C++ znacznie częściej, aniżeli w Javie czy C#. Metoda interfejsu zarządzającego często nic nie zwraca, a jeżeli już, to jedynie kod informacyjny np. ‚operacja zaplanowana do wykonania’, albo ‚nie można wykonać’. Po pewnym czasie przychodzi odpowiedź w formie wywołania metody z interfejsu notyfikacji, który uprzednio zaimplementowaliśmy i przekazaliśmy do modułu. Test takiej metody powinien sprawdzać zwracany kod, czekać (ale nie nieskończenie długo) na przyjście odpowiedzi oraz zweryfikować przysłane w notyfikacji dane.  Przykładowy projekt takich interfejsów możecie znaleźć w moim repozytorium w pakiecie pl.fones.blog.asynctest.

Klasa Engine to interfejs zarządzający, EngineNotifcations to, rzecz jasna, interfejs powiadomień, EngineTest to testy naszego komponentu, dziedziczą po AsyncBaseTestCase, a te z kolei po JUnitowych test case’ach. Będziemy testować metodę start(), która zwraca swój wynik w notyfikacji engineStarted().

Cała logika testowania asynchronicznych metod zawarta jest w klasie AsyncBaseTestCase. Metody notifyCallback() oraz waitForCallback() powinny być wołane w naszym teście, jedna w zaimplementowanym na rzecz testu interfejsie notyfikacji, a druga – w samej metodzie testującej. Ich działanie opiera się na obiekcie CountDownLunch.  Metoda waitForCallback() czeka, aż wartość licznika tego obiektu będzie równa zero, a metoda notifyCallback() zmniejsza jego wartość domyślnie ustawioną na 1. Dodatkowe struktury służą do przekazania parametrów notyfikacji i informacji, czy dany callback w ogóle nastąpił. W ten sposób możemy ustawić, jaki maksymalny czas dajemy komponentowi na odpowiedź. Poprzez tablicę flag sprawdzamy, czy dana notyfikacja nastąpiła, a przez kolekcję rezultatów dowiadujemy się o przekazanych parametrach. Wszystko w bezpieczny, wielowątkowy sposób, z wykorzystaniem dostępnych metod synchronizacji.

Jeśli znacie inny sposób, który usprawniłby moje rozwiązanie, chętnie go poznam.

  • Pingback: Testowanie metod asynchronicznych w Javie - develway.pl()

  • Piotr Maj

    Robię u siebie bardzo podobnie (też na CountDownLatch) i mi takie rozwiązanie w zupełności wystarcza. Wielokrotnie musiałem przeprojektowywać testowane klasy, aby uwzględniały możliwość odpalenia opcjonalnych callbacków w różnych miejscach. Bez nich klasy były praktycznie nietestowalne, a więc po prostu złe. Tak więc jestem na tak ;)

  • Witold Szczerba

    Co do zdania:

    „Testy jednostkowe są naprawdę pożytecznym narzędziem, bo choć wydłużają proces produkcji […]”

    raczej nie mogę się z nim zgodzić, chyba że projekt tak mały, że zanim zaczniesz go robić już jest koniec, albo jeśli testy są źle pisane i przeszkadzają w kodowaniu aplikacji (zamiast ten proces wspierać).

    Co do rozwiązania, nie przeczytałem jeszcze twojego sposobu, ale sposób testowania kodu asynchronicznego jest omówiony w jednym z rozdziałów książki „Growing Object Oriented Software, Guided by Tests”.

    Każdy kto pisze aplikacje, powinien się zapoznać z tą książką – uważam ją za jedną z najlepszych książek o tym jak powinien wyglądać proces tworzenia softu.