infinite state machine

Android Tests in der JVM und JUnit 4

Introduction

user

Franziska Neumeister

Hat "Media Systems" studiert, entwickelt mobile Apps und will wissen, ob Androiden auch von elektrischen Schafen träumen


LATEST POSTS

Multithreading mit RxJava 18th April, 2016

Testbaren Code schreiben (Teil 1) 23rd April, 2015

Android

Android Tests in der JVM und JUnit 4

Posted on .

Mit der Version 1.1 von Android Studio und dem Android Gradle Plugin ist es möglich auch Android-Code auf der JVM auszuführen. Vorher war es zwar auch möglich, ein reines Java-Test-Projekt anzulegen und gegen die lokale Android.jar des Android SDK zu linken. Allerdings führte jeder Methodenaufruf im Android SDK zu einer RuntimeException mit der Nachricht „Stub!“, da es sich um eine leere Implementierung handelte.

Viele der Android-Klassen sind als Final markiert und lassen sich daher nicht ohne größeren Aufwand durch Mock-Objekte ersetzten. Mit der Version 1.1 gibt es jetzt eine Version der Android.jar, in der bei allen Klassen die Final Markierung entfernt wurde und alle Klassen durch Mocks ersetzt werden können. Außerdem kann jetzt JUnit 4 als Test-Bibliothek verwendet werden.

Allerdings funktionieren die AndroidTestCase-Klassen nicht auf der JVM. Es fehlt also eine Möglichkeit, Android-Komponenten über die Instrumentation-API zu steuern. Außerdem müssen für alle Android-Klassen, die verwendet werden, Mock-Klassen geschrieben werden.

Abhilfe bringt die Testbibliothek Robolectric, die zur Laufzeit alle Aufrufe von Klassen aus dem Android SDK durch so genante Shadow-Objekte ersetzt. Das sind Implementierungen, die entweder original Android-Code aufrufen oder Mock Code enthalten. Außerdem bieten sie manchmal zusätzliche Methoden, um das Schreiben von Tests zu erleichtern.

