Grundlagen des Testens

Dieses Codelab ist Teil des Kurses „Advanced Android in Kotlin“. Sie können den größten Nutzen aus diesem Kurs ziehen, wenn Sie die Codelabs der Reihe nach durcharbeiten. Das ist jedoch nicht zwingend erforderlich. Alle Codelabs des Kurses sind auf der Landingpage für Codelabs zu „Android für Fortgeschrittene mit Kotlin“ aufgeführt.

Einführung

Als Sie die erste Funktion Ihrer ersten App implementiert haben, haben Sie den Code wahrscheinlich ausgeführt, um zu prüfen, ob er wie erwartet funktioniert. Sie haben einen Test durchgeführt, allerdings einen manuellen Test. Während Sie Funktionen hinzugefügt und aktualisiert haben, haben Sie Ihren Code wahrscheinlich auch immer wieder ausgeführt und überprüft, ob er funktioniert. Das jedes Mal manuell zu tun, ist jedoch mühsam, fehleranfällig und nicht skalierbar.

Computer sind hervorragend für Skalierung und Automatisierung geeignet. Entwickler in großen und kleinen Unternehmen schreiben daher automatisierte Tests. Diese Tests werden von Software ausgeführt und Sie müssen die App nicht manuell bedienen, um zu prüfen, ob der Code funktioniert.

In dieser Reihe von Codelabs erfahren Sie, wie Sie eine Sammlung von Tests (eine Testsuite) für eine echte App erstellen.

In diesem ersten Codelab werden die Grundlagen des Testens auf Android behandelt. Sie schreiben Ihre ersten Tests und erfahren, wie Sie LiveData und ViewModel testen.

Was Sie bereits wissen sollten

Sie sollten mit Folgendem vertraut sein:

Lerninhalte

Folgende Themen werden behandelt:

  • Unittests unter Android schreiben und ausführen
  • Testgetriebene Entwicklung verwenden
  • Instrumentierte Tests und lokale Tests auswählen

Sie lernen die folgenden Bibliotheken und Codekonzepte kennen:

Aufgaben

  • Lokale und instrumentierte Tests in Android einrichten, ausführen und interpretieren.
  • Unittests in Android mit JUnit4 und Hamcrest schreiben
  • Einfache LiveData- und ViewModel-Tests schreiben

In dieser Reihe von Codelabs arbeiten Sie mit der App „TO-DO Notes“. Mit der App können Sie Aufgaben aufschreiben, die Sie erledigen müssen, und sie werden in einer Liste angezeigt. Sie können sie dann als erledigt oder nicht erledigt markieren, filtern oder löschen.

Diese App ist in Kotlin geschrieben, hat mehrere Bildschirme, verwendet Jetpack-Komponenten und folgt der Architektur aus dem Leitfaden zur App-Architektur. Wenn Sie lernen, wie Sie diese App testen, können Sie auch Apps testen, die dieselben Bibliotheken und dieselbe Architektur verwenden.

Laden Sie zuerst den Code herunter:

Zip herunterladen

Alternativ können Sie das Github-Repository für den Code klonen:

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

In dieser Aufgabe führen Sie die App aus und sehen sich die Codebasis an.

Schritt 1: Beispiel-App ausführen

Öffnen Sie die TO-DO-App in Android Studio und führen Sie sie aus. Es sollte kompiliert werden. So können Sie die App ausprobieren:

  • Erstellen Sie eine neue Aufgabe mit der schwebenden Aktionsschaltfläche „Pluszeichen“. Geben Sie zuerst einen Titel und dann zusätzliche Informationen zur Aufgabe ein. Speichern Sie sie mit der grünen schwebenden Aktionsschaltfläche.
  • Klicken Sie in der Aufgabenliste auf den Titel der gerade erledigten Aufgabe und sehen Sie sich auf dem Detailbildschirm für diese Aufgabe den Rest der Beschreibung an.
  • Setzen Sie in der Liste oder auf dem Detailbildschirm ein Häkchen in das Kästchen der Aufgabe, um den Status auf Abgeschlossen zu setzen.
  • Kehren Sie zum Aufgabenbildschirm zurück, öffnen Sie das Filtermenü und filtern Sie die Aufgaben nach dem Status Aktiv und Abgeschlossen.
  • Öffne die Navigationsleiste und klicke auf Statistiken.
  • Kehren Sie zum Übersichtsbildschirm zurück und wählen Sie im Navigationsmenü Abgeschlossene löschen aus, um alle Aufgaben mit dem Status Abgeschlossen zu löschen.

Schritt 2: Beispiel-App-Code ansehen

Die TO-DO-App basiert auf dem beliebten Test- und Architekturbeispiel Architecture Blueprints (mit der reaktiven Architektur-Version des Beispiels). Die App folgt der Architektur aus dem Leitfaden zur App-Architektur. Es verwendet ViewModels mit Fragments, ein Repository und Room. Wenn Sie mit einem der folgenden Beispiele vertraut sind, hat diese App eine ähnliche Architektur:

Es ist wichtiger, dass Sie die allgemeine Architektur der App verstehen, als dass Sie die Logik auf einer bestimmten Ebene genau kennen.

Hier ist eine Zusammenfassung der Pakete:

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

.addedittask

