Teststrategien

Automatisierte Tests können die App-Qualität auf verschiedene Weise verbessern. So können Sie beispielsweise Validierungen durchführen, Regressionen erkennen und die Kompatibilität prüfen. Mit einer guten Teststrategie können Sie automatisierte Tests nutzen und sich auf einen wichtigen Vorteil konzentrieren: die Produktivität von Entwicklern.

Teams erzielen eine höhere Produktivität, wenn sie einen systematischen Ansatz für Tests in Verbindung mit Infrastrukturverbesserungen verwenden. So erhalten Sie zeitnah Feedback zum Verhalten des Codes. Eine gute Teststrategie umfasst Folgendes:

  • Probleme werden so früh wie möglich erkannt.
  • Wird schnell ausgeführt.
  • Sie erhalten klare Hinweise, wenn etwas behoben werden muss.

Auf dieser Seite erfahren Sie, welche Arten von Tests Sie implementieren sollten, wo Sie sie ausführen und wie oft.

Die Testpyramide

Sie können Tests in modernen Anwendungen nach Größe kategorisieren. Kleine Tests konzentrieren sich nur auf einen kleinen Teil des Codes und sind daher schnell und zuverlässig. Große Tests haben einen breiten Umfang und erfordern komplexere Setups, die schwer zu verwalten sind. Bei großen Tests ist die Genauigkeit jedoch höher und es können viel mehr Probleme auf einmal erkannt werden.

*Fidelity bezieht sich auf die Ähnlichkeit der Testlaufzeitumgebung mit der Produktionsumgebung.

Die Verteilung der Anzahl der Tests nach Umfang wird in der Regel in einer Pyramide dargestellt.
Abbildung 1. Die Verteilung der Anzahl der Tests nach Umfang wird in der Regel in einer Pyramide dargestellt.

Die meisten Apps sollten viele kleine und relativ wenige große Tests haben. Die Verteilung der Tests in den einzelnen Kategorien sollte eine Pyramide bilden, wobei die zahlreichen kleinen Tests die Basis und die weniger zahlreichen großen Tests die Spitze bilden.

Kosten eines Fehlers minimieren

Eine gute Teststrategie maximiert die Produktivität von Entwicklern und minimiert gleichzeitig die Kosten für das Auffinden von Fehlern.

Hier ein Beispiel für eine möglicherweise ineffiziente Strategie: Hier ist die Anzahl der Tests nach Größe nicht in einer Pyramide organisiert. Es gibt zu viele große End-to-End-Tests und zu wenige UI-Komponententests:

Eine strategie, bei der viele Tests manuell durchgeführt werden und die Gerätetests nur nachts ausgeführt werden.
Abbildung 2: Eine strategie, bei der viele Tests manuell durchgeführt werden und die Gerätetests nur nachts ausgeführt werden.

Das bedeutet, dass vor dem Zusammenführen zu wenige Tests ausgeführt werden. Wenn ein Fehler vorliegt, wird er möglicherweise erst bei den nächtlichen oder wöchentlichen End-to-End-Tests erkannt.

Es ist wichtig, die Auswirkungen auf die Kosten für das Erkennen und Beheben von Fehlern zu berücksichtigen und die Testbemühungen auf kleinere und häufigere Tests auszurichten:

  • Wenn der Fehler durch einen Unit-Test erkannt wird, ist er in der Regel innerhalb von Minuten behoben. Die Kosten sind also gering.
  • Bei einem End-to-End-Test kann es Tage dauern, bis derselbe Fehler gefunden wird. Dazu sind möglicherweise mehrere Teammitglieder erforderlich, was die Gesamtproduktivität verringert und möglicherweise zu einer Verzögerung der Veröffentlichung führt. Die Kosten dieses Fehlers sind höher.

Eine ineffiziente Teststrategie ist jedoch immer noch besser als gar keine. Wenn ein Fehler in die Produktion gelangt, dauert es lange, bis die Korrektur auf den Geräten der Nutzer landet, manchmal Wochen. Der Feedback-Zyklus ist also am längsten und teuersten.

Eine skalierbare Teststrategie

Die Testpyramide wurde traditionell in drei Kategorien unterteilt:

  • Einheitentests
  • Integrationstests
  • End-to-End-Tests.

