Podstawowe informacje o testowaniu

To ćwiczenie programowania jest częścią kursu „Android dla zaawansowanych w Kotlinie”. Korzyści z tego kursu będą dla Ciebie najbardziej wartościowe, jeśli wykonasz je w sekwencjach ćwiczeń z programowania, ale nie jest to obowiązkowe. Wszystkie ćwiczenia z kursu są wymienione na stronie Zaawansowane ćwiczenia z programowania na Androida w Kotlin.

Wstęp

Po wdrożeniu pierwszej funkcji pierwszej aplikacji prawdopodobnie udało Ci się uruchomić kod, by sprawdzić, czy wszystko działa zgodnie z oczekiwaniami. Przeprowadzono test, a nie test ręczny. W miarę dodawania i aktualizowania funkcji udało Ci się pewnie kontynuować uruchamianie kodu i sprawdzanie, czy wszystko działa. Robimy to ręcznie za każdym razem, gdy męczymy się, popełniamy błędy i nie staramy się skalować.

Komputery są świetne w skalowaniu i automatyzacji. Programiści w dużych i małych firmach piszą testy automatyczne, które są przeprowadzane przez oprogramowanie i nie wymagają ręcznego uruchamiania aplikacji, by sprawdzić, czy kod działa.

W tej serii ćwiczeń z programowania nauczysz się tworzyć testy (zwane pakietami testowymi) dla rzeczywistych aplikacji.

W pierwszym ćwiczeniu z programowania poznasz podstawy testowania na urządzeniach z Androidem. Napiszesz pierwsze testy i nauczysz się testować LiveData i ViewModel.

Co musisz wiedzieć

Pamiętaj:

Czego się nauczysz

Poznasz te tematy:

  • Pisanie i uruchamianie testów jednostkowych w Androidzie
  • Jak korzystać z testowania z wykorzystaniem wersji testowej
  • Jak wybrać testy z instrumentem i testy lokalne

Poznasz też następujące biblioteki i pojęcia związane z kodem:

Jakie zadania wykonasz:

  • Skonfiguruj, uruchom i zinterpretuj zarówno testy lokalne, jak i instrumentowane w Androidzie.
  • Zapisuj testy jednostkowe w Androidzie przy użyciu jednostek JUnit4 i Hamcrest.
  • Napisz proste testy LiveData i ViewModel.

W ramach tej serii ćwiczeń z programowania wykonasz kilka zadań w aplikacji Zadania do wykonania. Ta aplikacja pozwoli Ci zapisywać zadania do wykonania i wyświetlać je na liście. Następnie możesz oznaczyć je jako ukończone, odfiltrować lub usunąć.

Ta aplikacja została napisana w Kotlin i ma kilka ekranów, używa komponentów Jetpack oraz jest zgodna z architekturą przewodnika po architekturze aplikacji. Naucząc się testować tę aplikację, będziesz mieć możliwość testowania aplikacji korzystających z tych samych bibliotek i architektury.

Na początek pobierz kod:

Pobierz aplikację Zip

Możesz też skopiować kod GitHuba:

$ git clone https://github.com/googlecodelabs/android-testing.git
$ cd android-testing
$ git checkout starter_code

W tym zadaniu uruchomisz aplikację i przeanalizujesz kod.

Krok 1. Uruchom przykładową aplikację

Po pobraniu aplikacji Do zrobienia otwórz ją w Android Studio i uruchom. Powinien się skompilować. Poznaj aplikację, wykonując te czynności:

  • Utwórz nowe zadanie z pływającym przyciskiem czynności plus. Wpisz tytuł, a następnie dodatkowe informacje o zadaniu. Zapisz przy użyciu zielonego przycisku wyboru.
  • Na liście zadań kliknij tytuł właśnie zakończonego zadania i wyświetl jego ekran szczegółów, aby wyświetlić pozostałe opisy.
  • Na liście lub na ekranie szczegółów zaznacz pole wyboru obok zadania, by ustawić jego stan na Ukończone.
  • Wróć do ekranu zadań, otwórz menu filtra i przefiltruj zadania według stanu Aktywne lub Ukończone.
  • Otwórz panel nawigacji i kliknij Statystyki.
  • Wróć na ekran przeglądu i z menu panelu nawigacji wybierz Wyczyść ukończone, aby usunąć wszystkie zadania o stanie Ukończone.

Krok 2. Sprawdź przykładowy kod aplikacji

Aplikacja Do zrobienia wykorzystuje przykłady z popularnych testów i architektury architektury Blueprints (korzystając z próbki architektury reaktywnej). Ta aplikacja jest zgodna z architekturą Przewodnika po architekturze aplikacji. Wykorzystuje ono obiekty ViewModels z fragmentami, repozytorium i salą. Jeśli znasz dowolny z tych przykładów, aplikacja ma podobną architekturę:

Ważne jest, aby lepiej zrozumieć ogólną architekturę aplikacji niż zrozumieć jej działanie na jednej warstwie.

Podsumowanie przesyłek znajdziesz tutaj:

Przesyłka: com.example.android.architecture.blueprints.todoapp

.addedittask

Dodawanie lub edytowanie ekranu zadania: kod warstwy interfejsu służący do dodawania i edytowania zadań.

.data

Warstwa danych: dotyczy warstwy danych z zadaniami. Zawiera on bazę danych, sieć i kod repozytorium.

.statistics

Ekran statystyk: kod warstwy interfejsu wyświetlany na ekranie statystyk.

.taskdetail

Ekran szczegółów zadania: kod warstwy interfejsu dotyczący pojedynczego zadania.

.tasks

Ekran Listy zadań: kod warstwy interfejsu zawierający listę wszystkich zadań.

.util

Klasy narzędzi: udostępniane klasy w różnych częściach aplikacji, np. do układu przesuwania używanego na wielu ekranach.

Warstwa danych (.data)

Ta aplikacja zawiera symulowaną warstwę sieciową w pakiecie remote i warstwę bazy danych w pakiecie local. Dla uproszczenia w tym projekcie symulacja warstwy sieciowej jest symulowana za pomocą tylko HashMap z opóźnieniem, a nie przez rzeczywiste żądania sieciowe.

DefaultTasksRepository koordynuje lub pośredniczy między warstwą sieciową a warstwą bazy danych i zwraca dane do warstwy interfejsu.