Bildschirm zum Hinzufügen oder Bearbeiten einer Aufgabe:UI-Ebene-Code zum Hinzufügen oder Bearbeiten einer Aufgabe.

.data

Die Datenschicht:Hier geht es um die Datenschicht der Aufgaben. Sie enthält den Datenbank-, Netzwerk- und Repository-Code.

.statistics

Statistikbildschirm:UI-Layer-Code für den Statistikbildschirm.

.taskdetail

Aufgabendetailansicht:UI-Ebene-Code für eine einzelne Aufgabe.

.tasks

Aufgabenbildschirm:UI-Layer-Code für die Liste aller Aufgaben.

.util

Hilfsklassen:Gemeinsam genutzte Klassen, die in verschiedenen Teilen der App verwendet werden, z.B. für das Layout zum Aktualisieren per Wischen, das auf mehreren Bildschirmen verwendet wird.

Datenschicht (.data)

Diese App enthält eine simulierte Netzwerkschicht im Paket remote und eine Datenschicht im Paket local. Der Einfachheit halber wird die Netzwerkschicht in diesem Projekt nur mit einem HashMap mit einer Verzögerung simuliert, anstatt echte Netzwerkanfragen zu stellen.

Die DefaultTasksRepository-Koordinaten oder ‑Vermittlungen zwischen der Netzwerk- und der Datenbankebene und geben Daten an die UI-Ebene zurück.

UI-Ebene ( .addedittask, .statistics, .taskdetail, .tasks)

Jedes der UI-Layer-Pakete enthält ein Fragment und ein View-Modell sowie alle anderen Klassen, die für die Benutzeroberfläche erforderlich sind, z. B. einen Adapter für die Aufgabenliste. TaskActivity ist die Aktivität, die alle Fragmente enthält.

Navigation

Die Navigation für die App wird über die Navigationskomponente gesteuert. Sie ist in der Datei nav_graph.xml definiert. Die Navigation wird in den Ansichtsmodellen mit der Klasse Event ausgelöst. Die Ansichtsmodelle legen auch fest, welche Argumente übergeben werden sollen. Die Fragmente beobachten die Events und führen die tatsächliche Navigation zwischen den Bildschirmen durch.

In dieser Aufgabe führen Sie Ihre ersten Tests aus.

  1. Öffnen Sie in Android Studio den Bereich Projekt und suchen Sie nach diesen drei Ordnern:
  • com.example.android.architecture.blueprints.todoapp
  • com.example.android.architecture.blueprints.todoapp (androidTest)
  • com.example.android.architecture.blueprints.todoapp (test)

Diese Ordner werden als Quellsätze bezeichnet. Quellsätze sind Ordner, die den Quellcode für Ihre App enthalten. Die Quellsätze, die grün gefärbt sind (androidTest und test), enthalten Ihre Tests. Wenn Sie ein neues Android-Projekt erstellen, erhalten Sie standardmäßig die folgenden drei Quellsätze. Diese sind:

  • main: Enthält Ihren App-Code. Dieser Code wird von allen verschiedenen Versionen der App, die Sie erstellen können (Build-Varianten), gemeinsam genutzt.
  • androidTest: Enthält instrumentierte Tests.
  • test: Enthält Tests, die als lokale Tests bezeichnet werden.

Der Unterschied zwischen lokalen Tests und instrumentierten Tests liegt in der Art und Weise, wie sie ausgeführt werden.

Lokale Tests (test Quellgruppe)

Diese Tests werden lokal auf der JVM Ihres Entwicklercomputers ausgeführt und erfordern keinen Emulator oder kein physisches Gerät. Daher werden sie schnell ausgeführt, aber ihre Genauigkeit ist geringer. Das bedeutet, dass sie sich weniger wie in der realen Welt verhalten.

In Android Studio werden lokale Tests durch ein grünes und rotes Dreieckssymbol dargestellt.

Instrumentierte Tests (androidTest Quellgruppe)

Diese Tests werden auf echten oder emulierten Android-Geräten ausgeführt. Sie spiegeln also wider, was in der realen Welt passiert, sind aber auch viel langsamer.

In Android Studio werden instrumentierte Tests durch ein Android-Symbol mit einem grünen und einem roten Dreieck dargestellt.

Schritt 1: Lokalen Test ausführen

  1. Öffnen Sie den test-Ordner, bis Sie die Datei ExampleUnitTest.kt finden.
  2. Klicken Sie mit der rechten Maustaste darauf und wählen Sie Run ExampleUnitTest aus.

Im Fenster Ausführen unten auf dem Bildschirm sollte die folgende Ausgabe angezeigt werden:

  1. Achten Sie auf die grünen Häkchen und maximieren Sie die Testergebnisse, um zu bestätigen, dass ein Test namens addition_isCorrect bestanden wurde. Es ist gut zu wissen, dass die Addition wie erwartet funktioniert.

Schritt 2: Test fehlschlagen lassen

Unten sehen Sie den Test, den Sie gerade ausgeführt haben.

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

Beachten Sie, dass Tests

  • eine Klasse in einem der Testquellensätze ist.
  • Funktionen enthalten, die mit der Annotation @Test beginnen (jede Funktion ist ein einzelner Test).
  • Sie enthalten in der Regel Assertionsanweisungen.