Da diese Konzepte jedoch keine genauen Definitionen haben, können Teams ihre Kategorien unterschiedlich definieren, z. B. mit fünf Ebenen:

Eine fünfschichtige Testpyramide mit den Kategorien „Unit-Tests“, „Komponententests“, „Funktionstests“, „Anwendungstests“ und „Releasekandidatentests“ in aufsteigender Reihenfolge.
Abbildung 3: Eine 5-schichtige Testhierarchie.
  • Ein Unittest wird auf dem Hostcomputer ausgeführt und prüft eine einzelne funktionale Logikeinheit ohne Abhängigkeiten vom Android-Framework.
    • Beispiel: Überprüfen von Off-by-One-Fehlern in einer mathematischen Funktion.
  • Bei einem Komponententest wird die Funktionalität oder das Erscheinungsbild eines Moduls oder einer Komponente unabhängig von anderen Komponenten im System überprüft. Im Gegensatz zu Unittests erstreckt sich der Oberflächenbereich eines Komponententests auf höhere Abstraktionen über einzelnen Methoden und Klassen.
  • Bei einem Funktionstest wird die Interaktion von zwei oder mehr unabhängigen Komponenten oder Modulen überprüft. Feature-Tests sind umfangreicher und komplexer und werden in der Regel auf Feature-Ebene ausgeführt.
  • Bei einem Anwendungstest wird die Funktionalität der gesamten Anwendung in Form eines bereitstellbaren Binärprogramms geprüft. Es handelt sich um große Integrationstests, bei denen ein debuggbares Binärprogramm, z. B. ein Entwickler-Build, der Testhooks enthalten kann, als zu testendes System verwendet wird.
    • Beispiel: UI-Verhaltenstest zur Überprüfung von Konfigurationsänderungen auf einem faltbaren Gerät, Lokalisierungs- und Bedienungshilfentests
  • Bei einem Releasekandidaten wird die Funktionalität eines Release-Builds getestet. Sie ähneln Anwendungstests, mit dem Unterschied, dass die Anwendungsbinärdatei minifiziert und optimiert ist. Dabei handelt es sich um umfangreiche End-to-End-Integrationstests, die in einer Umgebung ausgeführt werden, die der Produktionsumgebung so nahe wie möglich kommt, ohne die App öffentlichen Nutzerkonten oder öffentlichen Back-Ends auszusetzen.

Bei dieser Kategorisierung werden Genauigkeit, Zeit, Umfang und Isolierungsgrad berücksichtigt. Sie können verschiedene Arten von Tests für mehrere Ebenen haben. Die Anwendungstestebene kann beispielsweise Verhaltens-, Screenshot- und Leistungstests enthalten.

Umfang

Netzwerkzugriff

Ausführung

Build-Typ

Lebenszyklus

Einheit

Einzelne Methode oder Klasse mit minimalen Abhängigkeiten.

Nein

Lokal

Debuggable

Vor dem Zusammenführen

Komponente

Modul- oder Komponentenebene

Mehrere Kurse zusammen

Nein

Lokal
Robolectric
Emulator

Debuggable

Vor dem Zusammenführen

Feature

Funktionsebene

Integration mit Komponenten, die anderen Teams gehören

Mocked

Lokal
Robolectric
Emulator
Geräte

Debuggable

Vor dem Zusammenführen

Anwendung

Anwendungsebene

Integration mit Funktionen und/oder Diensten, die anderen Teams gehören

Simuliert
Staging-Server
Produktionsserver

Emulator
Geräte

Debuggable

Vor der Zusammenführung
Nach der Zusammenführung

Releasekandidat

Anwendungsebene

Integration mit Funktionen und/oder Diensten, die anderen Teams gehören

Produktionsserver

Emulator
Geräte

Minimierter Release-Build

Nach der Zusammenführung
Vor dem Release

Testkategorie auswählen

Als Faustregel gilt, dass Sie die unterste Ebene der Pyramide berücksichtigen sollten, die dem Team das richtige Feedback geben kann.

Überlegen Sie sich beispielsweise, wie Sie die Implementierung dieser Funktion testen können: die Benutzeroberfläche eines Anmeldevorgangs. Je nachdem, was Sie testen möchten, wählen Sie unterschiedliche Kategorien aus:

Zu testendes Subjekt

Beschreibung des Tests

