Test-Doubles in Android verwenden

Wenn Sie ein Element oder ein System von Elementen testen, geschieht dies isoliert. Wenn Sie beispielsweise ein ViewModel testen möchten, müssen Sie keinen Emulator starten und keine Benutzeroberfläche öffnen, da es nicht vom Android-Framework abhängt (oder abhängen sollte).

Es kann jedoch sein, dass die Testperson von anderen abhängen, damit sie funktioniert. Ein ViewModel kann beispielsweise von einem Datenrepository abhängen.

Wenn Sie eine Abhängigkeit für ein Testobjekt bereitstellen müssen, ist es üblich, einen Test-Double (oder ein Testobjekt) zu erstellen. Test Doubles sind Objekte, die wie Komponenten in Ihrer App aussehen und sich so verhalten, werden aber in Ihrem Test erstellt, um ein bestimmtes Verhalten oder Daten bereitzustellen. Die Hauptvorteile sind, dass sie Ihre Tests schneller und einfacher machen.

Arten von Test-Doubles

Es gibt verschiedene Arten von Test-Doubles:

Fake Ein Test-Double mit einer „funktionierenden“ Implementierung der Klasse, die jedoch so implementiert ist, dass sie für Tests geeignet, aber für die Produktion ungeeignet ist.

Beispiel: eine In-Memory-Datenbank.

Für Fakes ist kein Mocking-Framework erforderlich und sie sind effizient. Sie sind bevorzugt.

Mock Ein Test-Double, das sich so verhält, wie Sie es programmieren, und Erwartungen an seine Interaktionen haben. Mocks schlagen bei Tests fehl, wenn ihre Interaktionen nicht den von Ihnen definierten Anforderungen entsprechen. Mocks werden in der Regel mit einem Mocking-Framework erstellt, um all dies zu erreichen.

Beispiel: Überprüfen, ob eine Methode in einer Datenbank genau einmal aufgerufen wurde.

Stub Ein Test-Double, das sich wie programmiert verhält, aber keine Erwartungen an seine Interaktionen hat. Sie werden in der Regel mit einem Mocking-Framework erstellt. Fakes werden der Einfachheit halber gegenüber Stubs bevorzugt.
Dummy Ein Test-Double, das übergeben, aber nicht verwendet wird, z. B. wenn Sie es nur als Parameter angeben müssen.

Beispiel: Eine leere Funktion, die als Klick-Callback übergeben wird.

Spy – Susan Cooper Undercover Ein Wrapper für ein echtes Objekt, der ähnlich wie Mockups auch einige zusätzliche Informationen erfasst. Sie werden in der Regel vermieden, da sie die Komplexität erhöhen. Daher werden Fakes oder Mockups Spionen vorgezogen.
Schatten In Robolectric verwendete Fälschung.

Beispiel mit einer Fälschung

Angenommen, Sie möchten ein ViewModel, das von einer Schnittstelle namens UserRepository abhängt, in einem Unit-Test prüfen. Der Name des ersten Nutzers soll dabei in einer Benutzeroberfläche angezeigt werden. Sie können einen gefälschten Test-Double erstellen, indem Sie die Benutzeroberfläche implementieren und bekannte Daten zurückgeben.

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

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

Diese gefälschte UserRepository muss nicht von den lokalen und Remote-Datenquellen abhängen, die in der Produktionsversion verwendet werden. Die Datei befindet sich im Test-Quellsatz und wird nicht mit der Produktions-App ausgeliefert.

Eine gefälschte Abhängigkeit kann bekannte Daten zurückgeben, ohne auf Remote-Datenquellen angewiesen zu sein.
Abbildung 1: Eine gefälschte Abhängigkeit in einem Unit-Test.

Im folgenden Test wird überprüft, ob das ViewModel den ersten Nutzernamen korrekt der Ansicht zur Verfügung stellt.

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

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

Das Ersetzen der UserRepository durch eine Fälschung ist in einem Unit-Test ganz einfach, da das ViewModel vom Tester erstellt wird. Bei größeren Tests kann es jedoch schwierig sein, beliebige Elemente zu ersetzen.

Komponenten ersetzen und Abhängigkeitsaufruf

Wenn Tests keine Kontrolle über die Erstellung der zu testenden Systeme haben, wird der Austausch von Komponenten für Test-Doubles komplizierter. Außerdem muss die Architektur Ihrer Anwendung einem testbaren Design folgen.

Auch große End-to-End-Tests können von Testdoppeln profitieren, z. B. ein instrumentierter UI-Test, der einen vollständigen Nutzerfluss in Ihrer App durchläuft. In diesem Fall sollten Sie Ihren Test hermetisch gestalten. Bei einem hermetischen Test werden alle externen Abhängigkeiten vermieden, z. B. das Abrufen von Daten aus dem Internet. Dies verbessert die Zuverlässigkeit und Leistung.

Abbildung 2: Ein umfangreicher Test, der den Großteil der App abdeckt und Remote-Daten simuliert.

Sie können Ihre App so entwerfen, dass diese Flexibilität manuell erreicht wird. Wir empfehlen jedoch, ein Dependency Injection-Framework wie Hilt zu verwenden, um Komponenten in Ihrer App zum Testzeitpunkt zu ersetzen. Weitere Informationen finden Sie im Hilt-Testleitfaden.

Nächste Schritte

Auf der Seite Teststrategien erfahren Sie, wie Sie mit verschiedenen Arten von Tests Ihre Produktivität steigern können.