Android verwendet die Testbibliothek JUnit für Tests (in diesem Codelab JUnit4). Sowohl die Zusicherungen als auch die Annotation @Test stammen von JUnit.

Eine Assertion ist der Kern Ihres Tests. Es handelt sich um eine Codeanweisung, mit der geprüft wird, ob sich Ihr Code oder Ihre App wie erwartet verhalten hat. In diesem Fall ist die Behauptung assertEquals(4, 2 + 2), die prüft, ob 4 gleich 2 + 2 ist.

Um zu sehen, wie ein fehlgeschlagener Test aussieht, fügen Sie eine Assertion hinzu, die Ihrer Meinung nach leicht fehlschlagen sollte. Es wird geprüft, ob 3 = 1 + 1 ist.

  1. Fügen Sie assertEquals(3, 1 + 1) zum Test addition_isCorrect hinzu.

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. Führen Sie den Test aus.
  1. In den Testergebnissen sehen Sie ein „X“ neben dem Test.

  1. Beachten Sie außerdem Folgendes:
  • Wenn eine Assertion fehlschlägt, schlägt der gesamte Test fehl.
  • Sie sehen den erwarteten Wert (3) im Vergleich zum tatsächlich berechneten Wert (2).
  • Sie werden zur Zeile der fehlgeschlagenen Assertion (ExampleUnitTest.kt:16) weitergeleitet.

Schritt 3: Instrumentierungstest ausführen

Instrumentierte Tests befinden sich im Quellset androidTest.

  1. Öffnen Sie den androidTest-Quellensatz.
  2. Führen Sie den Test mit dem Namen ExampleInstrumentedTest aus.

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

Im Gegensatz zum lokalen Test wird dieser Test auf einem Gerät ausgeführt (im Beispiel unten auf einem emulierten Pixel 2-Smartphone):

Wenn Sie ein Gerät angeschlossen oder einen Emulator ausgeführt haben, sollte der Test auf dem Emulator ausgeführt werden.

In dieser Aufgabe schreiben Sie Tests für getActiveAndCompleteStats, mit der der Prozentsatz der Statistiken zu aktiven und abgeschlossenen Aufgaben für Ihre App berechnet wird. Diese Zahlen können Sie auf dem Statistikbildschirm der App sehen.

Schritt 1: Testklasse erstellen

  1. Öffnen Sie im Quellset main in todoapp.statistics die Datei StatisticsUtils.kt.
  2. Suchen Sie die Funktion 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)

Die Funktion getActiveAndCompletedStats akzeptiert eine Liste von Aufgaben und gibt ein StatsResult zurück. StatsResult ist eine Datenklasse, die zwei Zahlen enthält: den Prozentsatz der Aufgaben, die abgeschlossen sind, und den Prozentsatz der Aufgaben, die aktiv sind.

Android Studio bietet Tools zum Generieren von Test-Stubs, die Ihnen bei der Implementierung der Tests für diese Funktion helfen.

  1. Klicken Sie mit der rechten Maustaste auf getActiveAndCompletedStats und wählen Sie Generieren > Test aus.

Das Dialogfeld Test erstellen wird geöffnet:

  1. Ändern Sie den Klassennamen in StatisticsUtilsTest (anstatt StatisticsUtilsKtTest). Es ist etwas besser, wenn KT nicht im Namen der Testklasse enthalten ist.
  2. Behalten Sie die restlichen Standardeinstellungen bei. JUnit 4 ist die passende Testbibliothek. Das Zielpaket ist korrekt (es entspricht dem Speicherort der Klasse StatisticsUtils) und Sie müssen keine der Kästchen ankreuzen. Dadurch wird nur zusätzlicher Code generiert, aber Sie schreiben Ihren Test von Grund auf neu.
  3. Drücken Sie OK.

Das Dialogfeld Zielverzeichnis auswählen wird geöffnet:

Sie führen einen lokalen Test durch, da Ihre Funktion mathematische Berechnungen ausführt und keinen Android-spezifischen Code enthält. Sie müssen es also nicht auf einem echten oder emulierten Gerät ausführen.

  1. Wählen Sie das Verzeichnis test (nicht androidTest) aus, da Sie lokale Tests schreiben.
  2. Klicken Sie auf OK.
  3. Beachten Sie die generierte Klasse StatisticsUtilsTest in test/statistics/.

Schritt 2: Erste Testfunktion schreiben

Sie schreiben einen Test, der Folgendes prüft:

  • wenn es keine abgeschlossenen Aufgaben und eine aktive Aufgabe gibt,
  • Der Prozentsatz der aktiven Tests beträgt 100 %.
  • und der Prozentsatz der erledigten Aufgaben beträgt 0%.
  1. Öffnen Sie StatisticsUtilsTest.
  2. Erstellen Sie eine Funktion mit dem Namen getActiveAndCompletedStats_noCompleted_returnsHundredZero.

StatisticsUtilsTest.kt

class StatisticsUtilsTest {

    fun getActiveAndCompletedStats_noCompleted_returnsHundredZero() {
        // Create an active task

        // Call your function

        // Check the result
    }
}
  1. Fügen Sie die Annotation @Test über dem Funktionsnamen hinzu, um anzugeben, dass es sich um einen Test handelt.
  2. Erstellen Sie eine Liste mit Aufgaben.