Warstwa UI ( .addedittask, .statistics, .taskdetail, .tasks)

Każdy pakiet warstw interfejsu zawiera fragment i model widoku, a także wszystkie inne klasy wymagane w interfejsie (takie jak adapter do listy zadań). TaskActivity to działanie zawierające wszystkie fragmenty.

Nawigacja

Nawigacja w aplikacji jest sterowana za pomocą komponentu Nawigacja. Jest on zdefiniowany w pliku nav_graph.xml. Nawigacja jest uruchamiana w modelach widoku danych za pomocą klasy Event. Modele widoku danych określają też, jakie argumenty należy przekazać. Fragmenty śledzą Event i przeprowadzają rzeczywistą nawigację między ekranami.

W tym zadaniu wykonasz pierwsze testy.

  1. W Android Studio otwórz panel Projekt i znajdź te 3 foldery:
  • com.example.android.architecture.blueprints.todoapp
  • com.example.android.architecture.blueprints.todoapp (androidTest)
  • com.example.android.architecture.blueprints.todoapp (test)

Takie foldery są nazywane zbiorami źródłowymi. Zestawy źródłowe to foldery zawierające kod źródłowy aplikacji. Zestawy źródłowe są oznaczone kolorem zielonym (androidTest i test). Podczas tworzenia nowego projektu na Androida domyślnie otrzymujesz trzy poniższe zestawy źródeł. Są to:

  • main: zawiera kod aplikacji. Ten kod jest dostępny we wszystkich wersjach aplikacji, które możesz tworzyć (są to wersje kompilacji).
  • androidTest: zawiera testy z instrumentacją.
  • test: zawiera testy nazywane testami lokalnymi.

Różnica między testami lokalnymi a testami polowymi polega na sposobie ich przeprowadzania.

Testy lokalne (test zestaw źródeł)

Te testy są przeprowadzane lokalnie na maszynie wirtualnej dla programistów i nie wymagają emulatora ani urządzenia fizycznego. Dlatego grają szybko, ale ich wierność jest niższa, co oznacza, że grają mniej jak w prawdziwym świecie.

W Android Studio lokalne testy są oznaczone zieloną i czerwoną ikoną trójkąta.

Instrumentalne testy (androidTestzestaw źródłowy)

Testy są przeprowadzane na prawdziwych lub emulowanych urządzeniach z Androidem, dzięki czemu odzwierciedlają rzeczywiste wyniki, ale są też znacznie wolniejsze.

W Android Studio testy z instrukcjami są reprezentowane przez Androida z zieloną i czerwoną trójkątem.

Krok 1. Uruchom test lokalny

  1. Otwórz folder test, aż znajdziesz plik ExampleUnitTest.kt.
  2. Kliknij ją prawym przyciskiem myszy i wybierz Uruchom przykładowy test.

W oknie Uruchom u dołu ekranu powinny pojawić się te dane wyjściowe:

  1. Zwróć uwagę na zielone znaczniki wyboru i rozwiń wyniki testu, by potwierdzić, że 1 test o nazwie addition_isCorrect został zaliczony. Warto wiedzieć, że dodanie działa zgodnie z oczekiwaniami.

Krok 2. Sprawdź, czy test się nie powiódł

Poniżej znajduje się test, który właśnie udało Ci się przeprowadzić.

PrzykładowyTestTest.kt

// A test class is just a normal class
class ExampleUnitTest {

   // Each test is annotated with @Test (this is a Junit annotation)
   @Test
   fun addition_isCorrect() {
       // Here you are checking that 4 is the same as 2+2
       assertEquals(4, 2 + 2)
   }
}

Uwaga: testy

  • są klasą w jednym z testowych zbiorów źródłowych.
  • zawierają funkcje, które rozpoczynają się od adnotacji @Test (każda funkcja jest pojedynczym testem).
  • u8susel zawierają instrukcje.

Android służy do testowania biblioteki JUnit na potrzeby testów (w tym module Jlab4). Potwierdzenia i adnotacja @Test pochodzą z jednostki.

Potwierdzenie to podstawa testu. Jest to instrukcja, która sprawdza, czy kod lub aplikacja działa zgodnie z oczekiwaniami. W tym przypadku potwierdzenie to assertEquals(4, 2 + 2), które sprawdza, czy 4 jest równe 2 + 2.

Aby zobaczyć, jak wygląda niepowodzenie testu, dodaj potwierdzenie, które możesz łatwo zobaczyć. Sprawdza, czy trzecia liczba to 1+1.

  1. Dodaj assertEquals(3, 1 + 1) do testu addition_isCorrect.

PrzykładowyTestTest.kt

class ExampleUnitTest {

   // Each test is annotated with @Test (this is a Junit annotation)
   @Test
   fun addition_isCorrect() {
       assertEquals(4, 2 + 2)
       assertEquals(3, 1 + 1) // This should fail
   }
}
  1. Uruchom test.
  1. W wynikach testu zobaczysz symbol X widoczny obok testu.

  1. Zwróć też uwagę:
  • Jedna nieudana próba potwierdzenia danych kończy się niepowodzeniem w całości testu.
  • otrzymasz oczekiwaną wartość (3) w porównaniu do wartości, która została już obliczona (2).
  • Nastąpi przekierowanie do wiersza z błędnym potwierdzeniem (ExampleUnitTest.kt:16).

Krok 3. Uruchom test z instrumentacją

Testy z instrumentacją są w zestawie źródłowym androidTest.

  1. Otwórz zbiór źródłowy androidTest.
  2. Uruchom test o nazwie ExampleInstrumentedTest.

Przykładowy instrument testowy

@RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest {
    @Test
    fun useAppContext() {
        // Context of the app under test.
        val appContext = InstrumentationRegistry.getInstrumentation().targetContext
        assertEquals("com.example.android.architecture.blueprints.reactive",
            appContext.packageName)
    }
}

W przeciwieństwie do testu lokalnego ten test działa na urządzeniu (w przykładzie poniżej widać emulowany telefon Pixel 2):

Jeśli masz podłączone urządzenie lub uruchomiony emulator, powinien uruchomić go w emulatorze.

W tym zadaniu utworzysz testy dla getActiveAndCompleteStats, które obliczają procent aktywnych i ukończonych statystyk zadań dla aplikacji. Możesz zobaczyć te statystyki na ekranie statystyk aplikacji.

