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:
- Die Programmiersprache Kotlin
- Die folgenden Android Jetpack-Kernbibliotheken:
ViewModel
undLiveData
- Anwendungsarchitektur gemäß dem Muster aus dem Leitfaden zur App-Architektur und den Android-Grundlagen-Codelabs
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
- undViewModel
-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:
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:
- Codelab „Room with a View“
- Codelabs für die Android Kotlin Fundamentals-Schulung
- Codelabs für Fortgeschrittene für Android
- Android Sunflower-Beispiel
- Udacity-Schulungskurs „Android-Apps mit Kotlin entwickeln“
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 : | |
| Bildschirm zum Hinzufügen oder Bearbeiten einer Aufgabe:UI-Ebene-Code zum Hinzufügen oder Bearbeiten einer Aufgabe. |
| Die Datenschicht:Hier geht es um die Datenschicht der Aufgaben. Sie enthält den Datenbank-, Netzwerk- und Repository-Code. |
| Statistikbildschirm:UI-Layer-Code für den Statistikbildschirm. |
| Aufgabendetailansicht:UI-Ebene-Code für eine einzelne Aufgabe. |
| Aufgabenbildschirm:UI-Layer-Code für die Liste aller Aufgaben. |
| 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 Event
s und führen die tatsächliche Navigation zwischen den Bildschirmen durch.
In dieser Aufgabe führen Sie Ihre ersten Tests aus.
- Ö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
- Öffnen Sie den
test
-Ordner, bis Sie die Datei ExampleUnitTest.kt finden. - 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:
- 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.
- Fügen Sie
assertEquals(3, 1 + 1)
zum Testaddition_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
}
}
- Führen Sie den Test aus.
- In den Testergebnissen sehen Sie ein „X“ neben dem Test.
- 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
.
- Öffnen Sie den
androidTest
-Quellensatz. - 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
- Öffnen Sie im Quellset
main
intodoapp.statistics
die DateiStatisticsUtils.kt
. - 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.
- Klicken Sie mit der rechten Maustaste auf
getActiveAndCompletedStats
und wählen Sie Generieren > Test aus.
Das Dialogfeld Test erstellen wird geöffnet:
- Ändern Sie den Klassennamen in
StatisticsUtilsTest
(anstattStatisticsUtilsKtTest
). Es ist etwas besser, wenn KT nicht im Namen der Testklasse enthalten ist. - 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. - 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.
- Wählen Sie das Verzeichnis
test
(nichtandroidTest
) aus, da Sie lokale Tests schreiben. - Klicken Sie auf OK.
- Beachten Sie die generierte Klasse
StatisticsUtilsTest
intest/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%.
- Öffnen Sie
StatisticsUtilsTest
. - 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
}
}
- Fügen Sie die Annotation
@Test
über dem Funktionsnamen hinzu, um anzugeben, dass es sich um einen Test handelt. - Erstellen Sie eine Liste mit Aufgaben.
// Create an active task
val tasks = listOf<Task>(
Task("title", "desc", isCompleted = false)
)
- Rufen Sie
getActiveAndCompletedStats
mit diesen Aufgaben auf.
// Call your function
val result = getActiveAndCompletedStats(tasks)
- 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)
}
}
- 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.
- Ö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 QuellsetandroidTest
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
- Aktualisieren Sie den
getActiveAndCompletedStats_noCompleted_returnsHundredZero()
-Test, damit anstelle vonassertEquals
die Hamcrest-AssertionassertThat
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))
}
}
- 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:
- Wenn es eine abgeschlossene Aufgabe und keine aktiven Aufgaben gibt, sollte der Prozentsatz für
activeTasks
0f
und der Prozentsatz für abgeschlossene Aufgaben100f
sein . - Wenn es zwei abgeschlossene und drei aktive Aufgaben gibt, sollte der Prozentsatz der abgeschlossenen Aufgaben
40f
und der Prozentsatz der aktiven Aufgaben60f
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.
- Schreiben Sie den Test mit der Struktur „Given, When, Then“ (Angenommen, Wenn, Dann) und einem Namen, der der Konvention entspricht.
- Bestätigen Sie, dass der Test fehlschlägt.
- Schreiben Sie den minimalen Code, damit der Test bestanden wird.
- 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.
- Wenn eine leere Liste (
emptyList()
) vorhanden ist, sollten beide Prozentsätze 0f sein. - Wenn beim Laden der Aufgaben ein Fehler aufgetreten ist, ist die Liste
null
und beide Prozentsätze sollten 0f sein. - 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.
- Korrigieren Sie den Fehler in
getActiveAndCompletedStats
, indem Sie0f
zurückgeben, wenntasks
den Wertnull
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
)
}
}
- 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
.
- Öffnen Sie den Kurs, den Sie testen möchten, im
tasks
-Paket.TasksViewModel.
- Klicken Sie im Code mit der rechten Maustaste auf den Klassennamen
TasksViewModel
-> Generate (Generieren) -> Test (Test).
- Klicken Sie auf dem Bildschirm Test erstellen auf OK, um die Standardeinstellungen zu übernehmen (Sie müssen keine Änderungen vornehmen).
- 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.
- 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:
- Fügen Sie die Core- und Ext-Abhängigkeiten von AndroidX Test hinzu.
- Fügen Sie die Abhängigkeit der Robolectric Testing Library hinzu.
- Klasse mit dem AndroidJUnit4-Test-Runner annotieren
- AndroidX-Testcode schreiben
Sie werden diese Schritte ausführen und anschließend verstehen, was sie gemeinsam bewirken.
Schritt 3: Gradle-Abhängigkeiten hinzufügen
- 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
- 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.getApplicationContex
t
, mit der ein Anwendungskontext abgerufen wird.
- Erstellen Sie mit
ApplicationProvider.getApplicationContext()
aus der AndroidX-Testbibliothek einTasksViewModel
.
TasksViewModelTest.kt
// Given a fresh ViewModel
val tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())
- Rufe
addNewTask
untertasksViewModel
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
}
- 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.
- 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:
- „
InstantTaskExecutorRule
“ verwenden 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.
- 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"
TasksViewModelTest.kt
öffnen- Fügen Sie
InstantTaskExecutorRule
in die KlasseTasksViewModelTest
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
onChanged
-Ereignisse auslösen.- Transformationen auslösen.
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.
- Erstellen Sie im Quellset
test
eine neue Kotlin-Datei mit dem NamenLiveDataTestUtil.kt
.
- 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.
- Rufen Sie den Wert
LiveData
fürnewTaskEvent
mitgetOrAwaitValue
ab.
val value = tasksViewModel.newTaskEvent.getOrAwaitValue()
- 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()))
}
}
- 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.
- Schreibe anhand von
addNewTask_setsNewTaskEvent()
einen Test inTasksViewModelTest
mit dem NamensetFilterAllTasks_tasksAddViewVisible()
, der den Filtermodus aufALL_TASKS
festlegt und bestätigt, dass dietasksAddViewVisible
LiveDatatrue
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 istALL_TASKS.
. - Die Sichtbarkeit der Schaltfläche zum Hinzufügen einer Aufgabe wird durch
LiveData
tasksAddViewVisible.
gesteuert.
- 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-AnweisungApplicationProvider.getApplicationContext()
. - Sie rufen die Methode
setFiltering
auf und übergeben das Enum für den FiltertypALL_TASKS
. - Sie prüfen mit der Methode
getOrAwaitNextValue
, obtasksAddViewVisible
„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.
- Erstellen Sie eine
lateinit
-Instanzvariable mit dem NamentasksViewModel|
. - Erstellen Sie eine Methode mit dem Namen
setupViewModel
. - Versehen Sie sie mit der Annotation
@Before
. - 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())
}
- 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.
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:
- Leitfaden zur App-Architektur
- JUnit4
- Hamcrest
- Robolectric-Testbibliothek
- AndroidX Test Library
- AndroidX Architecture Components Core Test Library
- Quellsets
- Tests über die Befehlszeile durchführen
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“.