// Create an active task 
val tasks = listOf<Task>(
            Task("title", "desc", isCompleted = false)
        )
  1. Rufen Sie getActiveAndCompletedStats mit diesen Aufgaben auf.
// Call your function
val result = getActiveAndCompletedStats(tasks)
  1. Prüfen Sie mit Assertions, ob result Ihren Erwartungen entspricht.
// Check the result
assertEquals(result.completedTasksPercent, 0f)
assertEquals(result.activeTasksPercent, 100f)

Hier ist der vollständige Code.

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. Führen Sie den Test aus (klicken Sie mit der rechten Maustaste auf StatisticsUtilsTest und wählen Sie Ausführen aus).

Sie sollte bestanden werden:

Schritt 3: Hamcrest-Abhängigkeit hinzufügen

Da Ihre Tests als Dokumentation der Funktion Ihres Codes dienen, ist es gut, wenn sie für Menschen lesbar sind. Vergleichen Sie die folgenden beiden Behauptungen:

assertEquals(result.completedTasksPercent, 0f)

// versus

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

Die zweite Behauptung liest sich viel eher wie ein von einem Menschen verfasster Satz. Es wurde mit einem Assertion-Framework namens Hamcrest geschrieben. Ein weiteres gutes Tool zum Schreiben lesbarer Assertions ist die Truth-Bibliothek. In diesem Codelab verwenden Sie Hamcrest, um Zusicherungen zu schreiben.

  1. Öffnen Sie build.grade (Module: app) und fügen Sie die folgende Abhängigkeit hinzu.

app/build.gradle

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

Normalerweise verwenden Sie implementation, wenn Sie eine Abhängigkeit hinzufügen. Hier verwenden Sie jedoch testImplementation. Wenn Sie Ihre App für die Öffentlichkeit freigeben möchten, sollten Sie die Größe Ihres APK nicht durch Testcode oder Abhängigkeiten in Ihrer App unnötig aufblähen. Sie können mit Gradle-Konfigurationen festlegen, ob eine Bibliothek im Haupt- oder Testcode enthalten sein soll. Die häufigsten Konfigurationen sind:

  • implementation: Die Abhängigkeit ist in allen Quellsätzen verfügbar, einschließlich der Testquellsätze.
  • testImplementation: Die Abhängigkeit ist nur im Testquellenset verfügbar.
  • androidTestImplementation: Die Abhängigkeit ist nur im Quellset androidTest verfügbar.

Die verwendete Konfiguration bestimmt, wo die Abhängigkeit verwendet werden kann. Wenn Sie Folgendes eingeben:

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

Das bedeutet, dass Hamcrest nur im Test-Quellset verfügbar ist. Außerdem wird so sichergestellt, dass Hamcrest nicht in Ihre endgültige App aufgenommen wird.

Schritt 4: Hamcrest zum Schreiben von Assertions verwenden

  1. Aktualisieren Sie den getActiveAndCompletedStats_noCompleted_returnsHundredZero()-Test, damit anstelle von assertEquals die Hamcrest-Assertion assertThat verwendet wird.
// REPLACE
assertEquals(result.completedTasksPercent, 0f)
assertEquals(result.activeTasksPercent, 100f)

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

Sie können den Import import org.hamcrest.Matchers.`is` verwenden, wenn Sie dazu aufgefordert werden.

Der endgültige Test sieht so aus:

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. Führen Sie den aktualisierten Test aus, um zu prüfen, ob er weiterhin funktioniert.

In diesem Codelab werden nicht alle Details von Hamcrest behandelt. Wenn Sie mehr erfahren möchten, sehen Sie sich das offizielle Tutorial an.

Dies ist eine optionale Übungsaufgabe.

In dieser Aufgabe schreiben Sie weitere Tests mit JUnit und Hamcrest. Außerdem schreiben Sie Tests mit einer Strategie, die aus der Programmierpraxis der testgetriebenen Entwicklung abgeleitet wurde. Testgetriebene Entwicklung (Test-Driven Development, TDD) ist eine Programmiermethode, bei der zuerst die Tests geschrieben werden, anstatt zuerst den Code für die Funktion. Anschließend schreiben Sie den Code für die Funktion mit dem Ziel, die Tests zu bestehen.

Schritt 1: Tests schreiben

Tests für eine normale Aufgabenliste schreiben:

  1. Wenn es eine abgeschlossene Aufgabe und keine aktiven Aufgaben gibt, sollte der Prozentsatz für activeTasks 0f und der Prozentsatz für abgeschlossene Aufgaben 100f sein .
  2. Wenn es zwei abgeschlossene und drei aktive Aufgaben gibt, sollte der Prozentsatz der abgeschlossenen Aufgaben 40f und der Prozentsatz der aktiven Aufgaben 60f sein.

Schritt 2: Test für einen Fehler schreiben

Der Code für getActiveAndCompletedStats, wie er geschrieben wurde, enthält einen Fehler. Beachten Sie, dass nicht richtig berücksichtigt wird, was passiert, wenn die Liste leer oder null ist. In beiden Fällen sollten beide Prozentsätze null sein.

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()
   )
  
}