Krok 1. Utwórz klasę testową

  1. W zestawie źródłowym main w aplikacji todoapp.statistics otwórz StatisticsUtils.kt.
  2. Znajdź funkcję getActiveAndCompletedStats.

StatystykiUtils.kt

internal fun getActiveAndCompletedStats(tasks: List<Task>?): StatsResult {

   val totalTasks = tasks!!.size
   val numberOfActiveTasks = tasks.count { it.isActive }
   val activePercent = 100 * numberOfActiveTasks / totalTasks
   val completePercent = 100 * (totalTasks - numberOfActiveTasks) / totalTasks

   return StatsResult(
       activeTasksPercent = activePercent.toFloat(),
       completedTasksPercent = completePercent.toFloat()
   )
  
}

data class StatsResult(val activeTasksPercent: Float, val completedTasksPercent: Float)

Funkcja getActiveAndCompletedStats akceptuje listę zadań i zwraca StatsResult. StatsResult to klasa danych zawierająca 2 cyfry, odsetek ukończonych zadań i aktywnych zadań.

Android Studio udostępnia narzędzia do generowania atutów testowych i pomocy w testowaniu tej funkcji.

  1. Kliknij prawym przyciskiem myszy getActiveAndCompletedStats i wybierz Wygeneruj > Test.

Otworzy się okno Tworzenie testu:

  1. zmień Nazwę klasy: na StatisticsUtilsTest (zamiast StatisticsUtilsKtTest; lepiej jest unikać słowa kluczowego KT w nazwie klasy testowej).
  2. Zachowaj pozostałe ustawienia domyślne. JUnit 4 to odpowiednia biblioteka testowa. Pakiet docelowy jest prawidłowy (odzwierciedla lokalizację klasy StatisticsUtils) i nie musisz zaznaczać żadnych pól wyboru (spowoduje to wygenerowanie dodatkowego kodu, ale test zapiszesz od zera).
  3. Naciśnij OK.

Otworzy się okno Wybierz katalog docelowy.

Przeprowadzisz test lokalny, ponieważ Twoja funkcja wykonuje obliczenia matematyczne i nie będzie zawierać kodu dla Androida. Nie trzeba go więc uruchamiać na prawdziwym lub emulowanym urządzeniu.

  1. Wybierz katalog test (nie androidTest), ponieważ będziesz przygotowywać testy lokalne.
  2. Kliknij OK.
  3. Zwróć uwagę na wygenerowaną klasę StatisticsUtilsTest w test/statistics/.

Krok 2. Napisz pierwszą funkcję testową

Chcesz napisać test, który sprawdzi:

  • jeśli nie ma ukończonych zadań i jedno aktywne
  • że odsetek aktywnych testów wynosi 100%,
  • a procent ukończonych zadań wynosi 0%.
  1. Otwórz aplikację StatisticsUtilsTest.
  2. Utwórz funkcję o nazwie getActiveAndCompletedStats_noCompleted_returnsHundredZero.

StatystykiUtilsTest.kt

class StatisticsUtilsTest {

    fun getActiveAndCompletedStats_noCompleted_returnsHundredZero() {
        // Create an active task

        // Call your function

        // Check the result
    }
}
  1. Dodaj adnotację @Test nad nazwą funkcji, by wskazać, że jest to test.
  2. Utwórz listę zadań.
// Create an active task 
val tasks = listOf<Task>(
            Task("title", "desc", isCompleted = false)
        )
  1. Zadzwoń do getActiveAndCompletedStats i wykonaj te zadania.
// Call your function
val result = getActiveAndCompletedStats(tasks)
  1. Upewnij się, że ciąg result jest zgodny z oczekiwaniami, używając twierdzeń.
// Check the result
assertEquals(result.completedTasksPercent, 0f)
assertEquals(result.activeTasksPercent, 100f)

Oto pełny kod.

StatystykiUtilsTest.kt

class StatisticsUtilsTest {

    @Test
    fun getActiveAndCompletedStats_noCompleted_returnsHundredZero() {

        // Create an active task (the false makes this active)
        val tasks = listOf<Task>(
            Task("title", "desc", isCompleted = false)
        )
        // Call your function
        val result = getActiveAndCompletedStats(tasks)

        // Check the result
        assertEquals(result.completedTasksPercent, 0f)
        assertEquals(result.activeTasksPercent, 100f)
    }
}
  1. Uruchom test (kliknij prawym przyciskiem myszy StatisticsUtilsTest i wybierz Uruchom).

Powinny one spełniać te wymagania:

Krok 3. Dodaj zależność Hamcrest

Testy są tylko dowodem na działanie kodu, więc są czytelne, jeśli są czytelne dla człowieka. Porównaj te dwa stwierdzenia:

assertEquals(result.completedTasksPercent, 0f)

// versus

assertThat(result.completedTasksPercent, `is`(0f))

Drugie zdanie można dostrzec bardziej jak ludzkie zdanie. Jest napisany za pomocą platformy asercyjnej zwanej Hamcrestem. Innym przydatnym narzędziem do tworzenia zrozumiałych twierdzeń jest Biblioteka prawdziwości. Wykorzystasz Hamcrest w tym ćwiczeniu z programowania do pisania twierdzeń.

  1. Otwórz build.grade (Module: app) i dodaj tę zależność.

app/build.gradle

dependencies {
    // Other dependencies
    testImplementation "org.hamcrest:hamcrest-all:$hamcrestVersion"
}

Zazwyczaj podczas dodawania zależności korzystasz z implementation, ale tym razem używasz testImplementation. Gdy aplikacja będzie gotowa do udostępnienia światu, najlepiej nie wypełniać jej APK kodem testowym ani zależnościami. Użyj konfiguracji Gradle, by określić, czy biblioteka ma być uwzględniona w kodzie głównym czy testowym. Oto najczęstsze konfiguracje:

  • implementation – zależność jest dostępna we wszystkich zestawach źródłowych, w tym w testowych zestawach.
  • testImplementation – zależność jest dostępna tylko w zestawie testowym.
  • androidTestImplementation – zależność jest dostępna tylko w zestawie źródłowym androidTest.

Wybrana konfiguracja określa miejsce, w którym można korzystać z zależności. Jeśli napiszesz:

