Android Coden
Android 5 min lesen

Was du testen solltest

Nicht jeder Codeabschnitt verdient gleich viele Tests. Lerne, wie du nach Risiko, Verhalten und Wert priorisierst.

Tests schreiben kostet Zeit – und keine Codebasis hat unbegrenzte Testkapazität. Die entscheidende Frage ist daher nicht, ob du testest, sondern was du mit welcher Priorität testest. Wer wahllos auf eine hohe Coverage zielt, verschwendet Aufwand auf trivialen Code und lässt die wirklich kritischen Stellen ungesichert. Eine durchdachte Teststrategie richtet sich stattdessen an drei Achsen aus: Risiko, Verhalten und Wert.

Was ist das?

„Was testen?” bedeutet, systematisch zu entscheiden, welche Teile deiner Android-App durch automatisierte Tests abgesichert werden sollen und mit welcher Intensität. Es geht nicht darum, jede Zeile zu covern, sondern darum, die Bereiche zu identifizieren, deren Fehler die größten Konsequenzen hätten.

Im Android-Kontext trifft diese Entscheidung auf drei Ebenen statt: Unit-Tests prüfen einzelne Klassen und Funktionen in einer isolierten JVM-Umgebung. Integrationstests untersuchen das Zusammenspiel mehrerer Komponenten, etwa ein ViewModel mit einem Repository. UI-Tests beobachten tatsächliches Verhalten im Emulator oder auf einem echten Gerät. Das Verhältnis dieser Schichten beschreibt die Testpyramide: viele schnelle Unit-Tests an der Basis, wenige langsame End-to-End-Tests an der Spitze.

Die eigentliche Herausforderung liegt jedoch nicht in der technischen Umsetzung, sondern in der Auswahl: Welche Klassen, welche Pfade, welche Grenzwerte verdienen einen Test? Ohne eine klare Antwort auf diese Frage verpufft der Testaufwand.

Wie funktioniert es?

Die Priorisierung folgt drei Leitfragen, die du dir vor jedem neuen Feature oder vor jedem Code-Review stellen kannst.

Risiko: Was passiert, wenn dieser Code falsch funktioniert? Eine fehlerhafte Berechnung im Bezahlfluss ist kritischer als ein falscher Padding-Wert in einer Listenansicht. Bereiche mit hohem Risiko – Sicherheit, Datenpersistenz, Netzwerkkommunikation, Abrechnungslogik – sollten zuerst und am gründlichsten getestet werden. Ein Fehler dort kann direkt zu Datenverlust, Sicherheitslücken oder finanziellem Schaden führen.

Verhalten: Handelt es sich um reinen Weiterleitungscode, also einfache Getter oder Delegierungen ohne eigene Logik, oder um echte Geschäftslogik mit Zustandsübergängen, Fallunterscheidungen und Seiteneffekten? Komplexe Logik mit vielen möglichen Pfaden profitiert besonders von Tests, weil eine manuelle Prüfung aller Kombinationen schlicht unrealistisch ist. Compose-UIs mit mehreren reaktiven Zuständen und ViewModels mit mehreren when-Verzweigungen fallen eindeutig in diese Kategorie.

Wert: Welche Funktionen sind aus Nutzersicht unverzichtbar? Die Kernpfade deiner App – Login, Checkout, das Erstellen eines Eintrags, die Synchronisierung mit dem Backend – sollten mindestens durch einen Integrations- oder UI-Test abgedeckt sein, selbst wenn der Code intern einfach aussieht. Der Nutzerwert bestimmt die Priorität, nicht die technische Komplexität.

Historische Fragilität bildet eine vierte praktische Kategorie: Jede Stelle, die in der Vergangenheit mehrfach Regressionen verursacht hat, verdient einen gezielten Regressionstest, der den ursprünglichen Fehler reproduziert. Das schützt nicht nur vor Wiederholungen, sondern dokumentiert das Fehlerverhalten für zukünftige Entwicklerinnen und Entwickler.

Die Testpyramide als Gewichtungshilfe

Google empfiehlt für Android-Projekte eine grobe Aufteilung von 70 % Unit-Tests, 20 % Integrationstests und 10 % UI-Tests. Diese Zahlen sind kein starres Gesetz, aber sie spiegeln den Kompromiss zwischen Ausführungsgeschwindigkeit und Realitätsnähe wider. Unit-Tests laufen in Millisekunden auf der JVM; End-to-End-Tests dauern Minuten und sind vom Gerätezustand und vom Betriebssystem abhängig. Eine Verschiebung in Richtung zu vieler UI-Tests macht deine Test-Suite langsam und fragil, ohne die Aussagekraft proportional zu steigern.

In der Praxis

Stell dir vor, du entwickelst ein Feature, das den Kontostand eines Nutzers lädt und verschiedene UI-Zustände zurückgibt. Das ViewModel könnte so aussehen:

class BalanceViewModel(private val repository: BalanceRepository) : ViewModel() {

    private val _uiState = MutableStateFlow<BalanceUiState>(BalanceUiState.Loading)
    val uiState: StateFlow<BalanceUiState> = _uiState.asStateFlow()

    fun loadBalance(userId: String) {
        viewModelScope.launch {
            _uiState.value = when (val result = repository.getBalance(userId)) {
                is Result.Success -> if (result.data >= 0) {
                    BalanceUiState.Positive(result.data)
                } else {
                    BalanceUiState.Negative(result.data)
                }
                is Result.Error -> BalanceUiState.Error(result.exception.message ?: "Unbekannter Fehler")
            }
        }
    }
}

Dieses ViewModel enthält echte Logik: eine Fallunterscheidung zwischen positivem und negativem Saldo sowie einen Fehlerfall. Das sind mindestens drei Pfade, die alle getestet werden sollten. Ein Unit-Test mit einem gefakten Repository kann alle drei Szenarien in Millisekunden abdecken – ohne Emulator, ohne Netzwerk, ohne Boilerplate.

Typische Stolperfalle: Viele Entwicklerinnen und Entwickler testen nur den Erfolgsfall mit einem typischen Eingabewert. Der negative Saldo und der Netzwerkfehler werden erst entdeckt, wenn ein echter Nutzer im Produktivbetrieb darauf stößt. Schreibe von Anfang an Tests für Fehlerpfade und Grenzwerte – genau dort entstehen die härtesten Bugs und die teuersten Hotfixes.

Ein weiteres verbreitetes Muster: Coverage-Berichte werden als alleiniger Qualitätsmaßstab verwendet, ohne auf den Inhalt der Tests zu schauen. Eine Funktion, die in einem Test nur mit einem Standardwert aufgerufen wird, erscheint im Report als „abgedeckt”, obwohl kein einziger Grenzfall geprüft wurde. Coverage misst Codeausführung, nicht Korrektheit. Sie ist ein nützliches Werkzeug zum Aufspüren von ungetesteten Bereichen, aber kein Qualitätszertifikat.

Fazit

Wer testet, was wirklich wichtig ist, schreibt am Ende weniger Tests und schützt dabei mehr. Die drei Leitfragen – Risiko, Verhalten, Wert – geben dir ein praktisches Werkzeug, um in jedem Review und vor jedem neuen Feature zu entscheiden, wo Testaufwand sinnvoll investiert ist. Öffne jetzt die Codebasis, an der du gerade arbeitest, und frage dich: Welche drei Stellen hätten das größte Schadenpotenzial, wenn sie falsch funktionieren? Sind sie abgedeckt? Falls nicht, ist das dein nächster Test.

Quellen (2)
Redaktion

Geschrieben von

Redaktion

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