(Echte) Unit Tests für Android

Unit-Tests sollen die kleinsten Softwarebausteine überprüfen. Sie sind schnell angewandt und liefern ein direktes Feedback. Robolectric hilft bei der Umsetzung unter Android.

Werkzeuge  –  3 Kommentare
(Echte) Unit Tests für Android

Das Android SDK enthält die Testing Support Library, die aber bei der Umsetzung echter Unit-Tests nicht hilft, weil das Ausführen von Testläufen ist nicht ohne weiteres möglich ist. Zwar können Entwickler Java-Code ohne Abhängigkeiten zum Android-SDK mit JUnit innerhalb der Java Virtual Machine (JVM) testen, aber sie müssen Android-spezifische Tests immer auf einem Emulator oder einem Gerät durchführen.

Der TDD-Ansatz (Test-Driven Development) sieht folgendermaßen aus:

  • Schreiben der Tests,
  • Durchführen der Tests,
  • Tests schlagen fehl,
  • Implementierung der Programmlogik,
  • Erneuter Testlauf,
  • Die Tests sind erfolgreich.

Da die Tests auf einem Gerät oder Emulator erfolgen müssen, wobei schon die Datenübertragung langwierig ist, ist das Vorgehen zeitintensiv. Nutzer verbringen bei einem kleinen Funktionstest lange Zeit damit auf die Testausführung zu warten, statt ihren eigentlichen Job erledigen zu können.

Gut für Integrationstests ist die Testing Support Library geeignet, um beispielsweise langlaufende Operationen wie REST-Aufrufe zu verifizieren. Beim schnellen Test einzelner Funktionen stößt die Bibliothek jedoch an ihre Grenzen.

An dem Punkt kommt Robolectric ins Spiel, eine Open-Source-Bibliothek, die Android-Code innerhalb der JVM ohne Android-Umgebung testen kann. Abhängigkeiten erfolgen als "Mock"-Aufrufe: Das Tool ersetzt die Android-Laufzeitumgebung durch sinnvolle Funktionsaufrufe und Rückgabewerte gegen die Android-API.

Bei Robolectric heißen die Aufrufe Shadows. Sie implementieren die Android-Funktionen bestmöglich in normalem Java. Die Dokumentation zu Robolectric ist jedoch recht spartanisch. Daher erläutert dieser Artikel die Anwendung des Tools innerhalb einer App unter Nutzung von Android Studio.

Gradle hat sich als das Build-Management-Tool für Android etabliert. Dementsprechend müssen Entwickler darin die ersten Weichen stellen, um Robolectric einbinden zu können. Dazu fügen sie in der App die Robolectric-Abhängigkeit in das Modul Gradle File (nicht Gradle Settings File) ein:

Die Dependency-Sektion von build.gradle unterhalb des eigentlichen App-Moduls benötigt die Robolectric-Abhängigkeit(Abb. 1).
dependencies {
testCompile 'org.robolectric:robolectric:3.0'
}

Die Robolectric-Bibliothek sollte die Schlüsselwörter testCompile und nicht androidTestCompile erhalten. Ansonsten versucht Gradle (und damit Android Studio), sämtliche Robolectric-Tests auf einem Emulator oder Gerät auszuführen, was fehlschlagen würde.

Seit Version 2.0 legt Android Studio die Projektstruktur für Tests beim Erstellen eines neuen Projekts erfreulicherweise automatisch an und hebt sie farblich hervor. In bereits existierenden Apps müssen Entwickler gegebenenfalls folgende Ordner separat anlegen:

  • ./src/androidTest/java
  • ./src/test/java
So sollte die Testordnerstruktur ausschauen (Abb. 2).
Anlegen eines JUnit-4-Tests aus einer Klasse heraus (Abb. 3)

Nach der erfolgreichen Einbindung der Robolectric-Bibliothek per Gradle und der Konfiguration von Android Studio lässt sich der erste Test anlegen. Dazu öffnen Entwickler eine beliebige Klasse in Android Studio und verwenden standardmäßig das Tastaturkürzel Strg + Shift + T .

Als Testbibliothek kommt JUnit 4 zum Einsatz. Der Testordner sollte den Namen ./src/test haben. Durch die Verwendung des oben genannten Tastenkürzels sollte Android Studio automatisch eine entsprechende JUnit-Run-Konfiguration angelegt haben, wie Abbildung 4 zeigt.

Vollständige JUnit-Robolectric-Run-Konfiguration (Abb. 4)

Neben dem angegebenen Testklassennamen mit entsprechendem Paketnamen sollte ein besonderes Augenmerk auf folgenden Parametern liegen:

  • VM options: -ea aktiviert Assertions innerhalb der JVM zur Testausführung.
  • Working directory: Der Ordner, in dem sich die AndroidManifest.xml befindet. Statt ./src/main/AndroidManidfest.xml genügtin den meisten Fällen die Umgebungsvariable $MODULE_DIR$.
  • Use classpath of mod...: Der Pfad zum App-Modul innerhalb des Android-Projekts – beispielsweise <Datei-Pfad>/MyApplication/app-module/

Eine Testklasse sollte mit Robolectric folgendermaßen aussehen:

package com.example.sn.myapplication;
import android.app.Activity;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.Robolectric;
import org.robolectric.RobolectricGradleTestRunner;
import org.robolectric.annotation.Config;

import static org.junit.Assert.assertEquals;

@RunWith(RobolectricGradleTestRunner.class)
@Config(constants = BuildConfig.class, sdk = 21)
public class MainActivityTest {

private Activity cut;

@Before
public void setUp() throws Exception {
cut = Robolectric.setupActivity(MainActivity.class);
}

@Test
public void assertActivityTitle() throws Exception {
String expectedTitle = cut.getString(R.string.app_name);
assertEquals("Activity title did not match",
expectedTitle,
cut.getTitle().toString());
}
}

Der Befehl @RunWith(RobolectricGradleTestRunner) legt den Runner fest. Sollte Maven statt Gradle zum Einsatz kommen, heißt die passende Klasse RobolectricTestRunner. Alternativ lässt sich eine eigene Testklasse von der Basisklasse Runner ableiten.

Im Befehl @Config(constants, sdk) ist der Parameter constants erforderlich, um die von Android generierte BuildConfig.class anzugeben, damit der Test auf Ressourcen wie String-IDs aus den Assets der App zugreifen kann.

Wer eine Klasse auf SDK-Kompatibilität überprüft, kann das SDK-Level manuell setzen. Robolectric unterstützt mit Version 3.1 Android 6 (Marshmallow). Eine Gradle-Run-Konfiguration ermöglicht den Start aller Tests auf einen Schlag.

Die angezeigten Skriptparameter in der Gradle-Run-Konfiguration legen fest, dass bereits ausgeführter, unveränderter Code erneut getestet wird (Abb. 5).

Der Befehl zum manuellen Start von Tests sieht folgendermaßen aus:

$ gradlew cleanTest test