Um den Code zu korrigieren und Tests zu schreiben, verwenden Sie die testgetriebene Entwicklung. Test Driven Development umfasst die folgenden Schritte.

  1. Schreiben Sie den Test mit der Struktur „Given, When, Then“ (Angenommen, Wenn, Dann) und einem Namen, der der Konvention entspricht.
  2. Bestätigen Sie, dass der Test fehlschlägt.
  3. Schreiben Sie den minimalen Code, damit der Test bestanden wird.
  4. Wiederholen Sie den Vorgang für alle Tests.

Anstatt mit der Fehlerbehebung zu beginnen, schreiben Sie zuerst die Tests. Anschließend können Sie bestätigen, dass Sie Tests haben, die Sie davor schützen, diese Fehler in Zukunft versehentlich wieder einzuführen.

  1. Wenn eine leere Liste (emptyList()) vorhanden ist, sollten beide Prozentsätze 0f sein.
  2. Wenn beim Laden der Aufgaben ein Fehler aufgetreten ist, ist die Liste null und beide Prozentsätze sollten 0f sein.
  3. Führen Sie Ihre Tests aus und bestätigen Sie, dass sie fehlschlagen:

Schritt 3: Fehler beheben

Nachdem Sie die Tests haben, können Sie den Fehler beheben.

  1. Korrigieren Sie den Fehler in getActiveAndCompletedStats, indem Sie 0f zurückgeben, wenn tasks den Wert null hat oder leer ist:
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. Führen Sie die Tests noch einmal aus und prüfen Sie, ob alle Tests jetzt bestanden werden.

Durch die Befolgung von TDD und das Schreiben der Tests zuerst haben Sie dazu beigetragen, dass:

  • Neue Funktionen haben immer zugehörige Tests. Ihre Tests dienen also als Dokumentation dafür, was Ihr Code tut.
  • Ihre Tests prüfen auf die richtigen Ergebnisse und schützen vor Fehlern, die Sie bereits gesehen haben.

Lösung: Mehr Tests schreiben

Hier finden Sie alle Tests und den entsprechenden Funktionscode.

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

Sie haben die Grundlagen zum Schreiben und Ausführen von Tests gut gemeistert. Als Nächstes erfahren Sie, wie Sie grundlegende ViewModel- und LiveData-Tests schreiben.

Im restlichen Codelab erfahren Sie, wie Sie Tests für zwei Android-Klassen schreiben, die in den meisten Apps verwendet werden: ViewModel und LiveData.

Zuerst schreiben Sie Tests für TasksViewModel.


Sie konzentrieren sich auf Tests, deren gesamte Logik im Ansichtsmodell enthalten ist und die nicht auf Repository-Code angewiesen sind. Repository-Code umfasst asynchronen Code, Datenbanken und Netzwerkaufrufe, die alle die Komplexität von Tests erhöhen. Das wollen wir jetzt vermeiden und uns auf das Schreiben von Tests für die ViewModel-Funktionalität konzentrieren, bei denen nichts im Repository direkt getestet wird.



Mit dem Test, den Sie schreiben, wird geprüft, ob beim Aufrufen der Methode addNewTask das Event zum Öffnen des neuen Aufgabenfensters ausgelöst wird. Hier ist der App-Code, den Sie testen werden.

TasksViewModel.kt

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

Schritt 1: TasksViewModelTest-Klasse erstellen

In diesem Schritt erstellen Sie eine Testdatei für TasksViewModelTest. Gehen Sie dabei genauso vor wie bei StatisticsUtilTest.

  1. Öffnen Sie den Kurs, den Sie testen möchten, im tasks-Paket. TasksViewModel.
  2. Klicken Sie im Code mit der rechten Maustaste auf den Klassennamen TasksViewModel -> Generate (Generieren) -> Test (Test).

  1. Klicken Sie auf dem Bildschirm Test erstellen auf OK, um die Standardeinstellungen zu übernehmen (Sie müssen keine Änderungen vornehmen).
  2. Wählen Sie im Dialogfeld Zielverzeichnis auswählen das Verzeichnis test aus.

Schritt 2: ViewModel-Test schreiben

In diesem Schritt fügen Sie einen Viewmodel-Test hinzu, um zu prüfen, ob beim Aufrufen der Methode addNewTask das Event zum Öffnen des neuen Aufgabenfensters ausgelöst wird.

  1. Erstellen Sie einen neuen Test mit dem Namen 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

    }
    
}

Was ist mit dem Anwendungskontext?

Wenn Sie eine Instanz von TasksViewModel zum Testen erstellen, ist für den Konstruktor ein Application Context erforderlich. Bei diesem Test erstellen Sie jedoch keine vollständige Anwendung mit Aktivitäten, Benutzeroberfläche und Fragmenten. Wie erhalten Sie also einen Anwendungskontext?

TasksViewModelTest.kt

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

Die AndroidX-Testbibliotheken enthalten Klassen und Methoden, die Ihnen Versionen von Komponenten wie Anwendungen und Aktivitäten zur Verfügung stellen, die für Tests vorgesehen sind. Wenn Sie einen lokalen Test haben, für den Sie simulierte Android-Framework-Klassen(z. B. einen Anwendungskontext) benötigen, gehen Sie so vor, um AndroidX Test richtig einzurichten:

  1. Fügen Sie die Core- und Ext-Abhängigkeiten von AndroidX Test hinzu.
  2. Fügen Sie die Abhängigkeit der Robolectric Testing Library hinzu.
  3. Klasse mit dem AndroidJUnit4-Test-Runner annotieren
  4. AndroidX-Testcode schreiben

