Używanie podwójnej precyzji testów na Androidzie

Podczas projektowania strategii testowania elementu lub systemu należy zwrócić uwagę na 3 powiązane aspekty testowania:

  • Zakres: jaka część kodu dotyczy testu? Testy mogą weryfikować pojedynczą metodę, całą aplikację lub dowolne miejsce pośrednie. Zakres testowany jest w stanie w trakcie testów i zwykle jest nazywany obiektem w trakcie testowania, ale jest też określany jako system w trakcie testowania lub jednostka w trakcie testowania.
  • Szybkość: jak szybko przebiega test? Szybkość testu może się różnić od milisekund do kilku minut.
  • Wiarygodność: jak wygląda test w świecie rzeczywistym? Jeśli na przykład część testowanego kodu musi wysyłać żądanie sieciowe, to czy kod testowy faktycznie wysyła to żądanie sieciowe, czy fałszywy wynik? Jeśli test faktycznie komunikuje się z siecią, to oznacza, że ma on wyższą jakość. W tym jednak test może potrwać dłużej, spowodować błędy w przypadku awarii sieci lub być kosztowne w użyciu.

Zobacz, co testować, aby dowiedzieć się, jak rozpocząć definiowanie strategii testowej.

Izolacja i zależności

Testy elementów lub układu elementów wykonuje się w ramach izolacji. Na przykład do przetestowania ViewModel nie trzeba uruchamiać emulatora ani interfejsu użytkownika, ponieważ nie wymaga on (lub nie powinien) korzystać z platformy Androida.

Testowany element może jednak zależyć od innych czynników. Na przykład obiekt ViewModel może działać tylko od repozytorium danych.

Gdy chcesz podać zależność do testowanego podmiotu, często należy utworzyć podwójny wynik testu (lub obiekt testowy). Podwójne elementy testowe to obiekty, które wyglądają i działają jako komponenty aplikacji, ale są tworzone w teście po to, aby zapewniać określone zachowanie lub dane. Główną zaletą takiego rozwiązania jest to, że testy są szybsze i prostsze.

Rodzaje podwójnej precyzji testów

Są różne rodzaje podwójnej precyzji w testach:

Zawiera nieprawdziwe treści Test zmiennoprzecinkowy, który ma „działającą” implementację klasy, ale został zaimplementowany w taki sposób, że nadaje się do testów, ale nie nadaje się do produkcji.

Przykład: baza danych działająca w pamięci.

Fałszywe materiały nie wymagają żartu i są lekkie. Są one preferowane.

Przykład Podwójny preparat testowy, który zachowuje swoje zaprogramowane zachowanie i który ma oczekiwania dotyczące interakcji. Przykłady testów zakończą się niepowodzeniem, jeśli ich interakcje nie będą spełniać określonych przez Ciebie wymagań. Aby osiągnąć ten efekt, stosuje się zazwyczaj kilka żartów.

Przykład: sprawdź, czy metoda w bazie danych została wywołana dokładnie raz.

Szablon Test zmiennoprzecinkowy, który zachowuje się w taki sposób, w jaki został zaprogramowany, ale nie ma oczekiwań co do interakcji. Zwykle tworzone w formie żartów. Ze względu na prostotę preferujemy fałszywe treści.
pusty Wartość zmiennoprzecinkowa testu, która jest przekazywana, ale nie jest używana, np. gdy trzeba tylko podać go jako parametr.

Przykład: pusta funkcja przekazana jako wywołanie zwrotne kliknięcia.

Szpieg Otoka nad prawdziwym obiektem, która również śledzi dodatkowe informacje, podobne do próbek. Zwykle nie są one potrzebne ze względu na złożoność. Fałszywe materiały są więc preferowane od szpiegów.
Cień Robolectric.

Przykład z użyciem podróbki

Załóżmy, że chcesz przetestować model ViewModel, który zależy od interfejsu o nazwie UserRepository, i udostępnia w interfejsie nazwę pierwszego użytkownika. Możesz utworzyć podwójny fałszywy test, implementując interfejs i zwracając znane dane.

object FakeUserRepository : UserRepository {
    fun getUsers() = listOf(UserAlice, UserBob)
}

val const UserAlice = User("Alice")
val const UserBob = User("Bob")

Fałszywy element UserRepository nie musi zależeć od lokalnych i zdalnych źródeł danych, z których korzysta wersja produkcyjna. Plik znajduje się w źródle testowym i nie zostanie wysłany z wersją produkcyjną.

Fałszywa zależność może zwrócić znane dane bez konieczności korzystania z zdalnych źródeł danych
Rysunek 1. Fałszywa zależność w teście jednostkowym.

Ten test sprawdza, czy obiekt ViewModel prawidłowo ujawnia w widoku pierwszą nazwę użytkownika.

@Test
fun viewModelA_loadsUsers_showsFirstUser() {
    // Given a VM using fake data
    val viewModel = ViewModelA(FakeUserRepository) // Kicks off data load on init

    // Verify that the exposed data is correct
    assertEquals(viewModel.firstUserName, UserAlice.name)
}

W teście jednostkowym można łatwo zastąpić parametr UserRepository fałszywą wartością, ponieważ obiekt ViewModel jest tworzony przez testera. Zastępowanie dowolnych elementów w większych testach może być jednak trudne.

Zastępowanie komponentów i wstrzykiwania zależności

Gdy testy nie mają kontroli nad tworzeniem testowanych systemów, zastępowanie komponentów podwójnej precyzji testów staje się bardziej skomplikowane i wymaga, aby architektura aplikacji była testowalna.

Stosowanie wartości podwójnego testu przydaje się nawet w dużych, kompleksowych testach, np. w instrukcyjnym teście interfejsu, który przechodzi przez proces przeglądania aplikacji przez użytkownika. W takim przypadku warto przekształcić test w hermetyczny. Test hermetyczny pozwala uniknąć wszystkich zależności zewnętrznych, takich jak pobieranie danych z internetu. Zwiększa to niezawodność i wydajność.

Rysunek 2. Ogromny test obejmujący większość aplikacji i fałszywe dane zdalne.

Aplikację możesz tak zaprojektować ręcznie, ale zalecamy używanie platformy wstrzykiwania zależności, takiej jak Hilt, która służy do zastępowania komponentów w aplikacji w czasie testowania. Zobacz Przewodnik po testowaniu wyników.

Robolectric

Na Androidzie można użyć platformy Robolectric, która zapewnia specjalny typ typu double. Robolectric umożliwia przeprowadzanie testów na stacji roboczej lub w środowisku ciągłej integracji. Używa zwykłej maszyny JVM, bez emulatora ani urządzenia. Symuluje zawyżanie liczby wyświetleń, wczytywania zasobów i innych części platformy Androida za pomocą elementów testowych nazywanych cieniami.

Robolectric to symulator, więc nie powinien zastępować prostych testów jednostkowych ani nie powinien być używany do testowania zgodności. Zapewnia to szybkość, a w niektórych przypadkach niższa jakość. Dobrym podejściem do testów interfejsu jest zapewnienie ich zgodności zarówno z testami Robolectric, jak i instrumentalnymi, oraz decydowanie, kiedy je uruchomić w zależności od potrzeb testowania funkcji lub zgodności. Na Robolectric można przeprowadzać zarówno testy Espresso, jak i Compose.