testImplementation "org.hamcrest:hamcrest-all:$hamcrestVersion"

Oznacza to, że usługa Hamcrest będzie dostępna tylko w zestawie testowym. Dzięki temu w aplikacji końcowej nie będzie też aplikacji Hamcrest.

Krok 4. Użyj aplikacji Hamcrest do zapisu twierdzeń

  1. Zaktualizuj test getActiveAndCompletedStats_noCompleted_returnsHundredZero(), aby korzystać z testera Hamcrest i assertThat zamiast assertEquals.
// REPLACE
assertEquals(result.completedTasksPercent, 0f)
assertEquals(result.activeTasksPercent, 100f)

// WITH
assertThat(result.activeTasksPercent, `is`(100f))
assertThat(result.completedTasksPercent, `is`(0f))

Jeśli pojawi się taka prośba, możesz skorzystać z importu import org.hamcrest.Matchers.`is`.

Końcowy test będzie wyglądać tak jak w poniższym kodzie.

StatystykiUtilsTest.kt

import com.example.android.architecture.blueprints.todoapp.data.Task
import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.Matchers.`is`
import org.junit.Test

class StatisticsUtilsTest {

    @Test
    fun getActiveAndCompletedStats_noCompleted_returnsHundredZero {

        // Create an active tasks (the false makes this active)
        val tasks = listOf<Task>(
            Task("title", "desc", isCompleted = false)
        )
        // Call your function
        val result = getActiveAndCompletedStats(tasks)

        // Check the result
        assertThat(result.activeTasksPercent, `is`(100f))
        assertThat(result.completedTasksPercent, `is`(0f))

    }
}
  1. Uruchom zaktualizowany test, aby sprawdzić, czy nadal działa.

Dzięki tym ćwiczeniom nie poznasz wszystkie informacje o Hamcrestie, więc jeśli chcesz dowiedzieć się więcej, przeczytaj oficjalny samouczek.

To jest zadanie opcjonalne.

W tym zadaniu napiszesz więcej testów z wykorzystaniem JUnit i Hamcrest. Będziesz też przygotowywać testy z użyciem strategii opracowanej na podstawie programowania testowania. Testuj programowanie nakierowane na programowanie lub TDD to szkoła programowania, według której zamiast pisać swój kod funkcji, najpierw pisz testy. Następnie napisz kod funkcji, aby ukończyć testy.

Krok 1. Napisz testy

Napisz testy, gdy masz normalną listę zadań:

  1. Jeśli jest 1 ukończone zadanie i nie ma żadnych aktywnych zadań, wartość procentowa activeTasks powinna wynosić 0f, a odsetek ukończonych zadań to 100f.
  2. Jeśli 2 ukończone zadania są 3 aktywne, odsetek wartości powinien wynosić 40f, a wartość procentowa to 60f.

Krok 2. Testowanie testu

Zpisany kod getActiveAndCompletedStats ma błąd. Zwróć uwagę na to, co się dzieje, gdy lista jest pusta lub ma wartość NULL. W obu przypadkach wartość procentowa powinna wynosić zero.

internal fun getActiveAndCompletedStats(tasks: List<Task>?): StatsResult {

   val totalTasks = tasks!!.size
   val numberOfActiveTasks = tasks.count { it.isActive }
   val activePercent = 100 * numberOfActiveTasks / totalTasks
   val completePercent = 100 * (totalTasks - numberOfActiveTasks) / totalTasks

   return StatsResult(
       activeTasksPercent = activePercent.toFloat(),
       completedTasksPercent = completePercent.toFloat()
   )
  
}

Aby naprawić kod i utworzyć testy, użyj programowania opartego na testach. Aby to zrobić, postępuj zgodnie z tymi wskazówkami.

  1. Napisz test, stosując strukturę o podanej nazwie oraz Kiedy, kiedy i Później.
  2. Upewnij się, że test się nie powiedzie.
  3. Napisz minimalny kod potrzebny do zdania testu.
  4. Powtórz te czynności dla wszystkich testów.

Zamiast naprawiać błąd, trzeba zacząć od napisania testów. Następnie możesz potwierdzić, że przeprowadzasz testy chroniące przed przypadkowym ponownym wprowadzeniem tych błędów w przyszłości.

  1. Jeśli lista jest pusta (emptyList()), obie wartości powinny wynosić 0f.
  2. Jeśli podczas wczytywania zadań wystąpił błąd, lista będzie mieć wartość null i obie wartości powinny wynosić 0f.
  3. Uruchom testy i upewnij się, że ich niepowodzenie:

Krok 3. Rozwiąż problem

Teraz po zakończeniu testów napraw błąd.

  1. Napraw błąd w danych getActiveAndCompletedStats, zwracając wartość 0f, jeśli atrybut tasks ma wartość null lub jest pusty:
internal fun getActiveAndCompletedStats(tasks: List<Task>?): StatsResult {

    return if (tasks == null || tasks.isEmpty()) {
        StatsResult(0f, 0f)
    } else {
        val totalTasks = tasks.size
        val numberOfActiveTasks = tasks.count { it.isActive }
        StatsResult(
            activeTasksPercent = 100f * numberOfActiveTasks / tasks.size,
            completedTasksPercent = 100f * (totalTasks - numberOfActiveTasks) / tasks.size
        )
    }
}
  1. Uruchom testy ponownie i sprawdź, czy się zgadzają.

Wykonując testy TDD i pisając testy, pomogłeś:

  • Nowe funkcje są zawsze powiązane z testami, więc testy pełnią funkcję dokumentacji kodu.
  • Testy sprawdzają poprawność wyników i chronią przed zgłoszonymi przez Ciebie błędami.

Rozwiązanie: pisanie większej liczby testów

Poniżej znajdziesz wszystkie testy i odpowiadający im kod funkcji.

StatystykiUtilsTest.kt

class StatisticsUtilsTest {

    @Test
    fun getActiveAndCompletedStats_noCompleted_returnsHundredZero {
        val tasks = listOf(
            Task("title", "desc", isCompleted = false)
        )
        // When the list of tasks is computed with an active task
        val result = getActiveAndCompletedStats(tasks)

        // Then the percentages are 100 and 0
        assertThat(result.activeTasksPercent, `is`(100f))
        assertThat(result.completedTasksPercent, `is`(0f))
    }

