Podstawowe informacje o testowaniu

Te warsztaty są częścią kursu Zaawansowany Android w Kotlinie. Najwięcej korzyści z tego kursu uzyskasz, jeśli przejdziesz wszystkie ćwiczenia w kolejności, ale nie jest to obowiązkowe. Wszystkie ćwiczenia z tego kursu znajdziesz na stronie docelowej ćwiczeń z zaawansowanego Androida w Kotlinie.

Wprowadzenie

Gdy wdrażasz pierwszą funkcję pierwszej aplikacji, prawdopodobnie uruchamiasz kod, aby sprawdzić, czy działa zgodnie z oczekiwaniami. Przeprowadzono test, ale był to test ręczny. W miarę dodawania i aktualizowania funkcji prawdopodobnie nadal uruchamiasz kod i sprawdzasz, czy działa. Jednak ręczne wykonywanie tej czynności za każdym razem jest męczące, podatne na błędy i nie pozwala na skalowanie.

Komputery świetnie radzą sobie ze skalowaniem i automatyzacją. Dlatego programiści w małych i dużych firmach piszą testy automatyczne, czyli testy, które są uruchamiane przez oprogramowanie i nie wymagają ręcznego obsługiwania aplikacji w celu sprawdzenia, czy kod działa.

Z tej serii ćwiczeń dowiesz się, jak utworzyć zbiór testów (nazywany pakietem testów) dla rzeczywistej aplikacji.

W tym pierwszym laboratorium dowiesz się, jak testować aplikacje na Androida. Napiszesz pierwsze testy i nauczysz się testować LiveDataViewModel.

Co warto wiedzieć

Musisz znać:

Czego się nauczysz

Dowiesz się z niego:

  • Jak pisać i uruchamiać testy jednostkowe na Androidzie
  • Jak korzystać z programowania opartego na testach
  • Wybieranie testów z instrumentacją i testów lokalnych

Poznasz te biblioteki i koncepcje związane z kodem:

Jakie zadania wykonasz

  • Konfigurowanie, przeprowadzanie i interpretowanie testów lokalnych i instrumentalnych na Androidzie.
  • Pisać testy jednostkowe na Androidzie za pomocą JUnit4 i Hamcrest.
  • Napisz proste testy LiveDataViewModel.

W tej serii ćwiczeń będziesz pracować z aplikacją TO-DO Notes. Umożliwia ona zapisywanie zadań do wykonania i wyświetlanie ich na liście. Możesz je oznaczać jako ukończone lub nieukończone, filtrować lub usuwać.

Ta aplikacja jest napisana w języku Kotlin, ma kilka ekranów, korzysta z komponentów Jetpack i jest zgodna z architekturą opisaną w przewodniku po architekturze aplikacji. Dzięki temu, że dowiesz się, jak testować tę aplikację, będziesz w stanie testować aplikacje, które korzystają z tych samych bibliotek i architektury.

Aby rozpocząć, pobierz kod:

Pobierz plik ZIP

Możesz też sklonować repozytorium GitHub, aby uzyskać kod:

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

W tym zadaniu uruchomisz aplikację i zapoznasz się z bazą kodu.

Krok 1. Uruchom przykładową aplikację

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

  • Utwórz nowe zadanie za pomocą pływającego przycisku czynności. Najpierw wpisz tytuł, a potem dodatkowe informacje o zadaniu. Zapisz go za pomocą pływającego przycisku czynności z zielonym znacznikiem wyboru.
  • Na liście zadań kliknij tytuł zadania, które właśnie zostało ukończone, i sprawdź ekran szczegółów, aby zobaczyć resztę opisu.
  • Na liście lub na ekranie szczegółów zaznacz pole wyboru tego zadania, aby ustawić jego stan na Ukończone.
  • Wróć do ekranu zadań, otwórz menu filtra i filtruj zadania według stanu AktywneUkończone.
  • Otwórz panel nawigacji i kliknij Statystyki.
  • Wróć do ekranu przeglądu i w menu panelu nawigacyjnego wybierz Wyczyść ukończone, aby usunąć wszystkie zadania ze stanem Ukończone.