Sie werden diese Schritte ausführen und anschließend verstehen, was sie gemeinsam bewirken.

Schritt 3: Gradle-Abhängigkeiten hinzufügen

  1. Kopieren Sie diese Abhängigkeiten in die Datei build.gradle Ihres App-Moduls, um die Core- und Ext-Abhängigkeiten von AndroidX Test sowie die Robolectric-Testabhängigkeit hinzuzufügen.

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"

Schritt 4: JUnit-Test-Ausführer hinzufügen

  1. Fügen Sie @RunWith(AndroidJUnit4::class) über Ihrer Testklasse hinzu.

TasksViewModelTest.kt

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

Schritt 5: AndroidX Test verwenden

An diesem Punkt können Sie die AndroidX-Testbibliothek verwenden. Dazu gehört die Methode ApplicationProvider.getApplicationContext, mit der ein Anwendungskontext abgerufen wird.

  1. Erstellen Sie mit ApplicationProvider.getApplicationContext() aus der AndroidX-Testbibliothek ein TasksViewModel.

TasksViewModelTest.kt

// Given a fresh ViewModel
val tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())
  1. Rufe addNewTask unter tasksViewModel an.

TasksViewModelTest.kt

tasksViewModel.addNewTask()

Ihr Test sollte jetzt so aussehen wie im Code unten.

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. Führen Sie den Test aus, um zu prüfen, ob er funktioniert.

Konzept: Wie funktioniert AndroidX Test?

Was ist AndroidX Test?

AndroidX Test ist eine Sammlung von Bibliotheken zum Testen. Sie enthält Klassen und Methoden, mit denen Sie Versionen von Komponenten wie Anwendungen und Aktivitäten erhalten, die für Tests vorgesehen sind. Der folgende Code ist ein Beispiel für eine AndroidX Test-Funktion zum Abrufen eines Anwendungskontexts.

ApplicationProvider.getApplicationContext()

Einer der Vorteile der AndroidX Test APIs ist, dass sie sowohl für lokale Tests als auch für instrumentierte Tests entwickelt wurden. Das hat folgende Vorteile:

  • Sie können denselben Test als lokalen Test oder als instrumentierten Test ausführen.
  • Sie müssen keine unterschiedlichen Test-APIs für lokale Tests und instrumentierte Tests lernen.

Da Sie Ihren Code beispielsweise mit AndroidX-Testbibliotheken geschrieben haben, können Sie die Klasse TasksViewModelTest aus dem Ordner test in den Ordner androidTest verschieben und die Tests werden trotzdem ausgeführt. Das getApplicationContext() funktioniert etwas anders, je nachdem, ob es als lokaler Test oder als instrumentierter Test ausgeführt wird:

  • Bei einem instrumentierten Test wird der tatsächliche Anwendungskontext bereitgestellt, wenn ein Emulator gestartet oder eine Verbindung zu einem echten Gerät hergestellt wird.
  • Bei einem lokalen Test wird eine simulierte Android-Umgebung verwendet.

Was ist Robolectric?

Die simulierte Android-Umgebung, die von AndroidX Test für lokale Tests verwendet wird, wird von Robolectric bereitgestellt. Robolectric ist eine Bibliothek, die eine simulierte Android-Umgebung für Tests erstellt und schneller ausgeführt wird als das Starten eines Emulators oder die Ausführung auf einem Gerät. Ohne die Robolectric-Abhängigkeit wird dieser Fehler angezeigt:

Was macht @RunWith(AndroidJUnit4::class)?

Ein Test-Runner ist eine JUnit-Komponente, die Tests ausführt. Ohne Test-Runner würden Ihre Tests nicht ausgeführt. JUnit stellt einen Standard-Test-Runner bereit, den Sie automatisch erhalten. @RunWith ersetzt diesen Standard-Test-Runner.

Mit dem AndroidJUnit4-Testrunner können Sie Ihre Tests mit AndroidX Test unterschiedlich ausführen, je nachdem, ob es sich um instrumentierte oder lokale Tests handelt.

Schritt 6: Robolectric-Warnungen beheben

Wenn Sie den Code ausführen, sehen Sie, dass Robolectric verwendet wird.

Dank AndroidX Test und dem AndroidJunit4-Testrunner ist dies möglich, ohne dass Sie eine einzige Zeile Robolectric-Code schreiben müssen.

Möglicherweise werden zwei Warnungen angezeigt.

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

Sie können die Warnung No such manifest file: ./AndroidManifest.xml beheben, indem Sie Ihre Gradle-Datei aktualisieren.

  1. Fügen Sie Ihrer Gradle-Datei die folgende Zeile hinzu, damit das richtige Android-Manifest verwendet wird. Mit der Option includeAndroidResources können Sie in Ihren Unit-Tests auf Android-Ressourcen zugreifen, einschließlich Ihrer AndroidManifest-Datei.

app/build.gradle

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

        // ... 
    }

