8
www.android360.de Ausgabe 4/2011 Österreich € 11,70 Schweiz sFr 17,50 Luxemburg € 11,90 Deutschland € 9,80 Wunderschöne UIs Android und Drawable Ressourcen Android-Fragmentierung Die Vielfalt der Plattform als Chance Unterwegs programmieren Tools für das Coding in Bus und Bahn Warum scheitern mobile Projekte? Die Knackpunkte unter der Lupe magazin Testautomation und Continuous Integration für Android Brandneu: Die neuen Features von Android Ice Cream Sandwich! Tipps Strategien Werkzeuge ANDROID TESTING

Tut er's oder tut er's nicht?

Embed Size (px)

Citation preview

Page 1: Tut er's oder tut er's nicht?

www.facebook.com/mobiletechcon

www.twitter.com/mobiletechcon | #mtc12

www.xing.com/net/mobiletechcon

Präsentiert von: Veranstalter:

www.mobiletechcon.de

26. – 28. März 2012, MünchenExpo: 27. und 28. März 2012

Weitere

Informationen

zur MobileTech

Con 2012 auf

Seite 17– 20.

Very-Early-Bird-Rabatt: Intellibook-Notebook gratis sichern und bis zu 150 € sparen (bis zum 31.01.)!

Android Testing & CI | Fragmentierung | Draw

able Ressourcen | Android-Programm

ierung in Bus und Bahn | Warum

scheitern mobile Projekte?

andro

id360

www.android360.de

Ausgabe 4/2011

Österreich € 11,70 Schweiz sFr 17,50 Luxemburg € 11,90Deutschland € 9,80

Wunderschöne UIsAndroid und Drawable Ressourcen

Android-FragmentierungDie Vielfalt der Plattform als Chance

Unterwegs programmierenTools für das Coding in Bus und Bahn

Warum scheitern mobile Projekte?Die Knackpunkte unter der Lupe

magazin

Testautomation und Continuous Integration für Android

Brandneu: Die neuen Features von Android Ice Cream Sandwich!

TippsStrategien Werkzeuge

ANDROID TESTING

Page 2: Tut er's oder tut er's nicht?

www.android360.de 45

testen mit android | TiTelThema

4 | 2011 android360

von arne Limburg

Bei der Entwicklung einer Teststrategie für mobile Anwendungen muss auf diese Vielfalt der Geräte ein-gegangen werden, die gleichzeitig eine Vielfalt von Bild-schirmaufl ösungen, Eingabemethoden (Touchscreen, Multi-Touch, Trackball, externe Tastatur usw.) und Konfi gurationsmöglichkeiten mit sich bringt. Benötigt zum Beispiel eine Anwendung Positionsdaten, muss nicht nur überprüft werden, ob das Gerät überhaupt in der Lage ist, diese zu liefern, sondern auch, ob die gelieferten Positionsdaten für den entsprechenden An-wendungsfall präzise genug sind. Die Genauigkeit von Positionsdaten kann erheblich variieren, je nachdem ob sie von einem GPS-Empfänger kommen, aus der In-formation über die Mobilfunkzelle, in die das Gerät ge-rade eingeloggt ist, oder aus dem aktuellen IP-Bereich (z. B. über WLAN). Ähnliche Komplexitäten ergeben sich unter anderem bei der Verwendung von Kamera, Kompass- und/oder Lagesensor. Ein weiterer Punkt, der vor allem bei mobilen Anwendungen ins Gewicht fällt, ist die Vielfalt der Umwelteinfl üsse. So müssen der aktuelle Standort (im Wald oder in Häuserschluchten funktioniert GPS schlechter als auf dem freien Feld), der Batteriestand, die Netzqualität und viele weitere Faktoren berücksichtigt werden. Auch das Nutzer-verhalten stellt bei der mobilen Anwendung eine be-sondere Herausforderung dar. So kann es passieren, dass der Benutzer während einer Texteingabe auf die Idee kommt, das Handy vom Hoch- ins Querformat zu drehen und damit eine komplette Neuberechnung des Layouts anzustoßen. Weitere Herausforderungen stellen „plötzlich“ eingehende Anrufe dar oder zum Beispiel die Betätigung des Lautstärkereglers oder des Zurück-Knopfes.

Teststrategien und -werkzeuge für mobile Anwendungen in Android

Tut er’s oder tut er’s nicht? Das Testen stellt im mobilen Umfeld eine ungleich größere Herausforde-rung dar als in anderen Bereichen der Softwareentwicklung. Sie entsteht durch die hohe Vielfalt der Geräte und die damit einhergehenden Variatio-nen der Hard- und Softwarekonfi gurationen und -versionen.