Krok 2. Zapoznaj się z kodem przykładowej aplikacji

Aplikacja TO-DO jest oparta na popularnym przykładzie testowym i architektonicznym Architecture Blueprints (w wersji architektury reaktywnej). Aplikacja jest zgodna z architekturą opisaną w przewodniku po architekturze aplikacji. Korzysta z obiektów ViewModel z fragmentami, repozytorium i biblioteką Room. Jeśli znasz któryś z poniższych przykładów, ta aplikacja ma podobną architekturę:

Ważniejsze jest zrozumienie ogólnej architektury aplikacji niż dogłębne poznanie logiki na dowolnej warstwie.

Oto podsumowanie pakietów, które znajdziesz:

Pakiet: com.example.android.architecture.blueprints.todoapp

.addedittask

Ekran dodawania lub edytowania zadania: kod warstwy interfejsu użytkownika do dodawania lub edytowania zadania.

.data

Warstwa danych: dotyczy warstwy danych zadań. Zawiera kod bazy danych, sieci i repozytorium.

.statistics

Ekran statystyk: kod warstwy interfejsu ekranu statystyk.

.taskdetail

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

.tasks

Ekran zadań: kod warstwy interfejsu użytkownika dla listy wszystkich zadań.

.util

Klasy narzędziowe: klasy udostępnione używane w różnych częściach aplikacji, np. w układzie odświeżania przez przesunięcie, który jest używany na wielu ekranach.

Warstwa danych (.data)

Ta aplikacja zawiera symulowaną warstwę sieciową w pakiecie remote i warstwę bazy danych w pakiecie local. W tym projekcie warstwa sieci jest symulowana za pomocą funkcji HashMap z opóźnieniem, a nie za pomocą rzeczywistych żądań sieciowych.

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

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

Każdy z pakietów warstwy interfejsu zawiera fragment i model widoku oraz inne klasy wymagane w interfejsie (np. adapter listy zadań). TaskActivity to aktywność, która zawiera wszystkie fragmenty.

Nawigacja

Nawigacja w aplikacji jest kontrolowana przez komponent nawigacji. Jest on zdefiniowany w pliku nav_graph.xml. Nawigacja jest wywoływana w modelach widoku za pomocą klasy Event. Modele widoku określają też, jakie argumenty mają być przekazywane. Fragmenty obserwują Event i wykonują rzeczywistą nawigację między ekranami.

W tym zadaniu przeprowadzisz 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)

Te foldery są nazywane zestawami źródeł. Zbiory źródeł to foldery zawierające kod źródłowy aplikacji. Zbiory źródeł, które są oznaczone kolorem zielonym (androidTesttest), zawierają testy. Gdy tworzysz nowy projekt na Androida, domyślnie otrzymujesz te 3 zbiory źródeł: Są to:

  • main: zawiera kod aplikacji. Ten kod jest wspólny dla wszystkich wersji aplikacji, które możesz utworzyć (tzw. wariantów kompilacji).
  • androidTest: zawiera testy określane jako testy z użyciem instrumentacji.
  • test: zawiera testy lokalne.

Różnica między testami lokalnymitestami z użyciem instrumentacji polega na sposobie ich przeprowadzania.

Testy lokalne (test zbiór źródeł)

Te testy są uruchamiane lokalnie w JVM na komputerze deweloperskim i nie wymagają emulatora ani urządzenia fizycznego. Dlatego działają szybko, ale ich wierność jest mniejsza, co oznacza, że zachowują się mniej realistycznie.

W Android Studio testy lokalne są oznaczone zielono-czerwoną ikoną trójkąta.

Testy z instrumentacją (androidTest zbiór źródeł)

Testy te są przeprowadzane na rzeczywistych lub emulowanych urządzeniach z Androidem, więc odzwierciedlają to, co się stanie w rzeczywistości, ale są też znacznie wolniejsze.

W Android Studio testy z instrumentacją są reprezentowane przez ikonę Androida z zielonym i czerwonym trójkątem.