Die Warnung "WARN: Android SDK 29 requires Java 9..." ist komplizierter. Für Tests unter Android Q ist Java 9 erforderlich. Anstatt Android Studio für die Verwendung von Java 9 zu konfigurieren, sollten Sie für dieses Codelab das Ziel- und das Kompilierungs-SDK auf Version 28 belassen.

Zusammenfassung:

  • Tests für reine Ansichtsmodelle können in der Regel im Quellset test abgelegt werden, da ihr Code normalerweise kein Android erfordert.
  • Mit der AndroidX-Testbibliothek können Sie Testversionen von Komponenten wie Anwendungen und Aktivitäten abrufen.
  • Wenn Sie simulierten Android-Code in Ihrem test-Quellsatz ausführen müssen, können Sie die Robolectric-Abhängigkeit und die Annotation @RunWith(AndroidJUnit4::class) hinzufügen.

Herzlichen Glückwunsch! Sie verwenden sowohl die AndroidX-Testbibliothek als auch Robolectric, um einen Test auszuführen. Ihr Test ist noch nicht abgeschlossen. Sie haben noch keine Assert-Anweisung geschrieben. Dort steht nur // TODO test LiveData. Im nächsten Schritt lernen Sie, wie Sie Assert-Anweisungen mit LiveData schreiben.

In dieser Aufgabe lernen Sie, wie Sie den Wert von LiveData richtig bestätigen.

Hier ist die Stelle, an der du die Wiedergabe pausiert hast, ohne den addNewTask_setsNewTaskEvent-Ansichtsmodelltest.

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
    }
    

Um LiveData zu testen, empfehlen wir Ihnen, zwei Dinge zu tun:

  1. InstantTaskExecutorRule“ verwenden
  2. LiveData-Beobachtung sicherstellen

Schritt 1: InstantTaskExecutorRule verwenden

InstantTaskExecutorRule ist eine JUnit-Regel. Wenn Sie sie mit der Annotation @get:Rule verwenden, wird vor und nach den Tests Code in der Klasse InstantTaskExecutorRule ausgeführt. Den genauen Code können Sie mit der Tastenkombination Befehl+B aufrufen.

Mit dieser Regel werden alle Architekturkomponenten-bezogenen Hintergrundjobs im selben Thread ausgeführt, sodass die Testergebnisse synchron und in einer wiederholbaren Reihenfolge erfolgen. Verwenden Sie diese Regel, wenn Sie Tests schreiben, die das Testen von LiveData umfassen.

  1. Fügen Sie die Gradle-Abhängigkeit für die Core-Testbibliothek für Architekturkomponenten hinzu, die diese Regel enthält.

app/build.gradle

testImplementation "androidx.arch.core:core-testing:$archTestingVersion"
  1. TasksViewModelTest.kt öffnen
  2. Fügen Sie InstantTaskExecutorRule in die Klasse TasksViewModelTest ein.

TasksViewModelTest.kt

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

Schritt 2: Klasse „LiveDataTestUtil.kt“ hinzufügen

Als Nächstes müssen Sie dafür sorgen, dass die LiveData, die Sie testen, beobachtet wird.

Wenn Sie LiveData verwenden, beobachtet in der Regel eine Aktivität oder ein Fragment (LifecycleOwner) die LiveData.

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

Diese Beobachtung ist wichtig. Sie benötigen aktive Beobachter auf LiveData, um

Um das erwartete LiveData-Verhalten für die LiveData Ihres Ansichtsmodells zu erhalten, müssen Sie die LiveData mit einem LifecycleOwner beobachten.

Das ist ein Problem: In Ihrem TasksViewModel-Test haben Sie keine Aktivität oder kein Fragment, um Ihr LiveData zu beobachten. Um dieses Problem zu umgehen, können Sie die Methode observeForever verwenden. Dadurch wird LiveData ständig beobachtet, ohne dass ein LifecycleOwner erforderlich ist. Wenn Sie observeForever, müssen Sie daran denken, Ihren Beobachter zu entfernen, da sonst das Risiko eines Beobachterlecks besteht.

Das sieht in etwa so aus wie der Code unten. So gehts:

@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)
    }
}

Das ist viel Boilerplate-Code, um ein einzelnes LiveData in einem Test zu beobachten. Es gibt verschiedene Möglichkeiten, diesen Boilerplate-Text zu entfernen. Sie erstellen eine Erweiterungsfunktion namens LiveDataTestUtil, um das Hinzufügen von Beobachtern zu vereinfachen.

  1. Erstellen Sie im Quellset test eine neue Kotlin-Datei mit dem Namen LiveDataTestUtil.kt.


  1. Kopieren Sie den folgenden Code und fügen Sie ihn ein.

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
}

Diese Methode ist ziemlich kompliziert. Dadurch wird eine Kotlin-Erweiterungsfunktion namens getOrAwaitValue erstellt, die einen Observer hinzufügt, den Wert LiveData abruft und den Observer dann bereinigt. Im Grunde ist das eine kurze, wiederverwendbare Version des oben gezeigten observeForever-Codes. Eine ausführliche Beschreibung dieser Klasse finden Sie in diesem Blogpost.

Schritt 3: getOrAwaitValue zum Schreiben der Assertion verwenden