Wahl der richtigen TeststrategieDie oben aufgeführten Probleme der mobilen Anwen-dungsentwicklung stellen auch eine besondere Heraus-forderung an das Testen solcher Anwendungen dar. Es wird niemals möglich sein, alle Kombinationen von Hard- und Software, Umwelteinfl üssen und Benutzer-verhalten zu testen. Das führt dazu, dass beim Testen Kompromisse eingegangen werden müssen. Es gibt meh-rere Testmethoden, mit denen die verschiedenen Berei-che der oben genannten Felder getestet werden können. Diese Testmethoden haben dabei unterschiedliche Vor- und Nachteile. Die Erstellung einer Teststrategie besteht darin, die Vor- und Nachteile der einzelnen Methoden gegeneinander abzuwägen und zu gewichten und somit jeder Testmethode den richtigen Platz und Umfang im Entwicklungs- und Testprozess zu geben. Diese Aus-

Testkontexte

android bietet drei Varianten, um in einem test das Context-objekt zu ersetzen:

mockContext ■ : Keine methode ist implementiert, d. h. alle methoden werfen exceptions. er eignet sich, um von ihm abzuleiten, wenn man einen eigenen testContext schreiben muss und nur einzelne methoden implementieren möchte.isolatedContext ■ : damit läuft ein test isoliert vom Gesamtsystem ab. datei- und datenbankoperationen erfolgen in einem separaten test-bereich, wodurch dieser Context sehr gut geeignet ist, um diese zu testen. die meisten systemaufrufe liefern stub-antworten. einige aufrufe werfen exceptions.renamingdelegatingContext ■ : es delegiert alle systemaufrufe an einen „echten“ Context. datei- und datenbankaufrufe erfolgen wie beim isolatedContext in einem separaten Bereich.

Page 3: Tut er's oder tut er's nicht?

www.android360.de46

TiTelThema | testen mit android

android360 4 | 2011

wahl und Gewichtung ist ein wiederkehrender Prozess, in dem auf Basis der individuellen Anforderungen die jeweils richtige Teststrategie entwickelt wird.

Priorisierung und automatisierungUm in den Tests die Vielfalt der Geräte und Konfigura-tionen annähernd abdecken zu können, gibt es ein paar Punkte, die unbedingt berücksichtigt werden sollten. Es ist nicht möglich, auf allen späteren Endgeräten zu tes-ten. Das ist allein schon durch die Tatsache bedingt, dass bei der Schnelllebigkeit im Handymarkt viele der poten-ziellen Endgeräte zum Zeitpunkt der Anwendungsent-

wicklung noch gar nicht auf dem Markt sind. Aus dieser Tatsache sollten zwei Konsequenzen gezogen werden:

Man sollte so viele Tests wie möglich mit verschie-1. den konfigurierten AVDs (Android Virtual Devices) ausführen. Das ist dank des mitgelieferten Android-Emulators recht einfach.Man sollte beim Testen auf echten Geräten den 2. Fokus auf eine Auswahl wichtiger Zielgeräte und auf Tests legen, die mit dem Emulator nicht oder nicht in ausreichendem Maße durchgeführt werden können, z. B. die Kommunikation über GPRS mit verschiedenen Telekommunikationsanbietern.

Diese Anforderungen lassen sich nur durch ein hohes Maß an Testautomatisierung erfüllen. Das automati-sche Testen ist daher ein Kernbestandteil mobiler Test-strategien.

Unit TestsDie am einfachsten zu automatisierende Testmetho-de ist das Unit Testing. Hierbei handelt es sich um das Testen kleinstmöglicher Einheiten, also in der Regel das Testen der korrekten Funktion einzelner Klassen und Methoden. Der große Vorteil von Unit Tests ist, dass sie aufgrund ihres geringen Umfangs eine kurze Ausfüh-rungsdauer haben und dadurch häufig ausgeführt wer-den können.