Testkategorie

Beispiel für einen Testtyp

Logik des Formularvalidators

Eine Klasse, die die E‑Mail-Adresse anhand eines regulären Ausdrucks validiert und prüft, ob das Passwortfeld ausgefüllt wurde. Es hat keine Abhängigkeiten.

Einheitentests

Lokaler JVM-Unittest

Verhalten der Benutzeroberfläche des Anmeldeformulars

Ein Formular mit einem Button, der nur aktiviert wird, wenn das Formular validiert wurde

Komponententests

UI-Verhaltenstest, der mit Robolectric ausgeführt wird

Darstellung der Benutzeroberfläche des Anmeldeformulars

Ein Formular, das einer UX-Spezifikation entspricht

Komponententests

Screenshot-Test für Compose-Vorschau

Integration in den Auth-Manager

Die Benutzeroberfläche, über die Anmeldedaten an einen Autorisierungsmanager gesendet und Antworten empfangen werden, die verschiedene Fehler enthalten können.

Funktionstests

JVM-Test mit Fakes

Dialogfeld zur Anmeldung

Ein Bildschirm mit dem Anmeldeformular, wenn die Schaltfläche „Anmelden“ gedrückt wird.

Anwendungstests

UI-Verhaltenstest, der mit Robolectric ausgeführt wird

Critical User Journey: Anmeldung

Vollständiger Anmeldevorgang mit einem Testkonto auf einem Staging-Server

Releasekandidat

End-to-End-Compose-UI-Verhaltenstest, der auf dem Gerät ausgeführt wird

In einigen Fällen kann es subjektiv sein, ob etwas zu einer bestimmten Kategorie gehört. Es kann zusätzliche Gründe dafür geben, dass ein Test nach oben oder unten verschoben wird, z. B. Infrastrukturkosten, Instabilität und lange Testzeiten.

Die Testkategorie gibt nicht den Testtyp vor und nicht alle Funktionen müssen in jeder Kategorie getestet werden.

Manuelle Tests können ebenfalls Teil Ihrer Teststrategie sein. Normalerweise führen QA-Teams Release-Kandidatentests durch, sie können aber auch in anderen Phasen beteiligt sein. Beispiel: Exploratives Testen einer Funktion auf Fehler ohne Script.

Testinfrastruktur

Eine Teststrategie muss durch Infrastruktur und Tools unterstützt werden, damit Entwickler ihre Tests kontinuierlich ausführen und Regeln durchsetzen können, die dafür sorgen, dass alle Tests bestanden werden.

Sie können Tests nach Umfang kategorisieren, um festzulegen, wann und wo welche Tests ausgeführt werden sollen. Beispiel für das 5-Schicht-Modell:

Kategorie

Umgebung (wo)

Trigger (wenn)

Einheit

[Lokal][4]

Jedes Commit

Komponente

Lokal

Jedes Commit

Feature

Lokal und Emulatoren

Vor dem Zusammenführen, bevor eine Änderung zusammengeführt oder eingereicht wird

Anwendung

Lokal, Emulatoren, 1 Smartphone, 1 faltbares Smartphone

Nach dem Zusammenführen oder Einreichen einer Änderung

Releasekandidat

8 verschiedene Smartphones, 1 faltbares Smartphone, 1 Tablet

Vor Veröffentlichung

  • Unit- und Komponententests werden für jeden neuen Commit im Continuous Integration-System ausgeführt, aber nur für die betroffenen Module.
  • Alle Unit-, Komponenten- und Funktionstests werden vor dem Zusammenführen oder Einreichen einer Änderung ausgeführt.
  • Anwendungstests werden nach dem Zusammenführen ausgeführt.
  • Release Candidate-Tests werden jede Nacht auf einem Smartphone, einem faltbaren Gerät und einem Tablet ausgeführt.
  • Vor einem Release werden Release Candidate-Tests auf einer großen Anzahl von Geräten ausgeführt.

Diese Regeln können sich im Laufe der Zeit ändern, wenn sich die Anzahl der Tests auf die Produktivität auswirkt. Wenn Sie beispielsweise Tests auf einen nächtlichen Rhythmus umstellen, können Sie die Build- und Testzeiten für die kontinuierliche Integration verkürzen, aber auch den Feedbackzyklus verlängern.