Krok 1. Przeprowadź test lokalny

  1. Otwórz folder test, aż znajdziesz plik ExampleUnitTest.kt.
  2. Kliknij go prawym przyciskiem myszy i wybierz Run ExampleUnitTest (Uruchom ExampleUnitTest).

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

  1. Zwróć uwagę na zielone ikony potwierdzenia i rozwiń wyniki testu, aby sprawdzić, czy jeden z testów o nazwie addition_isCorrect został zaliczony. Cieszę się, że dodawanie działa zgodnie z oczekiwaniami.

Krok 2. Spraw, aby test się nie powiódł

Poniżej znajdziesz test, który został właśnie przeprowadzony.

ExampleUnitTest.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)
   }
}

Zwróć uwagę, że testy

  • należą do klasy w jednym z testowych zestawów źródeł.
  • zawierają funkcje, które zaczynają się od adnotacji @Test (każda funkcja to pojedynczy test);
  • zwykle zawierają instrukcje asercji.

Android używa biblioteki testowej JUnit (w tym ćwiczeniu z programowania JUnit4). Zarówno asercje, jak i adnotacja @Test pochodzą z JUnit.

Asercja jest podstawą testu. Jest to instrukcja kodu, która sprawdza, czy kod lub aplikacja działały zgodnie z oczekiwaniami. W tym przypadku asercja to assertEquals(4, 2 + 2), która sprawdza, czy 4 równa się 2 + 2.

Aby zobaczyć, jak wygląda nieudany test, dodaj asercję, która z łatwością powinna zakończyć się niepowodzeniem. Sprawdzi, czy 3 = 1+1.

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

ExampleUnitTest.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. Przeprowadź test.
  1. W wynikach testu zobaczysz znak X obok testu.

  1. Zwróć też uwagę na:
  • Jeśli nie powiedzie się jedno sprawdzenie, cały test zakończy się niepowodzeniem.
  • Wyświetlona zostanie oczekiwana wartość (3) i wartość, która została faktycznie obliczona (2).
  • Przekierujemy Cię do wiersza nieudanej asercji (ExampleUnitTest.kt:16).

Krok 3. Przeprowadź test z instrumentacją

Testy instrumentowane znajdują się w androidTest zbiorze źródeł.

  1. Otwórz zestaw źródeł androidTest.
  2. Uruchom test o nazwie ExampleInstrumentedTest.

ExampleInstrumentedTest

@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 jest przeprowadzany na urządzeniu (w poniższym przykładzie na emulowanym telefonie Pixel 2):

Jeśli masz podłączone urządzenie lub uruchomiony emulator, test powinien zostać przeprowadzony na emulatorze.

W tym zadaniu napiszesz testy dla funkcji getActiveAndCompleteStats, która oblicza odsetek aktywnych i ukończonych zadań w statystykach aplikacji. Te liczby możesz zobaczyć na ekranie statystyk aplikacji.

Krok 1. Utwórz klasę testową

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

StatisticsUtils.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 przyjmuje listę zadań i zwraca StatsResult. StatsResult to klasa danych, która zawiera 2 liczby: odsetek zadań ukończonych i odsetek zadań aktywnych.

Android Studio udostępnia narzędzia do generowania testowych elementów zastępczych, które pomagają wdrażać testy tej funkcji.

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

Otworzy się okno Utwórz test:

  1. Zmień Nazwa zajęć: na StatisticsUtilsTest (zamiast StatisticsUtilsKtTest; lepiej, żeby nazwa zajęć testowych nie zawierała KT).
  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 (generuje to tylko dodatkowy kod, ale test napiszesz od zera).
  3. Kliknij OK.

Otworzy się okno Wybierz katalog docelowy:

Przeprowadzisz test lokalny, ponieważ funkcja wykonuje obliczenia matematyczne i nie zawiera żadnego kodu specyficznego dla Androida. Nie musisz więc uruchamiać go na prawdziwym ani emulowanym urządzeniu.

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

Krok 2. Napisz pierwszą funkcję testową

Napiszesz test, który sprawdzi:

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

StatisticsUtilsTest.kt

class StatisticsUtilsTest {

    fun getActiveAndCompletedStats_noCompleted_returnsHundredZero() {
        // Create an active task

        // Call your function

        // Check the result
    }
}
  1. Dodaj adnotację @Test nad nazwą funkcji, aby wskazać, że jest to test.
  2. Utwórz listę zadań.
