Android Coden
Android 4 min lesen

Lokale Unit-Tests

Lokale Unit-Tests prüfen Kotlin-Logik direkt auf der JVM, ohne Android-Gerät. Sie liefern schnelles Feedback und sichern Architektur-Code zuverlässig ab.

Wer Android-Apps professionell entwickelt, kennt das Problem: Ein kleiner Logikfehler in einer Berechnung oder einem Repository-Aufruf lässt sich erst nach dem Starten des Emulators und mehreren Klicks nachvollziehen. Lokale Unit-Tests lösen dieses Problem, indem sie genau diese Logik isoliert und ohne Android-Infrastruktur prüfen – direkt auf der Java Virtual Machine, in Sekundenbruchteilen.

Was ist das?

Lokale Unit-Tests sind Tests, die auf der JVM deines Entwicklungsrechners ausgeführt werden, nicht auf einem Android-Gerät oder Emulator. In einem Android-Projekt leben sie im Ordner src/test/java/ (bzw. src/test/kotlin/), getrennt vom Ordner src/androidTest/, der für Instrumentierungstests reserviert ist.

Diese Tests prüfen ausschließlich puren Kotlin- oder Java-Code: Berechnungen, Zustandsmaschinen, Mapper-Funktionen, ViewModel-Logik, Repository-Interfaces und andere Architekturschichten, die keine direkte Abhängigkeit auf das Android-Framework haben. Da kein Context, kein Activity-Lebenszyklus und keine Android-spezifischen APIs gebraucht werden, startet die JVM die Tests in der Regel in weniger als einer Sekunde – selbst bei hunderten von Testfällen.

Im Rahmen der offiziellen Android-Testpyramide bilden lokale Unit-Tests die breite Basis: Sie sind zahlreich, schnell und günstig in der Ausführung. Integrations- und End-to-End-Tests kommen seltener vor und kosten mehr Zeit. Eine gesunde Test-Suite besteht überwiegend aus Unit-Tests, weil sie den höchsten Deckungsgrad bei geringstem Aufwand liefern.

Wie funktioniert es?

Android-Projekte nutzen standardmäßig JUnit 4 als Test-Framework; JUnit 5 ist über eine zusätzliche Abhängigkeit ebenfalls möglich. Die minimale Gradle-Konfiguration sieht so aus:

// build.gradle.kts (app)
dependencies {
    testImplementation("junit:junit:4.13.2")
    testImplementation("org.mockito.kotlin:mockito-kotlin:5.2.1")
}

Ein Test ist eine gewöhnliche Kotlin-Klasse, in der jede Testmethode mit @Test annotiert ist. JUnit führt sie automatisch aus, wenn du in Android Studio auf „Run Tests” klickst oder ./gradlew test im Terminal ausführst:

import org.junit.Assert.assertEquals
import org.junit.Test

class PreisRechnerTest {

    private val rechner = PreisRechner()

    @Test
    fun `brutto berechnet korrekt mit Mehrwertsteuer`() {
        val netto = 100.0
        val erwarteterBrutto = 119.0

        val ergebnis = rechner.berechneBrutto(netto, mwstSatz = 0.19)

        assertEquals(erwarteterBrutto, ergebnis, 0.001)
    }
}

Der Aufbau folgt dem Arrange–Act–Assert-Muster (AAA): Zuerst werden Testdaten bereitgestellt, dann die zu testende Funktion aufgerufen, schließlich wird das Ergebnis geprüft. Dieses Muster macht Tests lesbar und wartbar, weil jede Testmethode genau eine Verantwortung hat.

Für Klassen, die Abhängigkeiten haben – zum Beispiel ein ViewModel mit einem Repository – greifst du auf Mocking zurück. Mockito-Kotlin erlaubt es, Interfaces durch Fake-Implementierungen zu ersetzen, ohne echte Netzwerk- oder Datenbankaufrufe auszulösen:

import org.junit.Before
import org.junit.Test
import org.mockito.kotlin.mock
import org.mockito.kotlin.whenever
import org.junit.Assert.assertEquals