Die größte Herausforderung beim Unit Testing ist die Entkopplung der zu testenden Klasse vom Gesamtsys-tem. Diese Herausforderung wird im mobilen Umfeld umso größer, da hier in der Regel die Abhängigkeit zu der Laufzeitumgebung viel höher ist als in anderen Bereichen der Softwareentwicklung. Um diese Entkopplung zu er-reichen, werden alle Abhängigkeiten mit Mock-Objekten (engl. für Attrappe) gefüllt. Das Android-Testframework bringt bereits Mock-Objekte für die üblichen Abhängig-keiten von Android-Anwendungen wie MockApplica­tion, MockContext, MockContentPro vider mit (Kasten: „Testkontexte“). Neben den Mock-Objekten liefert Android eine komplette Integration in JUnit, allerdings nur für Version 3. Hier gibt es Oberklassen zum Testen der Android-Komponenten wie ApplicationTestCase, LoaderTestCase, ProviderTestCase2, ServiceTestCase, ActivityUnitTestCase und ActivityInstrumentationTest­Case2. Die damit erstellten Testklassen werden zusam-men mit der zu testenden Anwendung auf dem Gerät oder dem Emulator installiert und können dann über die Android Debug Bridge (ADB) direkt auf dem Gerät oder Emulator ausgeführt werden. Über adb-Parameter kann auch gesteuert werden, welche Tests ausgeführt werden sollen. So ist neben der Ausführung aller Tests des Projekts auch die Beschränkung auf einzelne Tests oder Testgruppen möglich. Testgruppen können gebil-det werden, indem die Tests mit einer der Annotations @SmallTest, @MediumTest oder @LargeTest annotiert werden. Auch die Definition eigener Annotations ist möglich. Um eine Trennung von Anwendung und Tests

Listing 1