    @Test
    fun getActiveAndCompletedStats_noActive_returnsZeroHundred() {
        val tasks = listOf(
            Task("title", "desc", isCompleted = true)
        )
        // When the list of tasks is computed with a completed task
        val result = getActiveAndCompletedStats(tasks)

        // Then the percentages are 0 and 100
        assertThat(result.activeTasksPercent, `is`(0f))
        assertThat(result.completedTasksPercent, `is`(100f))
    }

    @Test
    fun getActiveAndCompletedStats_both_returnsFortySixty() {
        // Given 3 completed tasks and 2 active tasks
        val tasks = listOf(
            Task("title", "desc", isCompleted = true),
            Task("title", "desc", isCompleted = true),
            Task("title", "desc", isCompleted = true),
            Task("title", "desc", isCompleted = false),
            Task("title", "desc", isCompleted = false)
        )
        // When the list of tasks is computed
        val result = getActiveAndCompletedStats(tasks)

        // Then the result is 40-60
        assertThat(result.activeTasksPercent, `is`(40f))
        assertThat(result.completedTasksPercent, `is`(60f))
    }

    @Test
    fun getActiveAndCompletedStats_error_returnsZeros() {
        // When there's an error loading stats
        val result = getActiveAndCompletedStats(null)

        // Both active and completed tasks are 0
        assertThat(result.activeTasksPercent, `is`(0f))
        assertThat(result.completedTasksPercent, `is`(0f))
    }

    @Test
    fun getActiveAndCompletedStats_empty_returnsZeros() {
        // When there are no tasks
        val result = getActiveAndCompletedStats(emptyList())

        // Both active and completed tasks are 0
        assertThat(result.activeTasksPercent, `is`(0f))
        assertThat(result.completedTasksPercent, `is`(0f))
    }
}

StatystykiUtils.kt

internal fun getActiveAndCompletedStats(tasks: List<Task>?): StatsResult {

    return if (tasks == null || tasks.isEmpty()) {
        StatsResult(0f, 0f)
    } else {
        val totalTasks = tasks.size
        val numberOfActiveTasks = tasks.count { it.isActive }
        StatsResult(
            activeTasksPercent = 100f * numberOfActiveTasks / tasks.size,
            completedTasksPercent = 100f * (totalTasks - numberOfActiveTasks) / tasks.size
        )
    }
}

Świetna robota z podstawami pisania i uruchamiania testów. W dalszej części dowiesz się, jak pisać podstawowe testy ViewModel i LiveData.

Z pozostałej części ćwiczeń dowiesz się, jak pisać testy w 2 klasach Androida, które są wspólne dla większości aplikacji: ViewModel i LiveData.

Zaczynasz od napisania testów: TasksViewModel.


Skoncentrujesz się na testach, które opierają się na wszystkich aspektach Twojego modelu w widoku danych i nie wymagają kodu repozytorium. Kod repozytorium obejmuje kod asynchroniczny, bazy danych i wywołania sieciowe, które zwiększają złożoność testu. Teraz pozwoli Ci to unikać i skoncentrować się na pisaniu testów funkcji ViewModel, które nie testują bezpośrednio żadnego elementu repozytorium.



W teście, który wpiszesz, sprawdzimy, czy po wywołaniu metody addNewTask uruchomi się Event w celu otwarcia nowego okna zadania. Oto kod aplikacji, który będzie testowany.

TaskTaskViewModel.kt

fun addNewTask() {
   _newTaskEvent.value = Event(Unit)
}

Krok 1. Tworzenie klasy TestViewModelTest

Wykonując te same czynności, co w przypadku klienta StatisticsUtilTest, w tym kroku utworzysz plik testowy dla TasksViewModelTest.

  1. Otwórz klasę, którą chcesz przetestować, w pakiecie tasks (TasksViewModel.)
  2. W kodzie kliknij prawym przyciskiem myszy nazwę zajęć TasksViewModel -> Wygeneruj -> Test.

  1. Na ekranie Utwórz test kliknij OK, aby zaakceptować (nie musisz zmieniać żadnych ustawień domyślnych).
  2. W oknie Choose Destination Directory (Wybierz katalog docelowy) wybierz katalog test.

Krok 2. Rozpocznij pisanie testu ViewModel

W tym kroku dodasz test modelu widoku, by sprawdzić, czy po wywołaniu metody addNewTask uruchamia się narzędzie Event do otwierania nowego okna zadania.

  1. Utwórz nowy test o nazwie addNewTask_setsNewTaskEvent.

TaskTaskViewModelTest.kt

class TasksViewModelTest {

    @Test
    fun addNewTask_setsNewTaskEvent() {

        // Given a fresh TasksViewModel


        // When adding a new task


        // Then the new task event is triggered

    }
    
}

A co z kontekstem aplikacji?

Jeśli tworzysz instancję TasksViewModel, aby ją przetestować, jej konstruktor wymaga kontekstu aplikacji. Ale w tym teście nie tworzysz pełnej aplikacji z aktywnościami, UI i fragmentami, więc jak uzyskać kontekst aplikacji?

TaskTaskViewModelTest.kt

// Given a fresh ViewModel
        val tasksViewModel = TasksViewModel(???)

Biblioteki testowe Androida X zawierają klasy i metody dostarczające wersji komponentów, takich jak aplikacje i działania, które są przeznaczone do testów. Jeśli prowadzisz test lokalny i potrzebujesz symulowanych klas platformy Androida(takich jak kontekst aplikacji), wykonaj te czynności, aby prawidłowo skonfigurować test Androida X:

  1. Dodaj zależności główne i wewnętrzne dla AndroidX Test
  2. Dodaj zależność biblioteki testowania Robo
  3. Dodaj do klasy adnotacje uruchamiające test AndroidaJunit4
  4. Zapisz kod testowy Androida X

Wykonaj te czynności, a następnie dowiesz się, co one robią.

Krok 3. Dodaj zależności Gradle

  1. Skopiuj te zależności do pliku build.gradle modułu aplikacji, aby dodać podstawowe zależności Android Core Core i Test Exxce oraz Androida w wersji Robolectric.

app/build.gradle

    // AndroidX Test - JVM testing
testImplementation "androidx.test.ext:junit-ktx:$androidXTestExtKotlinRunnerVersion"

    testImplementation "androidx.test:core-ktx:$androidXTestCoreVersion"

 testImplementation "org.robolectric:robolectric:$robolectricVersion"

Krok 4. Dodaj testera JUnit

  1. Dodaj @RunWith(AndroidJUnit4::class)nadczas lekcji.

TaskTaskViewModelTest.kt

@RunWith(AndroidJUnit4::class)
class TasksViewModelTest {
    // Test code
}

Krok 5. Użyj testu Androida X

Teraz możesz skorzystać z biblioteki testowej Androida X. Obejmuje to metodę ApplicationProvider.getApplicationContext, która pobiera kontekst aplikacji.

  1. Utwórz TasksViewModel przy użyciu ApplicationProvider.getApplicationContext() z biblioteki testowej Androida X.

TaskTaskViewModelTest.kt

// Given a fresh ViewModel
val tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())
  1. Zadzwoń do firmy addNewTask pod numer tasksViewModel.

TaskTaskViewModelTest.kt

tasksViewModel.addNewTask()

Na tym etapie test powinien wyglądać tak jak poniżej.

TaskTaskViewModelTest.kt

    @Test
    fun addNewTask_setsNewTaskEvent() {

        // Given a fresh ViewModel
        val tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())

        // When adding a new task
        tasksViewModel.addNewTask()

        // Then the new task event is triggered
        // TODO test LiveData
    }
  1. Uruchom test, aby sprawdzić, czy wszystko działa.

Pojęcie: Jak działa test AndroidX?

Co to jest test Androida X?

AndroidX Test to zbiór bibliotek do testowania. Obejmuje on klasy i metody, które udostępniają wersje komponentów takich jak aplikacje i działania, które są przeznaczone do testów. Ten kod to przykład funkcji testowej XX służącej do pobierania kontekstu aplikacji.

ApplicationProvider.getApplicationContext()

Jedną z zalet interfejsów API AndroidX Test jest to, że są one zaprojektowane do działania zarówno w przypadku testów lokalnych , jak i testów z instrumentem. To dobrze, bo:

  • Możesz przeprowadzić taki sam test jak test lokalny lub test z instrumentacją.
  • Nie musisz uczyć się korzystania z różnych interfejsów API do testów lokalnych i instrumentowanych.

Na przykład kod został napisany przez biblioteki testów Androida X, więc możesz przenieść klasę TasksViewModelTest z folderu test do folderu androidTest, a testy będą nadal przeprowadzane. getApplicationContext() działa trochę inaczej w zależności od tego, czy jest uruchomiony jako test lokalny czy instrumentalny:

  • Jeśli jest to instrumentowany test, otrzyma on rzeczywisty kontekst aplikacji podany podczas uruchamiania emulatora lub połączenia się z prawdziwym urządzeniem.
  • Jeśli jest to test lokalny, używany jest symulowany środowisko Androida.

Co to jest Robolectric?

Symulowane środowisko Androida, którego AndroidX Test używa do testowania lokalnych, udostępnia Robolectric. Robolectric to biblioteka, która tworzy symulowany środowisko Androida do testowania i działa szybciej niż uruchamianie emulatora czy uruchamianie urządzenia. Bez zależności od Robolectric otrzymasz ten błąd:

Co robi @RunWith(AndroidJUnit4::class)?

Tester to komponent JUnit, który przeprowadza testy. Jeśli nie uruchomi się tester, nie zostaną one uruchomione. Domyślny bieg testu dostarczony przez JUnit, który otrzymujesz automatycznie. @RunWith zastępuje to domyślne narzędzie do uruchamiania testów.

Uruchamiający test AndroidJUnit4, który umożliwia testowanie Androida X w zależności od tego, czy są to narzędzia, czy testy lokalne.

Krok 6. Rozwiązywanie ostrzeżeń Robolectric

Po uruchomieniu kodu zwróć uwagę, że używasz Robolectric.

Dzięki testowi AndroidX i testerowi AndroidJunit4 można to zrobić bez konieczności pisania choćby jednej linijki kodu Robolectric.

Możesz zobaczyć dwa ostrzeżenia.

  • No such manifest file: ./AndroidManifest.xml
  • "WARN: Android SDK 29 requires Java 9..."

Możesz naprawić ostrzeżenie dotyczące No such manifest file: ./AndroidManifest.xml, aktualizując plik Gradle.

  1. Dodaj ten wiersz do pliku Gradle, aby użyć właściwego pliku manifestu Androida. Opcja includeAndroidResources umożliwia dostęp do zasobów Androida w testach jednostkowych, w tym do pliku AndroidManifest.

app/build.gradle

    // Always show the result of every unit test when running via command line, even if it passes.
    testOptions.unitTests {
        includeAndroidResources = true

        // ... 
    }

Ostrzeżenie "WARN: Android SDK 29 requires Java 9..." jest bardziej skomplikowane. Uruchamianie testów na urządzeniach z Androidem Q wymaga środowiska Java 9. Zamiast próbować skonfigurować Android Studio tak, aby używało Javy 9, w tym ćwiczeniu z programowania zachowaj wartość docelową i skompiluj pakiet SDK w wersji 28.

Podsumowanie:

  • Testy modelu opartego na czystym widoku można zazwyczaj przeprowadzić w zestawie źródłowym test, ponieważ ich kod zwykle nie wymaga Androida.
  • Aby uzyskać wersje testowe komponentów, takich jak Aplikacje i Działania, możesz skorzystać z biblioteki testowej Androida.
  • Jeśli chcesz uruchomić symulowany kod Androida w zbiorze źródłowym test, możesz dodać zależność Robolectric i adnotację @RunWith(AndroidJUnit4::class).

Gratulacje, do przeprowadzenia testu używasz zarówno biblioteki testów Androida X, jak i aplikacji Robolectric. Test jeszcze się nie zakończył (nie masz jeszcze wypisanego oświadczenia, ponieważ ma to wyglądać tak: // TODO test LiveData). Poćwicz teraz tworzenie instrukcji za pomocą LiveData.

Z tego zadania dowiesz się, jak prawidłowo potwierdzić wartość LiveData.

Oto miejsce, w którym przerwano testowanie bez wyświetlania modelu atrybucji addNewTask_setsNewTaskEvent.

TaskTaskViewModelTest.kt

    @Test
    fun addNewTask_setsNewTaskEvent() {

        // Given a fresh ViewModel
        val tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())

        // When adding a new task
        tasksViewModel.addNewTask()

        // Then the new task event is triggered
        // TODO test LiveData
    }
    

Aby przetestować aplikację LiveData, wykonaj 2 rzeczy:

  1. Używaj modułu: InstantTaskExecutorRule
  2. Zadbaj o LiveData obserwację

Krok 1. Używaj reguły ExTaskereororRule

InstantTaskExecutorRule to reguła JUnit. Użycie wraz z adnotacją @get:Rule spowoduje, że kod w klasie InstantTaskExecutorRule zostanie uruchomiony przed testami i po nich. Aby zobaczyć dokładny kod, użyj skrótu klawiszowego @get:RuleCommand + B.

Ta reguła uruchamia wszystkie zadania związane z komponentami architektury w tym samym wątku, dzięki czemu wyniki testu są synchroniczne i powtarzalne. Jeśli tworzysz testy, które obejmują testowanie LiveData, użyj tej reguły.

  1. Dodaj zależność Gradle w podstawowej bibliotece testowej komponentów architektury (która zawiera tę regułę).

app/build.gradle

testImplementation "androidx.arch.core:core-testing:$archTestingVersion"
  1. Otwórz: TasksViewModelTest.kt
  2. Dodaj InstantTaskExecutorRule do klasy TasksViewModelTest.

TaskTaskViewModelTest.kt

class TasksViewModelTest {
    @get:Rule
    var instantExecutorRule = InstantTaskExecutorRule()
    
    // Other code...
}

Krok 2. Dodaj klasę LiveDataTestUtil.kt

Teraz zadbaj o to, aby testowane przez Ciebie LiveData były przestrzegane.

Gdy używasz LiveData, zwykle występuje aktywność lub fragment (LifecycleOwner) obserwowany LiveData.

viewModel.resultLiveData.observe(fragment, Observer {
    // Observer code here
})

Ta obserwacja jest ważna. Musisz mieć aktywnych obserwatorów w LiveData, aby

Aby uzyskać oczekiwane działanie LiveData w modelu widoku danych LiveData, musisz użyć właściwości LiveData za pomocą właściwości LifecycleOwner.

Może to powodować problem: w teście TasksViewModel nie masz aktywności ani fragmentu, które można by zaobserwować na urządzeniu LiveData. Aby obejść ten problem, możesz użyć metody observeForever, która zapewnia ciągłość właściwości LiveData i nie wymaga użycia właściwości LifecycleOwner. Podczas korzystania z funkcji observeForever musisz usunąć obserwatora, aby nie doszło do wycieku informacji.

Wygląda to mniej więcej tak. Sprawdź to:

@Test
fun addNewTask_setsNewTaskEvent() {

    // Given a fresh ViewModel
    val tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())


    // Create observer - no need for it to do anything!
    val observer = Observer<Event<Unit>> {}
    try {

        // Observe the LiveData forever
        tasksViewModel.newTaskEvent.observeForever(observer)

        // When adding a new task
        tasksViewModel.addNewTask()

        // Then the new task event is triggered
        val value = tasksViewModel.newTaskEvent.value
        assertThat(value?.getContentIfNotHandled(), (not(nullValue())))

    } finally {
        // Whatever happens, don't forget to remove the observer!
        tasksViewModel.newTaskEvent.removeObserver(observer)
    }
}

To sporo powtarzalnego kodu, dzięki któremu można zobaczyć pojedynczy LiveData podczas testu. Istnieje kilka sposobów, aby pozbyć się tego problemu. Zamierzasz utworzyć funkcję rozszerzenia o nazwie LiveDataTestUtil, która ułatwi dodawanie obserwatorów.

  1. Utwórz nowy plik Kotlin o nazwie LiveDataTestUtil.kt w zbiorze źródłowym test.


  1. Skopiuj i wklej poniższy kod.

LiveDataTestUtil.kt,

import androidx.annotation.VisibleForTesting
import androidx.lifecycle.LiveData
import androidx.lifecycle.Observer
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
import java.util.concurrent.TimeoutException


@VisibleForTesting(otherwise = VisibleForTesting.NONE)
fun <T> LiveData<T>.getOrAwaitValue(
    time: Long = 2,
    timeUnit: TimeUnit = TimeUnit.SECONDS,
    afterObserve: () -> Unit = {}
): T {
    var data: T? = null
    val latch = CountDownLatch(1)
    val observer = object : Observer<T> {
        override fun onChanged(o: T?) {
            data = o
            latch.countDown()
            this@getOrAwaitValue.removeObserver(this)
        }
    }
    this.observeForever(observer)

    try {
        afterObserve.invoke()

        // Don't wait indefinitely if the LiveData is not set.
        if (!latch.await(time, timeUnit)) {
            throw TimeoutException("LiveData value was never set.")
        }

    } finally {
        this.removeObserver(observer)
    }

    @Suppress("UNCHECKED_CAST")
    return data as T
}

Jest to dość skomplikowana metoda. Funkcja ta tworzy funkcję rozszerzenia Kotlin o nazwie getOrAwaitValue, która dodaje obserwatora, pobiera wartość LiveData i oczyszcza obserwatora. W skrócie jest to skrócona wersja wielokrotnego użytku przedstawionego powyżej kodu observeForever. Pełne omówienie tych zajęć znajdziesz w tym poście na blogu.

Krok 3. Zapisz potwierdzenie za pomocą funkcji getOrAwaitValue

W tym kroku należy użyć metody getOrAwaitValue i utworzyć instrukcję potwierdzającą, że newTaskEvent został wywołany.

  1. Pobierz wartość LiveData dla newTaskEvent przy użyciu getOrAwaitValue.
val value = tasksViewModel.newTaskEvent.getOrAwaitValue()
  1. Upewnij się, że wartość nie jest pusta.
assertThat(value.getContentIfNotHandled(), (not(nullValue())))

Pełny test powinien wyglądać tak jak w poniższym kodzie.

import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.example.android.architecture.blueprints.todoapp.getOrAwaitValue
import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.Matchers.not
import org.hamcrest.Matchers.nullValue
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith

@RunWith(AndroidJUnit4::class)
class TasksViewModelTest {

    @get:Rule
    var instantExecutorRule = InstantTaskExecutorRule()


    @Test
    fun addNewTask_setsNewTaskEvent() {
        // Given a fresh ViewModel
        val tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())

        // When adding a new task
        tasksViewModel.addNewTask()

        // Then the new task event is triggered
        val value = tasksViewModel.newTaskEvent.getOrAwaitValue()

        assertThat(value.getContentIfNotHandled(), not(nullValue()))


    }

}
  1. Uruchom kod i obejrzyj test.

Skoro wiesz już, jak przygotować test, napisz go samodzielnie. Na podstawie tych zdobytych umiejętności poćwicz kolejne testy TasksViewModel.

Krok 1. Utwórz własny test ViewModel

Piszesz do: setFilterAllTasks_tasksAddViewVisible(). Ten test powinien sprawdzić, czy filtr jest ustawiony na wyświetlanie wszystkich zadań i czy widoczny jest przycisk Dodaj zadanie.

  1. Dla ułatwienia napisz addNewTask_setsNewTaskEvent() w aplikacji TasksViewModelTest o nazwie setFilterAllTasks_tasksAddViewVisible(), który ustawia tryb filtrowania na ALL_TASKS i potwierdza, że tasksAddViewVisibleLiveData to true.


Użyj poniższego kodu, aby rozpocząć.

TestujModelZadań

    @Test
    fun setFilterAllTasks_tasksAddViewVisible() {

        // Given a fresh ViewModel

        // When the filter type is ALL_TASKS

        // Then the "Add task" action is visible
        
    }

Uwaga:

  • Wartość TasksFilterType w przypadku wszystkich zadań wynosi ALL_TASKS.
  • Widoczność przycisku umożliwiającego dodanie zadania jest określana przez zasadę LiveData tasksAddViewVisible.
  1. Uruchom test.

Krok 2. Porównywanie testu z rozwiązaniem

Porównaj swoje rozwiązanie z rozwiązaniem przedstawionym poniżej.

TestujModelZadań

    @Test
    fun setFilterAllTasks_tasksAddViewVisible() {

        // Given a fresh ViewModel
        val tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())

        // When the filter type is ALL_TASKS
        tasksViewModel.setFiltering(TasksFilterType.ALL_TASKS)

        // Then the "Add task" action is visible
        assertThat(tasksViewModel.tasksAddViewVisible.getOrAwaitValue(), `is`(true))
    }

Sprawdź, czy wykonujesz te czynności:

  • tasksViewModel jest tworzony za pomocą tej samej instrukcji Androida ApplicationProvider.getApplicationContext().
  • Wywołujesz metodę setFiltering, przekazując wyliczenie typu filtra ALL_TASKS.
  • Aby sprawdzić, czy tasksAddViewVisible zawiera wartość prawda, użyj metody getOrAwaitNextValue.

Krok 3. Dodawanie reguły @Before

Zwróć uwagę, że na początku obu testów definiujesz parametr TasksViewModel.

TestujModelZadań

        // Given a fresh ViewModel
        val tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())

Jeśli kod konfiguracji jest powtarzany wielokrotnie, możesz użyć adnotacji @przed, aby utworzyć metodę konfiguracji i usunąć z niego powtarzające się kody. Ponieważ wszystkie te testy będą testować element TasksViewModel, a potrzebują modelu widoku danych, przenieś ten kod do bloku @Before.

  1. Utwórz zmienną instancji lateinit o nazwie tasksViewModel|.
  2. Utwórz metodę o nazwie setupViewModel.
  3. Dodaj adnotację do tekstu @Before.
  4. Przenieś kod instancji modelu widoku do pola setupViewModel.

TestujModelZadań

    // Subject under test
    private lateinit var tasksViewModel: TasksViewModel

    @Before
    fun setupViewModel() {
        tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())
    }
  1. Uruchom kod.

Ostrzeżenie

Nie wykonuj tych czynności, nie inicjuj

tasksViewModel

ze swoją definicją:

val tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())

Spowoduje to użycie tej samej instancji we wszystkich testach. Należy tego unikać, ponieważ każdy test powinien mieć nową instancję testowanego tematu (w tym przypadku ViewView).

Ostateczny kod tagu TasksViewModelTest powinien wyglądać tak jak poniżej.

TasksViewModelTest

@RunWith(AndroidJUnit4::class)
class TasksViewModelTest {

    // Subject under test
    private lateinit var tasksViewModel: TasksViewModel

    // Executes each task synchronously using Architecture Components.
    @get:Rule
    var instantExecutorRule = InstantTaskExecutorRule()

    @Before
    fun setupViewModel() {
        tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())
    }


    @Test
    fun addNewTask_setsNewTaskEvent() {

        // When adding a new task
        tasksViewModel.addNewTask()

        // Then the new task event is triggered
        val value = tasksViewModel.newTaskEvent.awaitNextValue()
        assertThat(
            value?.getContentIfNotHandled(), (not(nullValue()))
        )
    }

    @Test
    fun getTasksAddViewVisible() {

        // When the filter type is ALL_TASKS
        tasksViewModel.setFiltering(TasksFilterType.ALL_TASKS)

        // Then the "Add task" action is visible
        assertThat(tasksViewModel.tasksAddViewVisible.awaitNextValue(), `is`(true))
    }
    
}

Kliknij tutaj, aby zobaczyć różnice między uruchomionym kodem a ostatecznym kodem.

Aby pobrać kod ukończonych ćwiczeń z programowania, możesz użyć poniższego polecenia git:

$ git clone https://github.com/googlecodelabs/android-testing.git
$ cd android-testing
$ git checkout end_codelab_1


Możesz też pobrać repozytorium jako plik ZIP, rozpakować go i otworzyć w Android Studio.

Pobierz aplikację Zip

Tematy te:

  • Uruchamianie testów w Android Studio
  • Różnica między testami lokalnymi (test) a testami instrumentacji (androidTest).
  • Jak przygotowywać testy lokalne za pomocą JUnit i Hamcrest.
  • Konfigurowanie testów ViewModel w Bibliotece testowej Androida

Kurs Udacity:

Dokumentacja dla programistów Androida:

Materiały wideo:

Inne:

Linki do innych ćwiczeń z programowania znajdziesz w kursie dotyczącym programowania na Androida dla zaawansowanych w Kotlin.