MVP – budowanie unit testów

Model View Presenter jest jednym z najprostszych i zarazem najpopularniejszych podejść konstruowania logiki widoków aplikacji. Pozwala nam osiągnąć separację między komponentami androidowymi (takimi jak Activity czy Fragment), a logiką biznesową naszej aplikacji. Ze względu na wspomniane rozdzielenie odpowiedzialności, te podejście do projektowania ekranów łatwo jest pokryć unit testami.

Naszym zadaniem jest zbudowanie prostego ekranu, na którym wyświetlimy listę kryptowalut z zewnętrznego API, pokażemy kręcące się kółko ładowania, a także poinformujemy użytkownika o ewentualnym błędzie pobierania danych.

Te wymagania możemy przełożyć na kontrakt MVP — definicje wspólnych interakcji widoku (View) i presentera:

W unit testach będziemy sprawdzać wzajemne interakcje interfejsów View oraz Presenter

Weźmy na warsztat pierwszy przypadek — zachowanie ekranu naszej aplikacji gdy uda się pobrać listę z API.

Dane do naszego presentera będzie dostarczać UseCase: FetchCryptoListUseCase. W tym interfejsie tworzymy jedna metodę — fetchData, która zwróci nam obiekt klasy Single<List<Data>> z RxJavy:

Dana jest również prosta implementacja presentera — w metodzie attach zachowujemy referencje do widoku i prosimy use case o dane:

Tworzenie testu

Niezależnie od tego w jakim stylu chcemy pisać nasze testy, powinniśmy wpasować je w strukturę arrange, act, assert:

Arrange: przygotowanie zachowań zewnętrznych komponentów (np. co ma zwracać dany use case). W specyfikacjach BDD możemy się spotkać z określeniem tej części jako GIVEN

Act: wywołanie metody systemu który testujemy — czasem określane WHEN

Assert: weryfikacja zachowania, czyli sprawdzenie tego, co się stało po poprzednich krokach i porównanie z oczekiwanym zachowaniem, czyli THEN.

Na potrzeby unit testów będziemy się posługiwać test doubles — implementacjami konkretnych komponentów które udają zachowanie prawdziwego systemu. Do przygotowania mocków użyjemy biblioteki Mockk:

Happy path oraz error path

Nasz pierwszy test case sprawdzi podstawową ścieżkę, czyli wyświetlanie danych na view, gdy uda się pobrać dane.

Na początek przygotujemy część arrange. Każde wywołanie execute() FetchCurrenyUseCase będzie zwracać Single.just(fakeCurrencyList). Zamockujemy również view, aby móc weryfikować wywołania jego metod.

Tworzymy nasz System Under Test (SUT), czyli MainScreenPresenter i przekazujemy do niego zamockowane zależności:

Po części act — czyli presenter.attach(view) przechodzimy do assert. W naszym wypadku chcemy sprawdzić czy konkretna lista została przekazana do metody view.displayData().

Możemy również napisać test case dla innego przypadku — wyświetlenia błędu na view, gdy use case wyemituje błąd:

Co z Schedulerami?

W unit testach generalnie nie powinniśmy mieć ścisłych zależności do klas specyficznych dla Androida — przykładem tutaj może być Context, Activity czy też AndroidSchedulers.mainThread(). Jeżeli musimy w naszej logice biznesowej użyć którejś z tych rzeczy (na przykład chcemy mieć pewność, że ustawiliśmy dane na view będąc na wątku UI), warto wprowadzić dodatkową warstwę abstrakcji — SchedulerProvider.

W naszych testach użyjemy jeszcze innej implementacji, przekierujemy każdy Scheduler na Schedulers.trampoline() — dzięki temu nasze asynchroniczne operacje staną się synchroniczne.

Pozostałe interakcje presentera i widoku

Zostało nam jeszcze do przetestowania zachowanie “isLoading”. Zastanówmy się ponownie w jakich warunkach chcemy aby wyświetlał nam się na ekranie loading indicator. Widok ten powinien być wyświetlony w trakcie ładowania danych. Do tego testu podejdziemy w trochę inny sposób. Nasz arrange -> act -> assert przedstawimy jako ON start fetching data IT SHOULD show progress indicator, a chowanie tego widoku rozbijemy na dwa przypadki: ON fetching data complete oraz ON fetching data error:

W pierwszym teście nasz zamockowany use case zwraca Single.never() — jest to specjalna konsturkacja Single , która po zasubskrybowaniu wywoła jedynie callback onSubscribe. Pozwoli nam to sprawdzić konkretny przebieg wydarzeń w izolacji — use case nic nie zwróci — ani wartości, ani błędu.

Pozostałe dwa przypadki — chowanie loading indicatora zrealizujemy podając do naszego mocka odpowiednio konkretne dane i później error.

Dlaczego nie przetestowaliśmy zachowania loading indicatora przy okazji sprawdzania wyświetlania danych na widoku?

Po pierwsze, starając się pisać unit testy powinniśmy zadbać o sprawdzenie konkretnego logicznego przypadku zachowania systemu. Całe zachowanie również warto przetestować — do tego celu świetnie nadają się testy instrumentalne.

Po drugie, większość frameworków testowych po pierwszej nieudanej asercji przerwie dalsze wykonywanie testu. Istnieje wtedy szansa, że w przyszłości naprawiając daną część kodu czy też dopisując nową funkcjonalność, testy nie dadzą nam pełnej informacji o tym co poszło źle.

Podsumowanie

Do naszej prostej logiki — pobrania danych i wyświetlenia ich na widoku stworzyliśmy kilka unit testów weryfikujących zachowanie i wzajemne interakcje komponentów. Możemy się postarać, aby przetestować każdy możliwy przypadek, pokryć testami każdą linijkę presentera. Często wysokie pokrycie kodu testami może nam dać fałszywe poczucie bezpieczeństwa. Niestety sprawdzenie każdego możliwego elementu logiki nie uchroni nas przed źle zdefiniowanymi wymaganiami.

Pisanie unit testów powinno stać się nawykiem developera który chce podążać ścieżką czystej architektury. W codziennej pracy warto dbać o jakość zarówno kodu produkcyjnego jak i testów.

Źródła

Repozytorium aplikacji nad którą pracowaliśmy jest dostępna na Githubie:
https://github.com/rozkminiacz/MVP-Testing

Do mockowania wykorzystaliśmy bibliotekę MockK:
https://github.com/mockk/mockk

A tutaj możemy znaleźć przykłady unit testów w Kotlinie z użyciem popularnych frameworków:
https://github.com/rozkminiacz/KotlinUnitTesting

Jarosław Michalik
Jarosław Michalik

Android Developer – tworzy i rozwija aplikacje mobilne we współpracy ze startupami. W codziennej pracy jest wielkim fanem pisania testów oraz wszelkiego rodzaju automatyzacji. Aktywny członek grupy GDG Kraków. Lubi dzielić się swoimi przemyśleniami i wiedzą – pisze artykuły na bloga i często występuje jako prelegent na branżowych konferencjach.

.

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *