Podstawy testowania aplikacji na Androida

Na tej stronie omawiamy podstawowe założenia testowania aplikacji na Androida, w tym najważniejsze sprawdzone metody i ich zalety.

Zalety testowania

Testowanie jest nieodłączną częścią procesu tworzenia aplikacji. Regularnie przeprowadzając testy aplikacji, możesz sprawdzić jej poprawność, działanie i użyteczność, zanim opublikujesz ją publicznie.

Możesz przetestować aplikację ręcznie, poruszając się po niej. Możesz używać różnych urządzeń i emulatorów, zmieniać język systemu i próbować generować wszystkie błędy u użytkowników lub przechodzić przez każdy proces.

Testy ręczne są jednak niezadowalające i łatwo przeoczyć regresje w działaniu aplikacji. Testy automatyczne obejmują korzystanie z narzędzi, które wykonują testy za Ciebie. Jest to szybsze, bardziej powtarzalne i ogólnie przekazuje więcej przydatnych informacji o aplikacji na wcześniejszym etapie procesu programowania.

Rodzaje testów na Androidzie

Aplikacje mobilne są złożone i muszą dobrze działać w wielu środowiskach. Jest wiele rodzajów testów.

Temat

Na przykład dostępne są różne rodzaje testów w zależności od tematu:

  • Testy funkcjonalne: czy moja aplikacja działa tak, jak powinna?
  • Testy wydajności: czy robi to szybko i skutecznie?
  • Testowanie ułatwień dostępu: czy dobrze działa z usługami ułatwień dostępu?
  • Testy zgodności: czy działa on dobrze na każdym urządzeniu i na każdym poziomie interfejsu API?

Zakres

Testy różnią się też w zależności od rozmiaru lub stopnia izolacji:

  • Testy jednostkowe lub niewielkie testy służą do weryfikacji tylko niewielkiej części aplikacji, takiej jak metoda lub klasa.
  • Testy kompleksowe lub duże testy pozwalają w tym samym czasie zweryfikować większe części aplikacji, np. cały ekran lub wzorzec przeglądania.
  • Testy średnie znajdują się w fazie testów i sprawdź integrację między co najmniej 2 jednostkami.
Testy mogą być małe, średnie lub duże.
Rys. 1. Zakresy testów w typowej aplikacji.

Testy można klasyfikować na wiele sposobów. Najważniejszą różnicą dla deweloperów aplikacji jest miejsce, w którym przeprowadza się testy.

Testy instrumentowane i lokalne

Testy możesz przeprowadzić na urządzeniu z Androidem lub na innym komputerze:

  • Testy instrumentalne są przeprowadzane na urządzeniu z Androidem (fizyczne lub emulowane). Aplikacja jest kompilowana i instalowana razem z aplikacją testową, która wstrzykuje polecenia i odczytuje stan. Są to zwykle testy interfejsu, podczas których uruchamiasz aplikację i wchodzisz z nią w interakcję.
  • Testy lokalne są wykonywane na komputerze programisty lub na serwerze. Są one też nazywane testami po stronie hosta. Zwykle są małe i szybkie, izolujące obiekt od reszty aplikacji.
Testy mogą być przeprowadzane na urządzeniu w ramach testów z instrumentacją lub lokalnie na komputerze programistycznym.
Rys. 2. Różne rodzaje testów w zależności od miejsca, w którym są prowadzone.

Nie wszystkie testy jednostkowe mają charakter lokalny i nie wszystkie kompleksowe testy są przeprowadzane na danym urządzeniu. Przykład:

  • Duży test lokalny: możesz użyć symulatora Androida, który działa lokalnie, np. Robolectric.
  • Mały test z użyciem instrumentów: możesz sprawdzić, czy kod działa dobrze z funkcją platformy, np. z bazą danych SQLite. Możesz uruchomić ten test na wielu urządzeniach, aby sprawdzić integrację z wieloma wersjami SQLite.

Przykłady

Poniższe fragmenty kodu pokazują, jak korzystać z interfejsu w instrumentalnym teście interfejsu, który polega na kliknięciu elementu i sprawdzaniu, czy się wyświetla inny element.

Espresso

// When the Continue button is clicked
onView(withText("Continue"))
    .perform(click())

// Then the Welcome screen is displayed
onView(withText("Welcome"))
    .check(matches(isDisplayed()))

Interfejs tworzenia

// When the Continue button is clicked
composeTestRule.onNodeWithText("Continue").performClick()

// Then the Welcome screen is displayed
composeTestRule.onNodeWithText("Welcome").assertIsDisplayed()

Ten fragment kodu pokazuje część testu jednostkowego modelu ViewModel (lokalnego testu po stronie hosta):

// Given an instance of MyViewModel
val viewModel = MyViewModel(myFakeDataRepository)

// When data is loaded
viewModel.loadData()

// Then it should be exposing data
assertTrue(viewModel.data != null)

Definiowanie strategii testowania

W idealnym świecie należałoby przetestować każdy wiersz kodu w aplikacji na każdym urządzeniu, z którym jest ona zgodna. Niestety, jest to zbyt powolne i kosztowne, by zastosować je w praktyce.

Dobra strategia testowania pozwala znaleźć odpowiednią równowagę między wiernością testu, jego szybkością i niezawodnością. O rzetelności testu decyduje podobieństwo środowiska testowego do prawdziwego urządzenia. Testy o wyższej wierności są przeprowadzane na emulowanych urządzeniach lub na samym urządzeniu fizycznym. Testy o niższej wierności mogą być przeprowadzane na maszynie JVM lokalnej stacji roboczej. Testy wysokiej jakości są często wolniejsze i wymagają większej ilości zasobów, więc nie każdy test powinien być skutecznym testem.

Niepewne wyniki testów

Błędy występują nawet w przypadku poprawnie zaprojektowanych i zaimplementowanych uruchomień testowych. Jeśli na przykład przeprowadzasz test na prawdziwym urządzeniu, automatyczna aktualizacja może się rozpocząć w środku testu i sprawić, że zakończy się niepowodzeniem. Subtelne warunki wyścigu w kodzie mogą występować tylko w niewielkim stopniu. Testy, które nie zostaną zaliczone w 100% czasu, wychodzą niestabilnie.

Architektura z możliwością przetestowania

W testowej architekturze aplikacji kod jest zgodny ze strukturą, która umożliwia łatwe testowanie różnych części aplikacji z osobna. Architektury testujące mają też inne zalety, np. lepszą czytelność, łatwość obsługi, skalowalność i możliwość wielokrotnego użytku.

Architektura, której nie można przetestować, sprawia, że:

  • Większe, wolniejsze i bardziej niestabilne testy. Klasy, których nie da się przetestować w ramach testów jednostkowych, mogą wymagać częstszych testów integracji lub testów interfejsu.
  • Mniej możliwości testowania różnych scenariuszy. Większe testy są wolniejsze, więc testowanie wszystkich możliwych stanów aplikacji może być nierealistyczne.

Więcej informacji o wytycznych dotyczących architektury znajdziesz w przewodniku po architekturze aplikacji.

Sposoby na odłączanie

Jeśli uda Ci się wyodrębnić część funkcji, klasy lub modułu z reszty, testowanie jest łatwiejsze i skuteczniejsze. Nazywamy to rozłączaniem i jest to koncepcja, która jest najważniejsza dla testowalnej architektury.

Typowe metody rozłączania danych to:

  • Podziel aplikację na warstwy, takie jak Prezentacja, Domena i Dane. Możesz też podzielić aplikację na moduły, po jednym na funkcję.
  • Unikaj dodawania logiki do encji, które mają duże zależności, np. w działaniach i fragmentach. Użyj tych klas jako punktów wejścia do platformy i przenieś logikę interfejsu użytkownika i logikę biznesową w inne miejsca, np. do warstwy Composable, ViewModel lub warstwy domeny.
  • Unikaj bezpośrednich zależności platformy w klasach zawierających logikę biznesową. Na przykład nie używaj kontekstu Androida w obiektach ViewModels.
  • Ułatw zastępowanie zależności. Na przykład używaj interfejsów zamiast konkretnych implementacji. Użyj funkcji wstrzykiwania zależności, nawet jeśli nie korzystasz z platformy DI.

Dalsze kroki

Wiesz już, dlaczego warto przeprowadzać testy i jakie są 2 główne typy testów. Teraz przeczytaj artykuł Co testować.

Jeśli chcesz utworzyć pierwszy test i wyciągnąć wnioski, zapoznaj się z ćwiczeniami z programowania dotyczącymi testowania.