infinite state machine

Unit Tests für Android

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

Unit Tests für Android

Posted on .

Das Android SDK bietet Unterstützung für Unit- und Instrumentation-Tests. Dazu gehören TestCase-Klassen, die z.B. den Lifecycle von Android-Komponenten steuern und es ermöglichen Abhängigkeiten von anderen System-Komponenten zu ersetzen durch andere Objekte, z.B. Mock-Implementierungen.

Standardmäßig laufen Tests in einer Android-Laufzeitumgebung. Das bedeutet, dass entweder ein Emulator oder ein richtiges Testgerät zur Verfügung stehen muss. Das Kompilieren, Übertragen und Installieren der Tests auf das Gerät oder den Emulator dauert entsprechend lange. Die Tests werden vom InstrumentationTestRunner gestartet, um über die Instrumentation-API die App-Komponenten zu starten, die sonst vom Android System gesteuert würden. Die Tests laufen standardmäßig mit JUnit 3.

Systemobjekte ersetzen

Es existieren verschiedene Testversionen von Android-Klassen. Vor allem für das Context-Objekt existieren verschiedene Implementierungen, die das Schreiben von Unit-Tests erleichtern:

IsolatedContext ist ein eingeschränkter Context, der keinen Zugriff auf Systemdienste oder  Broadcast-Intents zulässt.

RenamingDelegatingContext ist ein Context, der alle Datenbank- und Dateizugriffe mit einem Text-Prefix versieht und alle anderen Methodenaufrufe an einen anderen Context weiterleitet.

ContextWrapper ist ein Context, der alle Methodenaufrufe an einen anderen Kontext weiterleitet, es sei denn, die entsprechende Methode wurde überschrieben.

MockContext ist ein Kontext-Objekt ohne Funktionalität. Seine Methoden können überschrieben werden, um andere Objekte oder Mock-Implementierungen in das System zu bringen, dass gerade getestet wird.

Neben dem MockContext existieren noch weitere Mock-Versionen von Android Klassen: MockApplication, MockContentProvider, MockContentResolver, MockCursor, MockDialogInterface, MockPackageManager und MockResources.

Testcase Klassen für Android Komponenten

ActivityUnitTestCase

Die Klasse ActivityUnitTestCase kann ein isoliertes Activity-Objekt starten (und nach Ende des Tests wieder beenden) mit der Methode startActivity(). Sie stellt eine Parent-Activity, die eine Mock-Implementierung ist. Außerdem sorgt sie für ein Context- und Application-Objekt und ermöglicht das Context- und Application-Objekt für den Test durch Test-Implemtierungen, z.B. Mock-Objekte, zu ersetzten mit den Methoden setActivityContext() und setApplication().

public class MainActivityTest extends ActivityUnitTestCase {

    public MainActivityTest(Class activityClass) {
        super(activityClass);
    }

    public void setUp() throws Exception {
        super.setUp();
        Context context = getInstrumentation().getContext();
        ContentResolver resolver = new MockContentResolver();
        IsolatedContext mockContext = new IsolatedContext(resolver, context);
        setActivityContext(mockContext);
    }

    public void testActivityExists() throws Exception {
        Intent intent = new Intent(getInstrumentation().getTargetContext(), MainActivity.class);
        MainActivity sut = startActivity(intent, null, null);
        assertNotNull(sut);
    }
}

ApplicationTestCase

Diese Test-Klasse ist in der Lage eine Application-Klasse zu testen, in dem sie sie starten kann über die Methoden createApplication() und getApplication().

public class ApplicationTest extends ApplicationTestCase {

    public ApplicationTest() {
        super(MyApplication.class);
    }

    @Override
    protected void setUp() throws Exception {
        super.setUp();
        ContextWrapper context = new ContextWrapper(this.getSystemContext()) {
            @Override
            public Resources getResources() {
                return new MockResources();
            }
        };
        this.setContext(context);
    }

    public void testApplicationExists() throws Exception {
        this.createApplication();
        MyApplication sut = getApplication();
        assertNotNull(sut);
    }
}

AndroidTestCase

Diese Klasse bildet die Elternklasse für alle anderen Test-Klassen für Android, die hier aufgeführt werden. Sie liefert ein Context-Objekt mit getContext() und erlaubt es, dieses auch mit einer Test-Implementierung mit setContext() zu ersetzen. Diese Klasse kann dazu benutzt werden, jede Klasse zu testen, die eine Context-Instanz benötigt.

public class MyAdapterTest extends AndroidTestCase {

    public void setUp() throws Exception {
        super.setUp();
        new MyAdapter(this.getContext(),0);
    }
}

ServiceTestCase

Diese Test-Klasse kann eine Service-Klasse starten mit bindService() oder startService(), je nach Typ des Services, der getestet wird. Außerdem lassen sich wieder Application- und Context-Instanzen für die Testumgebung austauschen. Der getestete Service wird am Ende eines Tests automatisch wieder beendet.

public class MyServiceTest extends ServiceTestCase {

    public MyServiceTest(Class serviceClass) {
        super(serviceClass);
    }

    @Override
    public void setUp() throws Exception {
        super.setUp();
        setApplication(new MockApplication());
        Context context = getContext();
        String prefix = "test_";
        setContext(new RenamingDelegatingContext(context, prefix));
    }

    public void testServicesIsNotNull() throws Exception {
        Intent intent = new Intent(getContext(), MyService.class);
        startService(intent);
        assertNotNull("Service was NULL", this.getService());
    }
}

LoaderTestCase

Mit der LoaderTestCase Klasse lässt sich das Lade-Verhalten einer Loader-Klasse synchron testen mit der Methode getLoaderResultSynchronously(), die so lange blockiert, bis ein Ergebnis zur Verfügung steht. Da die Testklasse auch von AndroidTestCase erbt, stellt sie auch ein Context-Objekt zur Verfügung und erlaubt es, den Context auszutauschen.

public class MyLoaderTest extends LoaderTestCase {

    @Override
    protected void setUp() throws Exception {
        super.setUp();
        ContentResolver contentResolver = getContext().getContentResolver();
        IsolatedContext testContext = new IsolatedContext(contentResolver, getContext());
        this.setContext(testContext);
    }

    public void testLoaderDoesLoadStuff() throws Exception {
        MyLoader sut = new MyLoader(this.getContext());
        String result = getLoaderResultSynchronously(sut);
        assertNotNull("Loader did not return a String", result);
    }
}
profile

Franziska Neumeister

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

Navigation