// Create an active task 
val tasks = listOf<Task>(
            Task("title", "desc", isCompleted = false)
        )
  1. Wywołaj getActiveAndCompletedStats z tymi zadaniami.
// Call your function
val result = getActiveAndCompletedStats(tasks)
  1. Sprawdź, czy wartość result jest zgodna z oczekiwaniami, używając asercji.
// Check the result
assertEquals(result.completedTasksPercent, 0f)
assertEquals(result.activeTasksPercent, 100f)

Oto pełny kod.

StatisticsUtilsTest.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).

Powinien przejść:

Krok 3. Dodaj zależność Hamcrest

Testy działają jak dokumentacja tego, co robi Twój kod, więc dobrze, jeśli są czytelne dla ludzi. Porównaj te 2 twierdzenia:

assertEquals(result.completedTasksPercent, 0f)

// versus

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

Drugie stwierdzenie brzmi bardziej jak zdanie wypowiedziane przez człowieka. Jest on napisany przy użyciu platformy asercji o nazwie Hamcrest. Kolejnym dobrym narzędziem do pisania czytelnych asercji jest biblioteka Truth. W tym ćwiczeniu do pisania asercji będziesz używać biblioteki Hamcrest.

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

app/build.gradle

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

Zwykle podczas dodawania zależności używasz implementation, ale tutaj używasz testImplementation. Gdy będziesz gotowy(-a) do udostępnienia aplikacji całemu światu, nie powiększaj rozmiaru pliku APK za pomocą kodu testowego ani zależności w aplikacji. Możesz określić, czy biblioteka ma być uwzględniona w kodzie głównym czy w kodzie testowym, za pomocą konfiguracji Gradle. Najczęstsze konfiguracje to:

  • implementation– Zależność jest dostępna we wszystkich zestawach źródeł, w tym w zestawach źródeł testowych.
  • testImplementation – zależność jest dostępna tylko w zbiorze źródeł testowych.
  • androidTestImplementation – zależność jest dostępna tylko w androidTest zbiorze źródeł.

Używana konfiguracja określa, gdzie można używać zależności. Jeśli napiszesz:

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

Oznacza to, że biblioteka Hamcrest będzie dostępna tylko w zestawie źródeł testowych. Dzięki temu biblioteka Hamcrest nie będzie uwzględniona w końcowej wersji aplikacji.

Krok 4. Użyj Hamcrest do pisania asercji

  1. Zaktualizuj test getActiveAndCompletedStats_noCompleted_returnsHundredZero(), aby zamiast assertEquals używać funkcji assertThat biblioteki Hamcrest.
// REPLACE
assertEquals(result.completedTasksPercent, 0f)
assertEquals(result.activeTasksPercent, 100f)

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

W razie potrzeby możesz użyć opcji importowania import org.hamcrest.Matchers.`is`.

Ostateczny test będzie wyglądał jak kod poniżej.

StatisticsUtilsTest.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.

Te ćwiczenia z programowania nie nauczą Cię wszystkich szczegółów Hamcrest, więc jeśli chcesz dowiedzieć się więcej, zapoznaj się z oficjalnym samouczkiem.

To zadanie jest opcjonalne i ma charakter ćwiczenia.

W tym zadaniu napiszesz więcej testów za pomocą JUnit i Hamcrest. Napiszesz też testy, korzystając ze strategii wywodzącej się z praktyki programowania Test Driven Development. Programowanie sterowane testami (TDD) to szkoła programowania, która mówi, że zamiast najpierw pisać kod funkcji, najpierw piszesz testy. Następnie piszesz kod funkcji, aby przejść testy.

Krok 1. Pisanie testów

Napisz testy dla sytuacji, w których masz standardową listę zadań:

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

Krok 2. Pisanie testu na błąd

Kod getActiveAndCompletedStats zawiera błąd. Zwróć uwagę, że nie obsługuje on prawidłowo sytuacji, w której lista jest pusta lub ma wartość null. W obu tych przypadkach oba odsetki powinny 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 napisać testy, użyjesz programowania sterowanego testami. Programowanie sterowane testami przebiega w tych krokach.

  1. Napisz test, używając struktury „Given, When, Then” (Zakładając, że… gdy… to…), i nadaj mu nazwę zgodną z konwencją.
  2. Potwierdź, że test się nie powiódł.
  3. Napisz minimalny kod, aby test zakończył się powodzeniem.
  4. Powtórz te czynności dla wszystkich testów.

Zamiast zaczynać od naprawienia błędu, najpierw napiszesz testy. Następnie możesz potwierdzić, że masz testy, które chronią Cię przed przypadkowym ponownym wprowadzeniem tych błędów w przyszłości.

  1. Jeśli lista jest pusta (emptyList()), oba odsetki powinny wynosić 0f.
  2. Jeśli podczas wczytywania zadań wystąpił błąd, lista będzie pusta null, a oba odsetki powinny wynosić 0.
  3. Uruchom testy i sprawdź, czy zakończyły się niepowodzeniem:

Krok 3. Naprawianie błędu

Teraz, gdy masz już testy, napraw błąd.

  1. Popraw błąd w funkcji getActiveAndCompletedStats, zwracając 0f, jeśli tasks ma wartość null lub jest pusta:
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 wszystkie zostały zaliczone.

Stosując TDD i pisząc testy w pierwszej kolejności, pomagasz zapewnić, że:

  • Nowe funkcje zawsze mają powiązane testy, dzięki czemu testy działają jak dokumentacja tego, co robi Twój kod.
  • Testy sprawdzają, czy wyniki są prawidłowe, i chronią przed błędami, które już wystąpiły.

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

Oto wszystkie testy i odpowiadające im kody funkcji.

StatisticsUtilsTest.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))
    }
}

StatisticsUtils.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
        )
    }
}

Świetnie, że znasz już podstawy pisania i przeprowadzania testów. Następnie dowiesz się, jak pisać podstawowe testy ViewModelLiveData.

W dalszej części tego laboratorium dowiesz się, jak pisać testy dla 2 klas Androida, które są powszechne w większości aplikacji – ViewModelLiveData.

Zacznij od napisania testów dla TasksViewModel.


Skupisz się na testach, których cała logika znajduje się w modelu widoku i nie zależy od kodu repozytorium. Kod repozytorium obejmuje kod asynchroniczny, bazy danych i wywołania sieciowe, co zwiększa złożoność testów. Na razie tego unikniesz i skupisz się na pisaniu testów funkcji ViewModel, które nie testują bezpośrednio niczego w repozytorium.



Napisany przez Ciebie test sprawdzi, czy po wywołaniu metody addNewTask uruchamiany jest Event otwierający nowe okno zadania. Oto kod aplikacji, którą będziesz testować.

TasksViewModel.kt

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

Krok 1. Utwórz klasę TasksViewModelTest

Wykonaj te same czynności co w przypadku StatisticsUtilTest. W tym kroku utworzysz plik testowy dla TasksViewModelTest.

  1. Otwórz zajęcia, które chcesz przetestować, w pakiecie tasks TasksViewModel..
  2. W kodzie kliknij prawym przyciskiem myszy nazwę klasy TasksViewModel –> Wygeneruj –> Test.

  1. Na ekranie Utwórz test kliknij OK, aby zaakceptować ustawienia domyślne (nie musisz ich zmieniać).
  2. W oknie Wybierz katalog docelowy wybierz katalog test.

Krok 2. Rozpocznij pisanie testu ViewModel

W tym kroku dodasz test modelu widoku, aby sprawdzić, czy po wywołaniu metody addNewTask uruchamia się Event otwierający nowe okno zadania.

  1. Utwórz nowy test o nazwie addNewTask_setsNewTaskEvent.

TasksViewModelTest.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?

Gdy tworzysz instancję TasksViewModel na potrzeby testów, jej konstruktor wymaga kontekstu aplikacji. W tym teście nie tworzysz jednak pełnej aplikacji z aktywnościami, interfejsem i fragmentami. Jak więc uzyskać kontekst aplikacji?

TasksViewModelTest.kt

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

Biblioteki testowe AndroidX zawierają klasy i metody, które udostępniają wersje komponentów takich jak aplikacje i aktywności przeznaczone do testów. Jeśli przeprowadzasz test lokalny, w którym potrzebujesz symulowanych klas platformy Android(np. kontekstu aplikacji), wykonaj te czynności, aby prawidłowo skonfigurować AndroidX Test:

  1. Dodaj podstawowe i zewnętrzne zależności AndroidX Test.
  2. Dodaj zależność biblioteki testowej Robolectric.
  3. Dodaj do klasy adnotację mechanizmu uruchamiania testów AndroidJUnit4.
  4. Pisanie kodu testu AndroidX

Wykonaj te czynności, a potem dowiesz się, jak działają razem.

Krok 3. Dodawanie zależności Gradle

  1. Skopiuj te zależności do pliku build.gradle modułu aplikacji, aby dodać podstawowe zależności AndroidX Test core i ext oraz zależność testową 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. Dodawanie narzędzia JUnit Test Runner

  1. Dodaj @RunWith(AndroidJUnit4::class) nad klasą testową.

TasksViewModelTest.kt

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

Krok 5. Korzystanie z AndroidX Test

W tym momencie możesz użyć biblioteki AndroidX Test. Obejmuje to metodę ApplicationProvider.getApplicationContext, która pobiera kontekst aplikacji.

  1. Utwórz TasksViewModel za pomocą ApplicationProvider.getApplicationContext() z biblioteki testowej AndroidX.

TasksViewModelTest.kt

// Given a fresh ViewModel
val tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())
  1. Zadzwoń pod numer addNewTask, którego używa tasksViewModel.

TasksViewModelTest.kt

tasksViewModel.addNewTask()

Na tym etapie test powinien wyglądać jak poniższy kod.

TasksViewModelTest.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 działa.

Koncepcja: jak działa AndroidX Test?

Czym jest AndroidX Test?

AndroidX Test to zbiór bibliotek do testowania. Zawiera klasy i metody, które udostępniają wersje komponentów takich jak aplikacje i aktywności przeznaczone do testów. Na przykład ten napisany przez Ciebie kod jest przykładem funkcji AndroidX Test do uzyskiwania kontekstu aplikacji.

ApplicationProvider.getApplicationContext()

Jedną z zalet interfejsów API AndroidX Test jest to, że działają zarówno w przypadku testów lokalnych, jak i testów z instrumentacją. Jest to korzystne, ponieważ:

  • Ten sam test możesz przeprowadzić jako test lokalny lub test z instrumentacją.
  • Nie musisz poznawać różnych interfejsów API do testowania w przypadku testów lokalnych i instrumentowanych.

Na przykład jeśli kod został napisany przy użyciu bibliotek AndroidX Test, możesz przenieść klasę TasksViewModelTest z folderu test do folderu androidTest, a testy nadal będą działać. getApplicationContext() działa nieco inaczej w zależności od tego, czy jest uruchamiany jako test lokalny czy test z instrumentacją:

  • Jeśli jest to test z instrumentacją, otrzyma on rzeczywisty kontekst aplikacji dostarczony podczas uruchamiania emulatora lub łączenia się z rzeczywistym urządzeniem.
  • Jeśli jest to test lokalny, używa symulowanego środowiska Androida.

Co to jest Robolectric?

Symulowane środowisko Androida, którego AndroidX Test używa do testów lokalnych, jest udostępniane przez Robolectric. Robolectric to biblioteka, która tworzy symulowane środowisko Androida na potrzeby testów i działa szybciej niż uruchamianie emulatora lub testowanie na urządzeniu. Bez zależności Robolectric pojawi się ten błąd:

Do czego służy @RunWith(AndroidJUnit4::class)?

Uruchamiający test to komponent JUnit, który uruchamia testy. Bez narzędzia do uruchamiania testów nie można ich przeprowadzić. JUnit udostępnia domyślny program do uruchamiania testów, który otrzymujesz automatycznie. @RunWith zastępuje domyślny program do uruchamiania testów.

Program uruchamiający testy AndroidJUnit4 umożliwia uruchamianie testów AndroidX w różny sposób w zależności od tego, czy są to testy lokalne czy testy z instrumentacją.

Krok 6. Rozwiązywanie problemów z ostrzeżeniami Robolectric

Po uruchomieniu kodu zobaczysz, że używany jest Robolectric.

Dzięki AndroidX Test i programowi uruchamiającemu testy AndroidJunit4 nie musisz pisać ani jednej linijki kodu Robolectric.

Możesz zauważyć 2 ostrzeżenia.

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

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

  1. Dodaj do pliku Gradle ten wiersz, aby używany był prawidłowy plik 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. Przeprowadzanie testów na Androidzie Q wymaga Javy 9. Zamiast konfigurować Android Studio do używania Javy 9, w tym laboratorium zachowaj docelowy i kompilowany pakiet SDK na poziomie 28.

Podsumowanie:

  • Testy czystych modeli widoku zwykle można umieszczać w zestawie źródeł test, ponieważ ich kod nie wymaga zwykle Androida.
  • Możesz użyć biblioteki testowej AndroidX, aby uzyskać testowe wersje komponentów takich jak aplikacje i aktywności.
  • Jeśli musisz uruchomić symulowany kod Androida w test źródłowym, możesz dodać zależność Robolectric i adnotację @RunWith(AndroidJUnit4::class).

Gratulacje. Do uruchomienia testu używasz zarówno biblioteki testowej AndroidX, jak i Robolectric. Test nie został ukończony (nie napisano jeszcze instrukcji assert, jest tylko // TODO test LiveData). Instrukcji assert nauczysz się w LiveData.

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

Oto miejsce, w którym przerwaliśmy słuchanie audiobooka bez addNewTask_setsNewTaskEvent testu modelu.

TasksViewModelTest.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ć LiveData, zalecamy wykonanie 2 czynności:

  1. Używaj klawisza InstantTaskExecutorRule
  2. Sprawdź, czy LiveData obserwacja

Krok 1. Używanie reguły InstantTaskExecutorRule

InstantTaskExecutorRule to reguła JUnit. Gdy użyjesz go z adnotacją @get:Rule, spowoduje to uruchomienie części kodu w klasie InstantTaskExecutorRule przed testami i po nich (aby zobaczyć dokładny kod, możesz użyć skrótu klawiszowego Command+B, aby wyświetlić plik).

Ta reguła uruchamia wszystkie zadania w tle związane z komponentami architektury w tym samym wątku, dzięki czemu wyniki testów są synchronizowane i pojawiają się w powtarzalnej kolejności. Jeśli piszesz testy, które obejmują testowanie LiveData, użyj tej reguły.

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

app/build.gradle

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

TasksViewModelTest.kt

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

Krok 2. Dodawanie klasy LiveDataTestUtil.kt

Następnym krokiem jest upewnienie się, że testowany LiveData jest obserwowany.

Gdy używasz LiveData, aktywność lub fragment (LifecycleOwner) zwykle obserwuje LiveData.

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

Ta obserwacja jest ważna. AbyLiveData

Aby uzyskać oczekiwane działanie LiveData w przypadku LiveData modelu widoku, musisz obserwować LiveData za pomocą LifecycleOwner.

Stanowi to problem: w TasksViewModel nie masz aktywności ani fragmentu, aby obserwować LiveData. Aby to obejść, możesz użyć metody observeForever, która zapewnia ciągłą obserwację LiveData bez konieczności używania LifecycleOwner. Gdy observeForever, musisz pamiętać o usunięciu obserwatora, aby uniknąć wycieku informacji.

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

@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 dużo kodu szablonowego, aby zaobserwować pojedynczy element LiveData w teście. Istnieje kilka sposobów, aby się go pozbyć. Utworzysz funkcję rozszerzającą o nazwie LiveDataTestUtil, aby uprościć dodawanie obserwatorów.

  1. W zbiorze źródeł test utwórz nowy plik Kotlin o nazwie LiveDataTestUtil.kt.


  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. Tworzy funkcję rozszerzającą Kotlin o nazwie getOrAwaitValue, która dodaje obserwatora, pobiera wartość LiveData, a następnie usuwa obserwatora. Jest to w zasadzie krótka wersja kodu observeForever pokazanego powyżej, którą można ponownie wykorzystać. Pełne wyjaśnienie tej klasy znajdziesz w tym poście na blogu.

Krok 3. Użyj getOrAwaitValue, aby napisać asercję

W tym kroku użyjesz metody getOrAwaitValue i napiszesz instrukcję assert, która sprawdzi, czy wywołano funkcję newTaskEvent.

  1. Uzyskaj wartość LiveData dla newTaskEvent za pomocą getOrAwaitValue.
val value = tasksViewModel.newTaskEvent.getOrAwaitValue()
  1. Sprawdź, czy wartość nie jest pusta.
assertThat(value.getContentIfNotHandled(), (not(nullValue())))

Kompletny test powinien wyglądać tak, jak pokazano poniżej.

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 sprawdź, czy test został zaliczony.

Teraz, gdy wiesz już, jak napisać test, zrób to samodzielnie. W tym kroku, korzystając z nabytej wiedzy, przećwicz pisanie kolejnego testu TasksViewModel.

Krok 1. Pisanie własnego testu klasy ViewModel

Napiszesz setFilterAllTasks_tasksAddViewVisible(). Ten test powinien sprawdzać, czy jeśli typ filtra został ustawiony na wyświetlanie wszystkich zadań, przycisk Dodaj zadanie jest widoczny.

  1. Korzystając z addNewTask_setsNewTaskEvent() jako odniesienia, napisz test w TasksViewModelTest o nazwie setFilterAllTasks_tasksAddViewVisible(), który ustawia tryb filtrowania na ALL_TASKS i sprawdza, czy tasksAddViewVisible LiveData ma wartość true.


Aby rozpocząć, użyj kodu poniżej.

TasksViewModelTest

    @Test
    fun setFilterAllTasks_tasksAddViewVisible() {

        // Given a fresh ViewModel

        // When the filter type is ALL_TASKS

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

Uwaga:

  • Wartość wyliczenia TasksFilterType dla wszystkich zadań to ALL_TASKS..
  • Widoczność przycisku dodawania zadania jest kontrolowana przez ustawienie LiveData tasksAddViewVisible..
  1. Przeprowadź test.

Krok 2. Porównanie testu z rozwiązaniem

Porównaj swoje rozwiązanie z poniższym.

TasksViewModelTest

    @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:

  • tasksViewModel tworzysz za pomocą tego samego wyrażenia AndroidX ApplicationProvider.getApplicationContext().
  • Wywołujesz metodę setFiltering, przekazując wyliczenie typu filtra ALL_TASKS.
  • Sprawdź, czy wartość tasksAddViewVisible to „true” (prawda), używając metody getOrAwaitNextValue.

Krok 3. Dodawanie reguły @Before

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

TasksViewModelTest

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

Jeśli masz powtarzający się kod konfiguracji w przypadku wielu testów, możesz użyć adnotacji @Before, aby utworzyć metodę konfiguracji i usunąć powtarzający się kod. Wszystkie te testy będą testować TasksViewModel i wymagają modelu widoku, więc przenieś ten kod do bloku TasksViewModel.@Before

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

TasksViewModelTest

    // 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

z definicją:

val tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())

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

Ostateczny kod dla TasksViewModelTest powinien wyglądać tak, jak pokazano 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óżnicę między kodem początkowym a końcowym.

Aby pobrać kod ukończonego ćwiczenia, możesz użyć tego 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 plik ZIP

W tym ćwiczeniu z programowania omówiliśmy:

  • Jak uruchamiać testy w Androidzie Studio.
  • Różnica między testami lokalnymi (test) a testami z użyciem instrumentacji (androidTest).
  • Jak pisać lokalne testy jednostkowe za pomocą JUnitHamcrest.
  • Konfigurowanie testów ViewModel za pomocą biblioteki testowej AndroidX.

Kurs Udacity:

Dokumentacja dla deweloperów aplikacji na Androida:

Materiały wideo:

Inne:

Linki do innych ćwiczeń z tego kursu znajdziesz na stronie docelowej ćwiczeń z zaawansowanego Androida w Kotlinie.