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);
}
}