public class MyProviderTest extends ProviderTestCase2<MyProvider> { public static final String NAME = "test";

public MyProviderTest() { super(MyProvider.class, MyProviderMetaData.AUTHORITY); } public void testInsert() { //test ContentValues contentValues = new ContentValues(); contentValues.put(MyProviderMetaData.NAME, NAME); MockContentResolver contentResolver = getMockContentResolver(); Uri uri = contentResolver.insert(MyProvider.CONTENT_URI, contentValues); //verify String[] projection = new String[] {MyProviderMetaData.NAME}; Cursor cursor = getProvider().query(uri, projection, null, null, null); cursor.moveToFirst(); assertEquals(NAME, cursor.getString(0)); assertTrue(cursor.isLast()); }}

Listing 2

public class MyNetworkServiceTest extends ServiceTestCase<MyNetworkService> {

public MyNetworkServiceTest() { super(MyNetworkService.class); }

public void testPostRequest() { MyNetworkService.MyNetworkServiceBinder serviceBinder = (MyNetworkServiceBinder)bindService(new Intent(getContext(), MyNetworkService.class)); MockHttpClient mockHttpClient = new MockHttpClient(); getService().setHttpClient(mockHttpClient);

serviceBinder.postRequest(); assertTrue(mockHttpClient.getRequest() instanceof HttpPost); }}

Page 4: Tut er's oder tut er's nicht?

www.android360.de 47

testen mit android | TiTelThema

4 | 2011 android360

zu erreichen, werden die Tests in ein separates Testpro-jekt ausgelagert. In Eclipse kann es direkt über New | Android Test Project angelegt werden. Im darauf fol-genden Wizard wird dann das zu testende Projekt ange-geben. Wie bereits erwähnt, gibt es für die verschiedenen Android-Komponenten jeweils eine Testoberklasse, von denen die drei wichtigsten hier vorgestellt werden.

Testen eines ContentProvidersZum Testen eines ContentProviders bietet das Android-Testframework die Klasse ProviderTestCase2. An der nachgestellten 2 lässt sich erkennen, dass es sich bereits um die zweite Version dieser Klasse handelt. Android hat das Testframework überarbeitet und einige Testober-klassen ersetzt. Die Klasse ProviderTestCase2 nimmt bei der Testerstellung einige Arbeit ab. So instanziiert sie automatisch den zu testenden ContentProvider und versieht ihn dabei mit einem IsolatedContext (Kasten: „Testkontexte“). Dadurch kann es nicht passieren, dass der Unit Test „aus Versehen“ den Rest des Testsystems beeinflusst. Datei- und vor allem Datenbankoperatio-nen sind mit dem IsolatedContext aber möglich, was wichtig ist, weil das die Operationen sind, die bei einem ContentProvider in der Regel (je nach Implementierung) getestet werden müssen. Zugreifen kann man auf den ContentProvider über einen MockContentResolver, der auch von der Testklasse zur Verfügung gestellt wird und über getMockContentResolver() verwendet werden kann. Die Verwendung dieses MockContentResolvers ist dem direkten Zugriff auf den ContentProvider vor-zuziehen, da später der Zugriff der realen Anwendung auf den ContentProvider auch immer über einen Con­tentResolver erfolgen wird. Zu testen sind natürlich alle zur Verfügung stehenden Operationen des ContentPro­viders. Sie werden über die Menge der URIs definiert, die der ContentProvider akzeptiert. Das Verhalten des ContentProviders bei ungültigen URIs sollte auch getes-tet werden. Listing 1 zeigt exemplarisch das Testen der Insert-Operation eines ContentProviders.

Testen eines ServiceAndroid-Services haben einen klar definierten Lebens-zyklus [2]. Die Oberklasse ServiceTestCase sorgt dafür, dass er während des Tests automatisch durchlaufen wird. Dazu bietet die Oberklasse über die Methoden startService(…) und bindService(…) genau die Schnitt-stelle, mit der später im echten Anwendungscode auch der Service gestartet wird. Das Testen der Businesslogik eines Service ist häufig recht schwierig, weil Services in der Regel asynchron ablaufen. Über den von der Me-thode bindService(…) zurückgegebenen ServiceBinder können zwar im Test Servicemethoden aufgerufen wer-den, wenn die aufgerufene Methode aber einen eigenen Thread startet (was bei lang laufenden Operationen zu empfehlen ist), ist es nicht leicht, im Test festzustellen, dass die Ausführung der Servicemethode beendet ist, so-dass sich dann auch schwer feststellen lässt, ob sie das getan hat, was man erwartet hat.

Listing 2 zeigt den Test eines NetworkServices, der beim Aufruf der Methode postRequest(…) einen Thread startet und asynchron einen HTTP Request absetzt. Beim Testen dieses asynchronen Aufrufs unterstützt uns die Klasse CountDownLatch aus dem java.util.concurrent Package. Über deren Methode countDown() kann der aufgerufene Thread einen Zähler herunterzählen und der Aufrufer kann über die Methode await() warten, bis der Zähler auf 0 ist. Um in unserem Beispiel testen zu können, ob der Aufruf von postRequest(…) tatsächlich zu einem HTTP Request führt, können wir den Http­Client, der von dem NetworkService verwendet wird, durch ein Mock-Objekt (Listing 3) ersetzen. Dieser MockHttpClient verwendet den eben genannten Count­DownLatch, der beim erwarteten Aufruf der Methode execute(…) heruntergezählt wird. Der Testcode kann nun über getRequest() auf den übergebenen Request zugreifen und überprüfen, ob der NetworkService tat-sächlich einen korrekten Request abgesetzt hat. Erfolgt der Aufruf der Methode getRequest() vor dem Aufruf der Methode execute(…), so wartet der MockHttpCli­ent zunächst durch den Aufruf von CountDownLatch.await(…), bis der Aufruf von execute(…) erfolgt ist und den Zähler vermindert hat. Um sicherzugehen, dass der Test nicht ewig wartet (weil der Service nie HttpClient.execute(…) aufruft), wird die Methode await(…) mit ei-nem Timeout versehen.

Testen einer activityDas Testen einer Activity losgelöst vom Gesamtsystem (also als Unit Test) gestaltet sich besonders schwierig, weil Activities automatisch eine Vielzahl von Verbin-

Listing 3

public class MockHttpClient implements HttpClient {

private HttpUriRequest request; private CountDownLatch latch = new CountDownLatch(1);

public HttpUriRequest getRequest() { try { latch.await(10, TimeUnit.SECONDS); } catch (InterruptedException e1) { //nothing to do here } return request; }

public HttpResponse execute(HttpUriRequest request) { this.request = request; latch.countDown(); BasicStatusLine line = new BasicStatusLine(HttpVersion.HTTP_1_1, 200, "OK"); return new BasicHttpResponse(line); } //other mock methods omitted}

Page 5: Tut er's oder tut er's nicht?

www.android360.de48

TiTelThema | testen mit android

android360 4 | 2011

dungen mit der Anwendungsinfrastruktur haben. Die Oberklasse ActivityUnitTestCase kapselt die zu testen-de Activity dennoch recht gut vom Gesamtsystem ab. Das hat aber zur Folge, dass einige Methodenaufrufe der Activity zu Exceptions führen und daher nicht in einem Unit Test getestet werden können. Hierbei han-delt es sich vor allem um Methoden zur Interaktion zwischen verschiedenen Activities wie startActivity­FromChild(…) oder createPendingResult(…). Einige Dinge lassen sich dennoch isoliert testen, zum Beispiel der einfache Aufruf einer Folge-Activity. Der Trick ist

hier, dass die Folge-Activity nicht tatsächlich gestartet wird (dann wäre es kein Unit Test mehr), man aber spä-ter überprüfen kann, ob der Aufruf zum Start (nämlich startActivity(…) oder startActivityForResult(…)) tat-sächlich erfolgt ist. Das ist über die Methoden getStar­tedActivityIntent() und getStartedActivityRequest() möglich, die von der Oberklasse ActivityUnitTest zur Verfügung gestellt werden. Listing 4 zeigt den Unit Test einer ListActivity, die beim Klick auf einen Listenein-trag in eine Detailansicht springt und dabei eine ID in dem Intent übergeben soll. Nach dem Aufruf der Me-thode der Activity wird dann über getStartedActivity­Intent() überprüft, ob die korrekten Werte im Intent tatsächlich enthalten sind.

Funktionale TestsNeben den reinen Unit Tests ist es mit dem Android-Testframework auch möglich, komplette Funktionstests zu erzeugen, die das gesamte UI-Verhalten (vom Klick bis zum Ergebnis der ausgelösten Aktion) oder sogar ganze Klickpfade und damit ganze Use Cases testen. Betrachtet man das obige Beispiel von der ListActivity, wäre es beispielsweise wünschenswert, dass nicht nur die korrekte Funktion der Activity getestet werden könnte, sondern der gesamte Flow vom tatsächlichen Klick auf das UI-Element (in diesem Fall eine Liste) bis zum Start der Detailansicht. Das ist tatsächlich möglich, wenn man als Oberklasse den ActivityInstrumentationTestCase2 verwendet. Die Kommunikation mit der Android-Inf-rastruktur ermöglicht dabei die Klasse Instrumentation, die innerhalb des Tests über getInstrumentation() zur Verfügung steht. Mit ihr ist es möglich, direkt mit dem Emulator oder dem Device in Interaktion zu treten und zum Beispiel Activities zu starten oder Tastatureingaben zu simulieren. Wer Texteingaben (oder andere Attribu-te) lieber auf dem View direkt setzen will, indem er ihn über findViewById(…) holt und den entsprechenden Wert dann über set… setzt, muss das im UI Thread von Android tun. Hierzu gibt es zwei Möglichkeiten: Entwe-der man verwendet die Methode runOnUiThread(…) der Activity oder man versieht den gesamten Test mit der Annotation @UiThreadTest.

Listing 5 zeigt einen Test, der die aus dem Test aus Listing 3 bekannte Listenansicht testet und hierzu die In­strumentation verwendet. Damit wird nicht nur die kor-rekte Funktion der Activity getestet, sondern tatsächlich über die Simulation eines Fingerdrucks auf die Liste, der über die Klasse TouchUtils erzeugt werden kann, der ge-samte Programmfluss bis zum Start der DetailActivity. Über die Instrumentation ist es möglich, einen Monitor für die DetailActivity zu erzeugen und damit auf deren Aufruf zu warten. Eine andere Möglichkeit wäre es, ein sleep(…) in den Test einzubauen. Das beinhaltet aber das Risiko, dass die Anwendung zu langsam reagiert und der Test deshalb fehlschlägt. Um dieses Risiko zu minimieren, gibt es die Annotation @FlakyTest. Hiermit kann für einen Test angegeben werden, dass der Test bei einem einmaligen Fehlschlag wiederholt werden soll,

Listing 4

public class MyListActivityUnitTest extends ActivityUnitTestCase<MyListActivity> {

private static final long ENTRY_ID = 42;

public MyListActivityUnitTest() { super(MyListActivity.class); }

public void testOnListItemClick() { //test Intent listIntent = new Intent(getInstrumentation().getTargetContext(), MyListActivity.class); startActivity(listIntent, null, null); getActivity().onListItemClick(null, null, 0, ENTRY_ID); //verify Intent startedIntent = getStartedActivityIntent(); assertNotNull(startedIntent); assertEquals(MyDetailActivity.class.getName(), startedIntent.getComponent().getClassName()); long entryId = (Long)startedIntent.getSerializableExtra("ENTRY_ID"); assertEquals(ENTRY_ID, entryId); }}

www.jaxenter.de

Das Portal für Java, Enterprise- Architekturen und SOA.

Testen von Seiteneffekten wie „Telefonanruf“ oder „niedriger Batteriestand“

detaillierten Zugriff auf den emulator bietet die ddms-Perspektive von eclipse. Hier hat man die möglichkeit, den eingang eines telefonanrufs oder einer sms am emulator zu simulieren. des Weiteren lässt sich die netzgeschwindigkeit steuern und die GPs-Koordinaten des emulators setzen. Wem die möglichkeiten der Perspektive nicht ausreichen, der kann sich über telnet localhost <Port> mit dem emulator verbinden (den Port des emulators findet man in der titelzeile des emulatorfensters). Hier stehen weitere möglichkeiten der steuerung des emulators zur Verfügung, zum Beispiel das setzen des Batteriestandes. die eingabe von help listet alle Befehle auf. theoretisch könnten über die telnet-schnittstelle des emulators diese seiteneffekte zwar auch automatisiert getestet werden, in der Praxis ist das aber recht kompliziert. es empfiehlt sich daher, diese seiteneffekte manuell auszulösen, während gerade ein automatisierter Funktionstest läuft, um so die reaktion der anwendung auf die seiteneffekte zu testen.

Page 6: Tut er's oder tut er's nicht?

testen mit android | TiTelThema

und erst wenn der Test mehrmals nicht erfolgreich aus-geführt werden kann, gilt er als fehlgeschlagen. Wie oft er wiederholt werden soll, kann dabei durch die Angabe eines Wertes für das tolerance-Attribut in der Annota-tion festgelegt werden. In Listing 5 enthält die Detail­Activity einen TextView. Hier wird zum Schluss über die Klasse ViewAsserts getestet, ob dieser sichtbar ist. Danach wird anhand des Inhalts überprüft, ob die De­tailActivity mit den korrekten Daten aufgerufen wurde.

Eine weitere Vereinfachung von funktionalen Tests bietet die Zusatzbibliothek Robotium [3] (siehe auch Artikel auf Seite 58). Mit ihr ist es möglich, funktionale Tests als Blackboxtests durchzuführen, das heißt dass es nicht nötig ist zu wissen, aus welchen Views eine Acti-vity aufgebaut ist. Mit der Klasse Solo kann direkt der Klick auf eine Koordinate auf dem Bildschirm simuliert werden oder sogar der Klick auf einen bestimmten Text, der auf dem aktuellen Bildschirm zu sehen ist. Listing 6 zeigt ein Beispiel für einen Robotium-Test der bereits bekannten ListActivity.

Continuous integrationBetrachtet man den Funktionstest unserer ListActivity, stellt sich die Frage, wozu überhaupt Unit Tests benö-tigt werden, wenn sich doch die ganze Anwendung über komplette Funktionstests testen lässt. Eine erste schnel-le Antwort kommt sofort in den Sinn, wenn man die

beiden Tests im Vergleich betrachtet: Der Funktionstest ist deutlich komplexer als der Unit Test und ein Unit Test daher offensichtlich deutlich leichter zu schreiben. Eine weitergehende Antwort ist im Entwicklungspro-zess begründet. Das Ziel von Unit Tests ist es zum einen, durch Testen kleiner Einheiten einen möglichen Fehler schnell eingrenzen zu können, und zum anderen durch häufige Testausführung einen Fehler im Laufe des Ent-wicklungsprozesses früh zu erkennen. Diese frühe Er-kennung von Fehlern gewährleistet der Einsatz eines

Activity Unit Test vs. Activity-Funktionstest

in einem Unit test wird nur die Funktionalität der activity selbst getes-tet, das heißt es wird getestet, was passiert, wenn die entsprechende methode (in Listing 4 die methode onListitemClick(…)) aufgerufen wird. auch wird die nachfolgende activity nicht tatsächlich gestartet, sondern der aufruf von startactivity(…) wird nur aufgenommen und es kann spä-ter über getstartedactivityintent() überprüft werden, ob er tatsächlich stattgefunden hat. durch auslesen der extra-informationen aus dem intent wird überprüft, ob die activity den intent auch mit den richtigen Parametern erzeugt hat. im Gegensatz dazu wird bei dem Funktionstest tatsächlich der Klick auf das Ui-element simuliert. dann wird überprüft, ob tatsächlich die detailactivity gestartet wird. ob die activity mit den richtigen Parametern gestartet wurde, wird hier getestet, indem die Ui-elemente der detailactivity untersucht werden.

Anzeige

Page 7: Tut er's oder tut er's nicht?

www.android360.de50

TiTelThema | testen mit android

android360 4 | 2011

Continuous-Integration-Servers. Für die Server Hudson beziehungsweise Jenkins gibt es ein Android-Plug-in, das dafür sorgt, dass vor der Ausführung der Tests ein Android-Emulator gestartet wird, der nach dem Been-den der Tests automatisch wieder gestoppt wird. Soll ein anderer Continuous-Integration-Server verwendet werden, muss das Starten und Stoppen des Emulators in das Build Script integriert werden.

Durch die Verwendung von Continuous Integration ist es möglich, alle Unit Tests regelmäßig sofort nach dem Einchecken der Codeänderung auszuführen und den Entwickler dadurch umgehend automatisiert per E-Mail zu informieren, wenn seine Codeänderung einen Programmfehler beinhaltet oder einen unerwünschten Seiteneffekt auf andere Codeteile hat. Es empfiehlt sich dabei, den Continuous-Integration-Server so zu konfi-gurieren, dass alle Unit Tests unmittelbar nach Code-änderungen (also nach dem Commit eines Entwicklers) durchgeführt werden. Die Funktionstests, deren Aus-führung deutlich länger dauert, sollten hingegen maxi-mal einmal täglich ausgeführt werden. Auf diese Art und Weise kann bereits während des Entwicklungsprozesses sichergestellt werden, dass die bereits umgesetzten Teile der Anwendung fehlerfrei funktionieren. Dadurch wird gewährleistet, dass nach Umsetzung aller Features be-reits eine qualitativ hochwertige Software an die Test-abteilung übergeben werden kann.

abnahme- und RegressionstestsAlle bisher vorgestellten Testmethoden basieren auf JUnit und damit auf dem Schreiben von Testcode in Java. Diese Tatsache führt dazu, dass die obigen Tests in der Regel nicht dazu geeignet sind, von Testabtei-lungen erstellt zu werden, weil deren Java-Kenntnisse meistens begrenzt oder gar nicht vorhanden sind. Um den Anforderungen des Testens durch Testabteilungen gerecht zu werden, liefert Android ein Tool, das die Definition der Tests über eine Skriptsprache (in diesem Fall das Python-Derivat Jython) ermöglicht. Das Tool heißt „Monkeyrunner“. Mit ihm kann man Android-Emulatoren oder Devices ohne die Verwendung von Java-Code steuern. Möglich wird das durch die Klassen MonkeyRunner, MonkeyDevice und MonkeyImage. Mit der Klasse MonkeyRunner ist es über die Metho-de waitForConnection möglich, sich mit einem Device oder Emulator zu verbinden. Als Parameter kann neben einem Timeout die Seriennummer des Geräts angegeben werden, wie sie von der Android Debug Bridge (ADB) vergeben wird. Wenn nur ein Gerät an den Computer angeschlossen beziehungsweise eine Emulator-Instanz gestartet ist, kann die Seriennummer auch weggelassen werden. Ansonsten lassen sich die Seriennummern der angeschlossenen Geräte und gestarteten Emulatoren über den Kommandozeilenbefehl adb devices ausge-ben. Die Methode waitForConnection liefert bei erfolg-reicher Verbindung zu einem Gerät oder Emulator ein Objekt vom Typ MonkeyDevice zurück. Mit ihm ist es möglich, zunächst die zu testende Anwendung zu instal-

Listing 6

public class MyListActivityRobotiumTest extends ActivityInstrumentationTestCase2<MyListActivity> {

public MyListActivityRobotiumTest() { super("de.mypackage", MyListActivity.class); }

public void testClick() { Solo solo = new Solo(getInstrumentation(), getActivity()); solo.assertCurrentActivity("Expected MyListActivity", MyListActivity.class); solo.clickOnScreen(100, 200); solo.assertCurrentActivity("Expected MyDetailActivity", MyDetailActivity.class); assertTrue(solo.searchText("42")); }}

Listing 5

public class MyListActivityUiTest extends ActivityInstrumentationTestCase2<MyListActivity> { private MyListActivity activity; private ListView view;

public MyListActivityUiTest() { super("de.mypackage", MyListActivity.class); }

public void setUp() { Intent intent = new Intent(Intent.ACTION_MAIN); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.setClassName(getInstrumentation().getTargetContext(), MainActivity.class.getName()); getInstrumentation().startActivitySync(intent); activity = getActivity(); view = (ListView)activity.findViewById(android.R.id.list); }

public void testClickOnList() { //test String className = MyDetailActivity.class.getName(); ActivityMonitor monitor = getInstrumentation().addMonitor(className, null, false); TouchUtils.clickView(this, view.getChildAt(0)); MyDetailActivity detailActivity = (MyDetailActivity)monitor.waitForActivity(); //verify assertNotNull(detailActivity); TextView idView = (TextView) detailActivity.findViewById(R.id.txt_id); ViewAsserts.assertOnScreen(detailActivity.getWindow().getDecorView(), idView); assertEquals("42", idView.getText().toString()); }}

Page 8: Tut er's oder tut er's nicht?

www.android360.de 51

testen mit android | TiTelThema

4 | 2011 android360

lieren, zu starten und dann skriptgesteuert Tests durch-zuführen. Ein Beispiel-Script kann unter [4] eingesehen werden.

Um das Ergebnis mit dem erwarteten Ergebnis ver-gleichen zu können, bietet die Klasse MonkeyDevice über die Methode takeSnapshot die Möglichkeit, ein Bildschirmfoto zu erstellen. Der Rückgabewert ist vom Typ MonkeyImage, das dann mit früher erstellten Bild-schirmfotos über die Methode sameAs(…) verglichen werden kann. Leider ist es momentan noch nicht mög-lich, auch Bilder von der Festplatte zu laden. Das soll aber in einem späteren Release des MonkeyRunners hinzugefügt werden. Da es sich bei dem Bildschirmfo-to tatsächlich um ein Foto des kompletten Bildschirms handelt, muss man bei dem Vergleich berücksichtigen, dass auch Uhrzeit und Batteriestatus enthalten sind. Ein 1:1-Vergleich mit einem vorher erstellten Screen-shot wird also in der Regel fehlschlagen. Um das zu umgehen, gibt es zwei Möglichkeiten. Entweder man gibt beim Bildvergleich über sameAs(…) als zweiten Parameter einen Prozentwert an, zu dem sich die Bil-der ähneln müssen, oder man wählt über die Methode getSubImage(…) den Bildbereich aus, den man tat-sächlich vergleichen möchte. Über diesen Bildvergleich kann überprüft werden, ob der Test bestanden wurde oder nicht.

Legt man nun für jede zu testende Android-Version und für verschiedene Hardwarekonfigurationen ein AVD (Android Virtual Device) an und startet für jedes AVD eine Emulator-Instanz, lassen sich die so erstell-ten Abnahmetests hervorragend (auch automatisiert) parallel auf den verschiedenen Emulator-Instanzen aus-führen und es kann ein breites Spektrum an Hard- und Softwarekonfigurationen und -version parallel getes-tet werden, indem man jeweils dem Jython Script die Seriennummer der zu testenden Emulator-Instanz als Parameter übergibt. Auf die gleiche Weise können die Tests auch auf echten Geräten durchgeführt werden. Außerdem lassen sich diese Abnahmetests für spätere Versionen der Software auch hervorragend als Regressi-onstests verwenden.

StresstestsZiel eines Stresstests ist es, dass sich die Anwendung stabil verhält, egal was die Endbenutzer mit ihren Ge-räten anstellen. Man würde sein Handy sicher keinem Affen in die Finger geben, aber in Anspielung auf des-sen mögliches Vorgehen in einer solchen Situation lie-fert Android neben dem MonkeyRunner ein weiteres „Affentool“: das Tool Monkey [5]. Mit diesem ist es möglich, zufälliges Benutzerverhalten zu simulieren, also eine (pseudo-)zufällige Folge von Touch-Events, Gesten, Tastatureingaben, Handytastendruck (Zu-rück-Taste, Home-Taste, Lautstärkeregelung etc.) und weitere Ereignisse wie die Bildschirmrotation usw. anzustoßen. Dabei sind die Anzahl der Events und der Abstand zwischen den einzelnen Events konfigurier-bar. Mit dem Monkey lassen sich auf diese Weise Tests

durchführen, bei denen man feststellen kann, wie stabil sich die Anwendung unter Stress verhält. Ein weiteres Feature dieses Tools ist es, dass man es über die Ein-schränkung des Tests auf bestimmte Packages auch so konfigurieren kann, dass nur Teile der Anwendung ge-testet werden. Das ist vor allem dann sinnvoll, wenn von Endkunden immer wieder ein bestimmter Fehler gemeldet wird, der aber nicht reproduziert werden kann. Er kann dann eventuell mit Monkey nachgestellt werden. Für diese Situation ist es auch sinnvoll, dass man den seed, also den Startwert der Zufallszahlenfol-ge, angeben und so die Reihenfolge der zufällig erzeug-ten Events reproduzieren kann.

FazitTesten von mobilen Geräten ist eine Herausforderung, aber mit den hier vorgestellten Methoden und Tools ist es möglich, eine individuell auf Anwendungen und Un-ternehmen zugeschnittene Teststrategie zu entwickeln, mit deren Hilfe über Unit Tests, funktionale Entwick-lertests, Abnahme- und Regressionstests und Stresstests die korrekte und zuverlässige Funktion der Anwendung weitestgehend sichergestellt werden kann.

links & literatur

[1] http://bit.ly/d8vteo

[2] http://bit.ly/piUama

[3] http://code.google.com/p/robotium/

[4] http://bit.ly/eQcdiW

[5] http://bit.ly/4fb7mi

arne limburg ist softwarearchitekt bei der open knowledge GmbH in oldenburg. er verfügt über langjährige erfahrung als entwickler, architekt und Consultant im Java-Umfeld und ist auch seit der ersten stunde im android-Umfeld aktiv.

mit dem tool monkey ist es mög-lich, zufälliges Benutzerverhalten zu simulieren, also eine (pseudo-)zufällige Folge z. B. von Gesten.