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.