In diesem Schritt verwenden Sie die Methode getOrAwaitValue und schreiben eine Assert-Anweisung, mit der geprüft wird, ob newTaskEvent ausgelöst wurde.

  1. Rufen Sie den Wert LiveData für newTaskEvent mit getOrAwaitValue ab.
val value = tasksViewModel.newTaskEvent.getOrAwaitValue()
  1. Sicherstellen, dass der Wert nicht null ist.
assertThat(value.getContentIfNotHandled(), (not(nullValue())))

Der vollständige Test sollte wie der Code unten aussehen.

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. Führen Sie Ihren Code aus und sehen Sie zu, wie der Test bestanden wird.

Nachdem Sie gesehen haben, wie man einen Test schreibt, können Sie jetzt selbst einen schreiben. In diesem Schritt üben Sie, einen weiteren TasksViewModel-Test zu schreiben.

Schritt 1: Eigenen ViewModel-Test schreiben

Sie schreiben setFilterAllTasks_tasksAddViewVisible(). Bei diesem Test sollte geprüft werden, ob die Schaltfläche Aufgabe hinzufügen sichtbar ist, wenn Sie den Filtertyp so eingestellt haben, dass alle Aufgaben angezeigt werden.

  1. Schreibe anhand von addNewTask_setsNewTaskEvent() einen Test in TasksViewModelTest mit dem Namen setFilterAllTasks_tasksAddViewVisible(), der den Filtermodus auf ALL_TASKS festlegt und bestätigt, dass die tasksAddViewVisible LiveData true ist.


Verwenden Sie den folgenden Code, um loszulegen.

TasksViewModelTest

    @Test
    fun setFilterAllTasks_tasksAddViewVisible() {

        // Given a fresh ViewModel

        // When the filter type is ALL_TASKS

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

Hinweis:

  • Der TasksFilterType-Enum für alle Aufgaben ist ALL_TASKS..
  • Die Sichtbarkeit der Schaltfläche zum Hinzufügen einer Aufgabe wird durch LiveData tasksAddViewVisible. gesteuert.
  1. Führen Sie den Test aus.

Schritt 2: Test mit der Lösung vergleichen

Vergleichen Sie Ihre Lösung mit der Lösung unten.

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

Prüfen Sie, ob Sie Folgendes tun:

  • Sie erstellen tasksViewModel mit derselben AndroidX-Anweisung ApplicationProvider.getApplicationContext().
  • Sie rufen die Methode setFiltering auf und übergeben das Enum für den Filtertyp ALL_TASKS.
  • Sie prüfen mit der Methode getOrAwaitNextValue, ob tasksAddViewVisible „true“ ist.

Schritt 3: @Before-Regel hinzufügen

Beachten Sie, dass Sie zu Beginn beider Tests eine TasksViewModel definieren.

TasksViewModelTest

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

Wenn Sie für mehrere Tests wiederholten Einrichtungscode haben, können Sie mit der Annotation @Before eine Einrichtungsmethode erstellen und wiederholten Code entfernen. Da in allen diesen Tests TasksViewModel getestet wird und ein View-Modell erforderlich ist, verschieben Sie diesen Code in einen @Before-Block.

  1. Erstellen Sie eine lateinit-Instanzvariable mit dem Namen tasksViewModel|.
  2. Erstellen Sie eine Methode mit dem Namen setupViewModel.
  3. Versehen Sie sie mit der Annotation @Before.
  4. Verschieben Sie den Code für die Instanziierung des Ansichtsmodells nach setupViewModel.

TasksViewModelTest

    // Subject under test
    private lateinit var tasksViewModel: TasksViewModel

    @Before
    fun setupViewModel() {
        tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())
    }
  1. Führen Sie den Code aus.

Warnung

Führen Sie die folgenden Schritte NICHT aus und initialisieren Sie NICHT

tasksViewModel

mit der Definition:

val tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())

Dadurch wird dieselbe Instanz für alle Tests verwendet. Das sollten Sie vermeiden, da für jeden Test eine neue Instanz des zu testenden Elements (in diesem Fall das ViewModel) verwendet werden sollte.

Der endgültige Code für TasksViewModelTest sollte so aussehen:

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

Klicken Sie hier, um einen Vergleich zwischen dem Code, mit dem Sie begonnen haben, und dem endgültigen Code zu sehen.

Wenn Sie den Code für das fertige Codelab herunterladen möchten, können Sie den folgenden Git-Befehl verwenden:

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


Alternativ können Sie das Repository als ZIP-Datei herunterladen, entzippen und in Android Studio öffnen.

Zip herunterladen

In diesem Codelab haben wir Folgendes behandelt:

  • Tests in Android Studio ausführen
  • Der Unterschied zwischen lokalen Tests (test) und Instrumentierungstests (androidTest).
  • Hier erfahren Sie, wie Sie lokale Einheitentests mit JUnit und Hamcrest schreiben.
  • ViewModel-Tests mit der AndroidX Test Library einrichten

Udacity-Kurs:

Android-Entwicklerdokumentation:

Videos:

Sonstiges:

Links zu anderen Codelabs in diesem Kurs finden Sie auf der Landingpage für Codelabs zum Thema „Android für Fortgeschrittene mit Kotlin“.