Um eine Testumgebung für Unit-Tests auf der JVM in Android Studio einzurichten, sind folgende Schritte notwendig:

  • Das Android Gradle Plugin muss mindestens Version 1.1 sein
    dependencies {
        
        classpath 'com.android.tools.build:gradle:1.1.0'

    }
  • In Android Studio geht man in den Einstellungen zu dem Punkt „Gradle > Experimental“ und schaltet dort „Enable Unit Testing support“ ein.
  • Jetzt findet sich im Fenster „Build variants“ ein Drop-Down Menu namens „Test Artifacts“, bei dem man wählen kann zwischen „Android Instrumentation Tests“ (führt die Tests im Verzeichnis „src/androidTest/java“ auf einem Android Gerät aus) und „Unit Tests“ (führt die Tests im Verzeichnis „src/test/java“ auf einer lokalen JVM aus). Letzteres wählt man aus.build-variant
  • Jetzt kann man eigentlich schon Test-Klassen anlegen, aber um Android Komponenten zu steuern und Klassen aus dem Android SDK zu benutzen, die keine leeren Implementierungen sind, braucht es noch Robolectric. Als erstes fügt man das Roblectric Gradle Plugin als Abhängigkeit zur build.gradle Datei des Projekts hinzu:
    dependencies {
        classpath 'com.android.tools.build:gradle:1.1.0'
        classpath 'org.robolectric:robolectric-gradle-plugin:1.0.1'
    }
  • In der build.gradle Datei des App Moduls wendet man dann das Roblectric Gradle Plugin an:
    apply plugin: 'org.robolectric'
  • Um Apps mit Android Lolipop zu testen benötigt man die 3.0 SNAPSHOT Version von Robolectric. Dafür muss das entsprechende Repository zum build.gradle des Projekts hinzugefügt werden. Die Snapshot-Version   anstelle der 2.4 Release-Version ist notwendig um Unterstützung für die Android API Level 19 und 21 zu erhalten.
    allprojects {
        repositories {
            jcenter()
            maven { url "https://oss.sonatype.org/content/repositories/snapshots" }
        }
    }
  • Schließlich fügt man JUnit 4 und die eigentliche Robolectric Bibliothek zu den Test-Abhängigkeiten in der build.gradle Datei des App Moduls hinzu. Die Build-Konfiguration für UnitTest auf der JVM heißt textCompile im Gegensatz zur Build-Konfiguration der Instrumentation-Tests in der Android-Laufzeitumgebung androidTestCompile
    dependencies {
        // ...
        testCompile 'junit:junit:4.12'
        testCompile "org.robolectric:robolectric:3.0-SNAPSHOT"
    }
  • Robolectric hat Probleme mit Bibliotheken,  die im AAR Format veröffentlicht werden. AAR ist das Distributions-Bundle Format für Android Library Projekte. Robolectric kann Klassen aus diesen Bibliotheken benutzen, aber keine Ressourcen, z.B. Themes oder Styles. Es kommt dann zu dieser Exception java.lang.RuntimeException: Could not find any resource  from reference [...]. Dieser Fehler tritt z.B. auf, wenn man die appcompat-v7 Bibliothek verwendet. In dem Fall muss man manuell Robolectric mitteilen, wo sich die entpackten Ressourcen befinden. Dazu legt man im Verzeichnis „app/src/main“ eine Datei mit dem Namen test-project.properties an und hinterlegt dort den Pfad für die entsprechenden Bibliotheken. Für appcompat-v7 sieht das so aus:
    android.library.reference.1=../../build/intermediates/exploded-aar/com.android.support/appcompat-v7/21.0.3
  • Um einen Test anzulegen öffnet man die zu testende Klasse, klickt rechts auf den Klassen-Namen und wählt „Go to > Test“. Darauf hin wird man gefragt, ob man einen neuen Test anlegen möchte. In dem Fenster, dass sich dann öffnet, wählt man „JUnit 4“ aus und klickt „OK“.
  • Der Testklasse fügt man zwei Annotationen hinzu. Einmal @RunWith, um JUnit zu sagen, dass es den TestRunner von Robolectric benutzen soll und einmal @Config um Robolectric mitzuteilen, wo es das richtige Android Manifest findet und dass es den API Level von Android Lollipop verwenden soll.
    @RunWith(RobolectricTestRunner.class)
    @Config(manifest = "app/src/main/AndroidManifest.xml", emulateSdk = 21)
    public class MainActivityTest {}
  • Jetzt kann man einen Test in Form einer Methode hinzufügen. Jede Test-Methode muss die Annotation @Test haben. So sieht zum Beispiel ein Test aus, der eine Activity mittels Robolectric startet und überprüft, ob dabei erfolgreich eine Instanz erzeugt wird:
    @Test
    
    public void testActivityIsStarting(){

        ActivityController ctl = buildActivity(MainActivity.class);
        MainActivity sut = ctl.create().start().resume().visible().get();

        assertNotNull(sut);
    }
  • Die Tests lassen sich über die den Terminal Befehl ./gradlew test --continue ausführen. Alternativ können die Tests auch über die IDE gestartet werden in dem man eine neue Run Configuration für die Tests zum Projekt hinzufügt. Dazu öffnet man im Menu „Run“ den Punkt „Edit Configurations…“. In dem Fenster klickt man auf das „+“ und wählt in dem Drop-Down Menu den Punkt „JUnit“. Der neuen Konfiguration gibt man einen aussagekräftigen Namen, unter „User classpath for module“ wählt man das Modul der App aus, standardmäßig „app“ genant. Unter dem Punkt „Before launch“ entfernt man den Punkt „make“ und fügt „Gradle-aware make“ hinzu. Der Task-Name kann leer bleiben.
profile

Franziska Neumeister

Hat "Media Systems" studiert, entwickelt mobile Apps und will wissen, ob Androiden auch von elektrischen Schafen träumen

Navigation