class ProduktViewModelTest {

    private val repository: ProduktRepository = mock()
    private lateinit var viewModel: ProduktViewModel

    @Before
    fun setup() {
        viewModel = ProduktViewModel(repository)
    }

    @Test
    fun `ladeProdukte gibt Liste zurueck wenn Repository Daten liefert`() {
        val produkte = listOf(Produkt(id = 1, name = "Kotlin-Buch"))
        whenever(repository.alleProdukte()).thenReturn(produkte)

        viewModel.ladeProdukte()

        assertEquals(produkte, viewModel.produkte.value)
    }
}

Die @Before-Methode richtet den Testzustand vor jedem einzelnen Testfall neu ein, sodass Tests voneinander unabhängig bleiben. Unabhängigkeit ist eine Grundvoraussetzung: Ein Test darf nicht auf dem Ergebnis eines anderen aufbauen.

In der Praxis

Lokale Unit-Tests entfalten ihren vollen Wert, wenn du Architekturschichten sauber trennst. Ein ViewModel, das direkt auf SharedPreferences oder Room zugreift, lässt sich auf der JVM nicht testen, weil diese Klassen Android-Framework-Code enthalten. Stattdessen injizierst du das Repository als Interface – dann kannst du es im Test durch ein Mock ersetzen.

Typische Stolperfalle: Android-Klassen im Unit-Test

Der häufigste Anfängerfehler ist der Zugriff auf Android-Klassen wie Log, Context oder Uri in einer Klasse, die eigentlich pure Logik enthält. Sobald du zum Beispiel Log.d(...) direkt in deinem ViewModel aufrufst, schlägt der lokale Unit-Test mit einer RuntimeException: Method ... not mocked fehl.

Lösung: Abstrahiere Android-Abhängigkeiten hinter Interfaces oder verwende eine Logging-Abstraktion. Für Log bietet sich ein einfaches Logger-Interface an, das im Test durch eine leere No-op-Implementierung ersetzt wird. Alternativ kannst du robolectric als Abhängigkeit hinzufügen, das viele Android-APIs für die JVM simuliert – allerdings auf Kosten der Testgeschwindigkeit, weshalb diese Option nur dort eingesetzt werden sollte, wo echte Android-Klassen unvermeidbar sind.

Schnelles Feedback im Entwicklungsalltag

Die eigentliche Stärke lokaler Unit-Tests liegt in der Feedback-Schleife: Statt den Emulator zu starten, eine App zu bauen und durch mehrere Screens zu navigieren, führst du einen Test in Millisekunden aus. Android Studio zeigt direkt im Editor an, welche Tests bestehen oder fehlschlagen. Das ermöglicht testgetriebene Entwicklung (TDD), bei der du erst den Test schreibst und anschließend die Implementierung – eine Praxis, die Designentscheidungen früh sichtbar macht, weil schlecht entkoppelter Code sich sofort schwer testbar anfühlt.

Fazit

Lokale Unit-Tests sind das effektivste Werkzeug, um Kotlin-Logik und Architekturschichten schnell und zuverlässig abzusichern. Sie laufen auf der JVM ohne Emulator, folgen dem AAA-Muster und profitieren von sauber getrennten Abhängigkeiten. Öffne jetzt ein bestehendes Projekt und suche eine Klasse mit purer Berechnungs- oder Zustandslogik – schreibe für diese Klasse einen ersten JUnit-Test, führe ihn mit ./gradlew test aus, und beobachte, wie schnell Android Studio dir Feedback gibt. Dieser erste Schritt legt das Fundament für eine nachhaltige Test-Kultur, die dich vor Regressionen schützt und gleichzeitig zwingt, besseren, modularen Code zu schreiben.

Quellen (6)
Redaktion

Geschrieben von

Redaktion

Das Redaktionsteam recherchiert und schreibt Artikel zu aktuellen Themen rund um Tech, Lifestyle und Ratgeber.