Lohnt der Einsatz von ProGuard, Monkey und uiautomator?

Android-Entwicklertools auf dem Prüfstand: ProGuard, Monkey & uiautomator
Kommentare

Das Android-SDK bildet heutzutage die Basis für all jene, die eine Applikation für das mobile Betriebssystem von Google erstellen wollen. Die Standardentwicklertools, wie z. B. AVD und SDK Manager sowie das eigene Logging-System logcat, sollten dabei jedem Android-Entwickler bekannt sein. Neben diesen Standardwerkzeugen enthält das SDK noch weitere Hilfsmittel zur Umsetzung mobiler Anwendungen.

Diese zusätzlichen Tools können den Entwickler sowohl direkt im Entwicklungsprozess als auch bei der Verbesserung der Sicherheit und beim Testen der fertigen Applikation unterstützen. Aufgrund der vielen Zusatzfunktionen lohnt sich eine genauere Betrachtung dieser Tools, wobei der Fokus auf ProGuard, Monkey und uiautomator gelegt wird. Einer ausführlichen Beschreibung der Funktionsweise folgt eine Evaluation darüber, ob die Verwendung des jeweiligen Tools zu einer gesteigerten Sicherheit oder Qualität des Endprodukts führt. Jedes Tool erhält somit eine Bewertung auf Basis der Evaluationsergebnisse und eines vordefinierten Kriterienkatalogs, der primär die Gebrauchstauglichkeit für Entwickler berücksichtigt.

Die Android-Entwicklertools sind hilfreiche Werkzeuge, durch deren Einsatz sich eine App selbst sowie ihr Entwicklungsprozess optimieren lassen. Da diese Werkzeuge nicht zwangsweise allen Entwicklern bekannt sind, stellt dieser Artikel daher drei dieser Hilfsmittel, ProGuard, Monkey und uiautomator vor. Grund für die Auswahl genau dieser drei ist die Tatsache, dass sie im Gegensatz zu anderen Entwicklertools, wie z. B. dem Emulator oder dem Logging-System logcat, relativ unbekannt sind, aber dennoch großes Potenzial für die Verbesserung der Sicherheit und Qualität einer App bieten.

Die einzelnen Funktionen der drei Tools zielen dabei auf den Schutz des geistigen Eigentums sowie das Durchführen von Testszenarien ab. ProGuard bietet dabei eine Verschleierung der Android-Installationsdatei, um Reverse Engineering zu erschweren, durch das ein Angreifer den Quellcode einer App offenlegen kann. Im Gegensatz dazu sind Monkey und uiautomator für das Ausführen verschiedener Testszenarien vorgesehen. Als simples Kommandozeilentool kann Monkey Pseudo-Touch-Events simulieren und die App somit einem Stresstest aussetzen, während uiautomator den Entwickler beim Schreiben und Durchführen von UI-Tests unterstützt.

Evaluiert wird nun, ob ein Einsatz der drei genannten Tools tatsächlich eine Steigerung der Sicherheit und Qualität bewirken kann. Um diese Frage zu beantworten, wird jedes Tool einzeln betrachtet. Die Betrachtung umfasst dabei eine Beschreibung der Funktionsweise inklusive mehrerer Anwendungsbeispiele sowie eine Evaluation in Bezug auf die Gebrauchstauglichkeit. Im Grunde stellt dieser Artikel einen Ratgeber dar, der Schwächen und Stärken von ProGuard, Monkey und uiautomator aufzeigt und Entwickler bei der Entscheidung bezüglich der Verwendung dieser Tools unterstützen soll.

Stellen Sie Ihre Fragen zu diesen oder anderen Themen unseren entwickler.de-Lesern oder beantworten Sie Fragen der anderen Leser.

ProGuard

Wie sich nach kurzer Recherche im Internet herausfinden lässt, liegen Applikationsdateien im Dateisystem von Android relativ ungeschützt vor. Bereits mit wenigen Kommandozeilenbefehlen ist es möglich, Installationsdateien vorhandener Apps sowie die zugehörigen Datenbankdateien von einem Smartphone auszulesen und auf einem Rechner abzuspeichern (siehe: Fora, P. O.: „Beginners Guide to Reverse Engineering Android Apps“, Präsentation auf der RSAConference, Februar 2014, S. 6–9); ein Root-Zugriff ist dafür nicht erforderlich. Das Speichern der heruntergeladenen Dateien ist insofern kritisch, als dass Angreifer versuchen könnten, den Quellcode einer App aus der Installationsdatei zu rekonstruieren, um somit Zugang zu sensiblen Daten, die direkt im Programmcode liegen, wie z. B. Lizenzschlüssel, zu erhalten. ProGuard versucht dieser Rekonstruktion, auch Reverse Engineering genannt, entgegenzuwirken. Um jedoch die Arbeits- und Funktionsweise von ProGuard besser verstehen zu können, ist zunächst die genauere Betrachtung der Angriffsmethode, also dem Reverse Engineering, sinnvoll.

Reverse Engineering – wie geht das?

Reverse Engineering bezeichnet grundlegend einen Vorgang, der versucht, aus einem bestehenden Programm den zugrunde liegenden Quellcode zu rekonstruieren. Dieser Vorgang wird im Normalfall durch den Einsatz von Decompilern oder Disassemblern unterstützt, die den binären Maschinencode des ausführbaren Programms wieder in eine für Menschen lesbare Darstellung zurückführen können.

Um den Vorgang des Reverse Engineerings unter Android besser zu verstehen, wird zunächst ein Blick auf die Installationsdatei einer Android-App, eine so genannte APK-Datei (siehe: Fora, P. O.: „Beginners Guide to Reverse Engineering Android Apps“, Präsentation auf der RSAConference, Februar 2014, S. 4), geworfen. Diese APK-Datei ist im Grunde ein archivierter Ordner, der die Ressourcen einer fertigen App enthält. Zum Inhalt dieses Ordners zählen ein Android-Manifest, die Datei classes.dex sowie drei weitere Unterordner, die die grafischen Ressourcen, verwendete Bibliotheken und Informationen über die App-spezifische Signatur enthalten. Diese Dateien befinden sich jedoch durch das Kompilieren in einer für den Menschen nicht lesbaren Form.

Zur Rekonstruktion des Programmcodes ist vorerst nur die Datei classes.dex von Interesse, da sie die Informationen über die Paketstruktur und die verwendeten Klassen beinhaltet. Zur Extraktion dieser Daten lässt sich das Tool dex2jar verwenden. Mithilfe einfacher Kommandozeilenbefehle kann die classes.dex-Datei in eine .jar-Datei umgewandelt werden (siehe: Fora, P. O.: „Beginners Guide to Reverse Engineering Android Apps“, Präsentation auf der RSAConference, Februar 2014, S. 14–16). Folgender Befehl zeigt eine beispielhafte Ausführung des Tools: $ dex2jar <apk_name>.apk.

Um den Quellcode aus der entstandenen .jar-Datei einsehen zu können, wird das Programm JD-GUI verwendet. JD-GUI ist ein Java-Decompiler mit grafischer Benutzeroberfläche, der die in der .jar-Datei vorhandenen .class-Dateien wieder in Java-Code umwandeln und somit den Programmcode in lesbarer Form anzeigen kann.

Das Reverse Engineering mit dem Tool dex2jar und des JD-GUI ist also relativ unkompliziert. Eine Beschränkung ergibt sich jedoch dadurch, dass keine Möglichkeit zur Rekonstruktion der grafischen Ressourcen, wie z. B. der Layout-XML-Dateien, existiert. Auch das Ändern des ausgelesenen Java-Codes und das anschließende Generieren einer APK-Datei aus den manipulierten Dateien lassen sich mit diesen Tools nicht realisieren. Werden diese Funktionen benötigt, bietet sich das Hilfsprogramm apktool an.

Ähnlich wie dex2jar lässt sich auch apktool über die Kommandozeile ausführen (siehe: Fora, P. O.: „Beginners Guide to Reverse Engineering Android Apps“, Präsentation auf der RSAConference, Februar 2014, S. 11–13). Die Syntax eines Befehls sieht dabei wie folgt aus: $ apktool d <apk_name>.apk.

Das Resultat dieses Befehls ist nun keine .jar-Datei, sondern ein neuer Ordner, der den dekodierten Inhalt der APK-Datei enthält. Die grafischen Ressourcen befinden sich im Ordner /res/, während der rekonstruierte Quellcode im Ordner /smali/ zu finden ist. Die Besonderheit von apktool ist, dass der dekodierte Programmcode nicht im Java-, sondern im smali-Format vorliegt. Das smali-Format beschreibt dabei eine lesbare Form von Android-Bytecode, den ein Angreifer manipulieren kann. Im Gegensatz zu dex2jar kann mithilfe von apktool aus den veränderten .smali-Dateien eine neue, manipulierte APK-Datei erzeugt werden. Sie lässt sich mithilfe der Tools jarsigner und keytool signieren. Anschließend wird die signierte APK-Datei per Dateiübertragung auf das Smartphone transferiert und dort installiert. Da der Android-Bytecode einer smali-Datei aufgrund seiner Syntax jedoch wesentlich schwerer zu lesen ist als normaler Java-Code, ist ein Angriff per apktool meist aufwändiger.

Das Auslesen wichtiger Informationen und sogar die Rekonstruktion und Manipulation des Quellcodes von Android-Apps ist also nicht besonders schwierig. Für Unternehmen kann dieser Umstand ein enormes Sicherheitsrisiko darstellen, wenn Angreifer entsprechende Codepassagen analysieren und dadurch z. B. Informationen über den Datenaustausch der App mit einem firmeninternen API extrahieren können.

Wie kann das Reverse Engineering erschwert bzw. verhindert werden?

Einen Ansatz Reverse Engineering zu erschweren, verfolgt das Tool ProGuard. Es ermöglicht die Verschleierung des Quellcodes, sodass dieser nach einem Reverse Engineering nur noch schwierig nachzuvollziehen ist. Diese Verschleierung findet während des Kompilierungsprozesses der APK-Datei statt und kann grundlegend in drei Schritte unterteilt werden. Abbildung 1 zeigt ein Schaubild dieser Schritte, die während des Kompilierungsprozesses direkt nach der Umwandlung des Quellcodes in eine .jar-Datei ausgeführt werden. Die Ausführung von ProGuard bezieht sich dabei jedoch immer nur auf den Quellcode aus dem eigenen Projekt; das heißt eingebundene Bibliotheksprojekte oder .jar-Dateien werden nicht verschleiert (Abb. 1).

Der erste Schritt befasst sich mit dem Minimieren des Quellcodes. Minimieren bedeutet in diesem Zusammenhang, dass ProGuard ungenutzte Klassen, Methoden und Attribute erkennt und entfernt. Im anschließenden Optimierungsprozess wird der Quellcode unter anderem durch das Entfernen ungenutzter Methodenparameter weiter verbessert. Der letzte Schritt ist die Verschleierung. Hier erhalten die verbliebenen Klassen, Methoden und Attribute neue Namen. Diese bestehen aus einem Minimum an Buchstaben und sind in der Regel nichtssagend, sodass ein Angreifer durch einen Methodennamen nicht auf die Funktionalität einer Methode schließen kann.

Es stellt sich die Frage, woher ProGuard die Kenntnis über genutzte und ungenutzte Klassen bzw. Methoden bei der Minimierung bezieht. Die Beantwortung dieser Frage ist relativ simpel: ProGuard durchläuft rekursiv den Programmcode und ermittelt dabei, ob eine Klasse oder Methode aufgerufen wird oder nicht. Für diese Rekursion werden die so genannten Einstiegspunkte benötigt. Wie der Name bereits vermuten lässt, definieren sie die Startpunkte, bei denen ProGuard mit der Rekursion beginnen soll. In mobilen Applikationen sind diese Einstiegspunkte in der Regel solche Klassen, die vom Typ Activity erben. Alle Klassen, Methoden und Attribute, die ProGuard als genutzt erkennt, bleiben erhalten, der Rest wird entfernt.

Das Entfernen aller von ProGuard als ungenutzt deklarierten Klassen und Methoden kann durchaus zu Problemen führen. Kritisch ist das Entfernen von Elementen vor allem dann, wenn eine Applikation sehr generisch aufgebaut ist. In diesem Fall werden viele Klassen und Methoden per Reflektion aufgerufen, das heißt unter Umständen ist nicht immer klar, welche Klasse oder Methode in den entsprechenden Codepassagen aufgerufen wird. Diese Dynamik beim Aufruf von Klassen oder Methoden macht es ProGuard unmöglich zu erkennen, ob ein Element genutzt wird oder nicht. Falls das Element an keiner anderen Stelle im Code aufgerufen wird, interpretiert ProGuard es als ungenutzt und entfernt es aus dem Quellcode.

Um dennoch keine Fehler oder sogar Abstürze aufgrund fehlender Klassen oder Methoden zu erhalten, muss der Entwickler dem Tool helfen, indem er über eine Konfigurationsdatei Einstellungen vornimmt, die die Ausführung des Tools beeinflussen. Nachfolgend werden einige der nützlichsten Konfigurationsmöglichkeiten erläutert, alle weiteren vorhandenen Befehle sind in der offiziellen Dokumentation zu finden.

keep [, modifier] class_specification: Dieser Befehl ermöglicht die Spezifizierung von Klassen oder Methoden, die ProGuard bei der Minimierung nicht entfernen darf. Soll z. B. die generische Klasse GenericClass im Paket de.test.example erhalten bleiben, so lässt sich dieses durch eine Zeile in der ProGuard-Konfigurationsdatei verhindern:

-keep public class de.test.example.GenericClass

Der keep-Befehl hat sehr viele Variationen, die es ermöglichen, verschiedenste Quellcodeelemente vor der Minimierung zu schützen. Dazu muss nicht immer der explizite Name einer zu schützenden Klasse gegeben sein, es lassen sich auch keep-Befehle definieren, die auf Typzugehörigkeit prüfen. Die erste Zeile des nachfolgenden Listings schützt z. B. alle Klassen, die vom Typ Activity erben, vor der Minimierung.

-keep public class * extends android.app.Activity
-keepclasseswithmembers class * {
  public (android.content.Context, android.util.AttributeSet);
}

Zeile 2 zeigt einen erweiterten keep-Befehl. Unter der Voraussetzung, dass die jeweilige Klasse einen Konstruktor besitzt, der als Parameter ein Objekt vom Typ Context und ein Objekt vom Typ AttributeSet erwartet, wird diese Klasse inklusive ihrer Attribute und Methoden vor der Minimierung geschützt.

dontshrink, dontoptimize, dontobfuscate: In bestimmten Fällen kann es durchaus vorkommen, dass die Deaktivierung einer der drei Schritte Minimierung, Optimierung und Verschleierung gewünscht ist. Dafür gibt es drei simple Befehle, deren Anwendung das folgende Listing beschreibt:

-dontshrink     // Deaktiviert den Minimierungs-Schritt
-dontoptimize   // Deaktiviert den Optimierungs-Schritt
-dontobfuscate  // Deaktiviert den Verschleierungs-Schritt

Die drei Schritte können dabei beliebig kombiniert werden. Es ist somit auch möglich, alle drei Schritte zu deaktivieren; dann würde die Ausführung des Tools allerdings keinen Vorteil bringen, da keine Änderungen am Programmcode vorgenommen werden. Standardmäßig sind alle drei Schritte aktiviert.

obfuscationdictionary filename: Über diesen Befehl lässt sich eine Textdatei angeben, die zum Verschleiern der Namen von Attributen und Methoden verwendet wird. Anstelle von kurzen Namen wie „a“ oder „b“ verschleiert ProGuard den Quellcode nun mit allen in der Textdatei angegebenen gültigen Worten. Leerzeichen, doppelte Worte sowie Satzzeichen werden dabei ignoriert. Dieser Befehl kann zum personalisierten Verschleiern genutzt werden, bringt aber keine signifikante Verbesserung der Verschleierungssicherheit mit sich.

Es ist absehbar, dass eine Konfiguration durch die verschiedenen Einstellungsmöglichkeiten und je nach Größe des Projekts sehr komplex und zeitaufwendig werden kann. Das Reverse Engineering ist zwar weiterhin durch die zuvor genannten Methoden möglich, allerdings wird es gerade durch den Schritt der Verschleierung erheblich erschwert, den Quellcode nachzuvollziehen. Abbildung 2 zeigt den extrahierten Quellcode des Reverse Engineerings von einer mit ProGuard kompilierten APK-Datei.

Abb. 2: Extrahierter Quellcode einer mit ProGuard verschleierten APK-Datei

Abb. 2: Extrahierter Quellcode einer mit ProGuard verschleierten APK-Datei

Wie zu sehen ist, kann nicht mehr auf den ersten Blick erkannt werden, welche Funktion die Klasse und ihre Methoden erfüllen. Die normalerweise sprechenden Klassen-, Variablen- und Methodennamen ersetzt ProGuard durch simple Namen, die einem Angreifer zunächst einmal nichts über die Funktionalität des Quellcodes verraten. Durch gezielte Analyse bestimmter Bereiche kann es jedoch trotzdem möglich sein, dass Angreifer den Quellcode manipulieren können. Die erforderliche Analyse zur Ermittlung der relevanten Codepassagen dauert jedoch je nach Konfiguration von ProGuard erheblich länger als bei nicht verschleiertem Quellcode.

Lohnt sich der Einsatz von ProGuard?

Das Reverse Engineering ist für Angreifer eine gute Möglichkeit, den Quellcode einer Applikation extrahieren und einsehen zu können. Gerade sensible Codepassagen, wie z. B. das Ansprechen eines firmeninternen API oder die Generierung von Schlüsseln, bieten Angreifern gute Möglichkeiten, Schadcode zu implementieren und somit die Applikation zu manipulieren. Mithilfe von ProGuard lassen sich Angriffe dieser Art erschweren.

Aufgrund der vielen Konfigurationselemente ist es Entwicklern möglich, den Quellcode entsprechend seiner Vorstellungen zu verschleiern. Die Quelle zeigt einige fertige Konfigurationsdateien auf und verdeutlicht, dass eine Konfiguration nicht immer trivial sein muss, sondern auch einen sehr komplexen Aufbau haben kann. Hinzu kommt, dass die Konfigurationsdatei bei Änderungen im Code erweitert oder angepasst werden muss, was zusätzlichen Implementierungsaufwand mit sich bringt. Es ist daher besonders wichtig, sich vorher Gedanken darüber zu machen, welche Inhalte gegebenenfalls verschleiert werden sollten und welche nicht.

Grundlegend sollte sich jeder Entwickler darüber im Klaren sein, dass die Verwendung von ProGuard keine hundertprozentige Sicherheit garantieren kann. Denn obwohl der Quellcode verschleiert ist und hilfreiche Kommentare ebenfalls fehlen, ist das Extrahieren von Informationen nicht unmöglich. Angreifer können versuchen, den verschleierten Code zu analysieren und darüber Verbindungen zu einzelnen Klassen und Methoden herzustellen. Dieser Vorgang ist allerdings mit einem sehr hohen Aufwand verbunden und für einen Angreifer nur dann sinnvoll, wenn er sicher sein kann, dass der Quellcode der App für ihn relevante Informationen enthält.

Zusammenfassend lässt sich sagen, dass ProGuard ein sehr nützliches Tool mit viel Potenzial  ist. Richtig konfiguriert kann es die Sicherheit des Quellcodes deutlich steigern und so Angriffe durch Reverse Engineering erschweren. Je nach Komplexität des Projekts sollte allerdings darauf geachtet werden, ob es sinnvoll ist, das gesamte Projekt oder lediglich die wichtigsten Pakete zu verschleiern. Grundsätzlich gilt, dass ProGuard auf jeden Fall genutzt werden sollte, sobald die App in Kommunikation mit einem firmeninternen API steht oder anderweitig mit sensiblen Daten arbeitet. Solche Passagen sind willkommene Angriffspunkte und sollten auf keinen Fall ungeschützt im Quellcode zu finden sein.

Monkey

Beim Testen von Applikationen kann es vorkommen, dass aufgrund von Zeitdruck nicht alle möglichen Testszenarien durchgespielt werden können. Dabei ist gerade das Testen des Gesamtzusammenspiels aller Funktionalitäten einer App besonders wichtig, um deren Qualität zu garantieren. Dazu zählt auch das Testen der App unter besonderen Bedingungen, denn gerade Fälle wie z. B. das Einschalten des Flugmodus werden nicht immer bedacht und können eine Applikation im Zweifelsfall zum Absturz bringen. Um diesen Fall zu verhindern, ist das gezielte Durchtesten der gesamten Applikation unumgänglich. Im Folgenden soll Monkey betrachtet werden, ein Programm zur Unterstützung von Applikationstests.

Monkey ist ein einfaches Kommandozeilentool mit dem sich Pseudo-Nutzer-Events auf einem Gerät oder Emulator simulieren lassen. Diese Events können sich auf eine Applikation beschränken und werden in der Standardkonfiguration sehr schnell hintereinander ausgeführt, sodass eine App innerhalb weniger Sekunden auf mehrere hunderte Events reagieren muss. Dies natürlich am besten, ohne dabei abzustürzen. Eine einfache Ausführung des Tools zeigt folgender Codeschnipsel:

$ adb shell Monkey <paketname> -v <event-count>

Dieser Beispielbefehl erlaubt das Einstellen zweier Parameter, zum einen den <event-count>-und zum anderen den <paketname>-Parameter. Der <event-count>-Parameter definiert die Anzahl an Events, die Monkey auf dem Gerät simulieren soll, und muss bei jedem Aufruf angegeben werden. Andernfalls führt das Tool den Befehl nicht aus. Der Parameter <paketname> ist im Gegensatz optional und dient dazu, die Events des Tools nur an bestimmte Pakete auszusenden. Wird der Parameter weggelassen, schickt Monkey Events an jede auf dem Gerät oder Emulator installierte Applikation aus.

Um die Ausführung des Tools weiter zu spezifizieren und sie möglichst genau auf die Bedürfnisse einer jeweiligen App anpassen zu können, gibt es zusätzliche Parameter. Diese lassen sich grundsätzlich in vier Kategorien unterteilen: die Basiskonfiguration, Constraints-, Debug- und Event-Kategorie. Die Parameter der Basiskonfiguration sind dabei für generelle Einstellungen, wie z. B. die Anzahl an auszuführenden Events, zuständig. In der Constraints-Kategorie befinden sich Parameter, die Einschränkungen des Testlaufs definieren. Ein Beispiel dafür ist die Angabe eines Paketnamens, sodass Monkey nur noch Events auf diesem Paket ausführen darf. Neben der Basiskonfiguration und der Constraints-Kategorie gibt es noch die Debug-Kategorie. Sie bietet hilfreiche Optionen, um genaue Logausgaben über die ausgeführten Events zu erhalten. Als letztes bleibt die Event-Kategorie. Mit ihren Parametern lassen sich die von Monkey ausgesendeten Events konfigurieren, z. B. durch das Festlegen einer Frequenz, mit der diese ausgelöst werden. Nachfolgend sind einige der möglichen Parameter aufgelistet.

-s <seed>: Mithilfe dieses Parameters kann der Entwickler einen Wert für den Pseudozufallsgenerator angeben. Führt er Monkey stets mit demselben Wert aus, wird dieselbe Sequenz von Events erstellt und simuliert. Das kann besonders dann hilfreich sein, wenn bei einer Sequenz ein Fehler auftritt und dieser reproduziert werden soll.

–throttle <milliseconds>: Normalerweise versucht Monkey, die Events so schnell wie möglich zu generieren und auszulösen. Mithilfe des throttle-Parameters lässt sich eine feste Verzögerung zwischen der Ausführung der einzelnen Events einstellen. Für den Entwickler ist dieser Parameter vor allem dann nützlich, wenn er z. B. auf die Rückgabe von Informationen aus einem API wartet oder er einfach den Verlauf und die Ausführung der Events auf dem Gerät mitverfolgen möchte.

–pct-syskeys <percent>: Über diesen Parameter lässt sich der prozentuale Anteil an Events definieren, die auf den Hardware- bzw. Systemelementen, wie z. B. Home-Button oder den Volume-Controllern ausgeführt werden.

–ignore-crashes: In der Regel stoppt Monkey die Aussendung von Events, sobald eine Applikation abstürzt. Dieser Parameter erlaubt dem Entwickler, dieses Verhalten zu deaktivieren, sodass Monkey beim Auftreten eines Absturzes weiterhin die Events an das Gerät aussendet, bis die vorgegebene Anzahl an Events erreicht ist.

Die hier genannten Parameter in Verbindung mit weiteren, hier nicht genannten Parametern, ermöglichen eine individuelle Konfiguration von Monkey. Das folgende Beispiel zeigt eine mögliche Ausführungsvariante des Tools:

$ adb shell Monkey -p de.test.example --pct-syskeys 20 --pct-motion 5 --pct-appswitch 0 --throttle 100 -v 2000

Dieser Befehl beschränkt die Ausführung der Events auf die Applikation mit dem Paketnamen „de.test.example“. Zusätzlich wird festgelegt, dass Monkey nur 20 Prozent aller Events auf Systemelemente, wie z. B. den Home-Button, ausführen darf und sogar nur 5 Prozent zum Erstellen von Motion-Events verwendet. Des Weiteren wird über den appswitch-Parameter definiert, dass Monkey die Applikation nur einmal starten und danach die Activity nicht erneut öffnen darf. Zu guter Letzt soll das Tool insgesamt 2 000 Events auslösen und sie in einem Intervall von 100 ms versenden.

Um die ausgelösten Events nachvollziehen zu können, dokumentiert sie Monkey und listet sie der Reihenfolge nach in der Konsole auf. Listing 1 zeigt, wie diese Dokumentation aussehen kann. Neben der Art des Events, z. B. Touch-Event, werden an dieser Stelle auch die Koordinaten angegeben, an deren Stelle Monkey das Event ausgelöst hat. Auch Fehlermeldungen bzw. Abstürze werden mitsamt ihres Stacktraces in der Konsole aufgezeigt, nachdem Monkey die Ausführung der aktuellen Sequenz aufgrund des Fehlers abgebrochen hat.

 :Switch:#Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;launchFlags=0x10200000;component=com.google.android.deskclock/com.android.deskclock.DeskClod;end
:Sending Touch (ACTION_DOWN): 0:(319.0, 591.0)
:Sending Touch (ACTION_UP): 0:(318.40, 591.15)
//Monkey finished

Ist Monkey zum Testen von Applikationen geeignet?

Grundsätzlich ist Monkey ein nützliches Tool, um Stresssituationen für Applikationen zu simulieren. Entwickler wissen meistens, wie sie ihre App bedienen müssen und werden blind für unsinnige Eingaben. Diese Eingaben können von Monkey übernommen werden. Das Tool erstellt und führt alle Events nach Zufall aus und schaltet dabei z. B. den Flugmodus ein, macht einen Screenshot und klickt generell jede Stelle an, die „klickbar“ ist.

Dennoch lässt sich auch feststellen, dass das Tool dieselben Aktionen oft doppelt ausführt und der Ablauf der Events durch die hohe Auslösegeschwindigkeit für den Entwickler nur schwer nachvollziehbar ist. Über den throttle-Parameter könnte zwar eine Verzögerung eingestellt werden, die Auslösung von 100 Events würde bei einer Verzögerung von einer Sekunde dann aber auch einige Zeit in Anspruch nehmen.

Auch das gezielte Testen bestimmter Funktionalitäten, z. B. einer Suchfunktion, ist mit Monkey nicht möglich. Da die Events nach dem Zufallsprinzip erstellt werden, ist das selbstständige Erzeugen einer Event-Reihenfolge nicht möglich, und auch die Parameter in der Constraints-Kategorie bieten nicht das Maß an Funktionalität, das für die Beschränkung auf eine Komponente der Applikation notwendig wäre.

Generell ist das Tool sinnvoll für Testfälle, in denen die Applikation einer Stresssituation ausgesetzt werden soll. Entwickler können so prüfen, wie eine App auf viele schnelle Eingaben und Events reagiert. Als Tool zum kontrollierten und gezielten Testen von Komponenten und Funktionen ist es allerdings nicht geeignet, da die Parameter dafür nicht fein genug einstellbar sind. Die Qualität einer App kann durch Nutzung dieses Tools also nicht maßgeblich gesteigert werden. Eine mögliche Alternative stellt das Tool uiautomator dar, das im nächsten Absatz genauer erläutert wird.

uiautomator

Zum Testen von Android-Applikationen bietet Google ein weiteres Tool namens uiautomator an. Im Gegensatz zu Monkey simuliert uiautomator jedoch keine Pseudonutzerevents, sondern erlaubt das automatisierte Testen der User-Interface-(UI-)Komponenten einer App. Diese Tests müssen zwar vom Entwickler selbst geschrieben werden, bieten hierdurch aber auch den Vorteil, bestimmte Funktionen einer App gezielt testen zu können. Zum Benutzen des Tools wird das Android-API-Level 16 oder höher und zur Durchführung der Tests ein Android-Smartphone benötigt.

Analysieren der UI-Elemente

Bevor ein solcher UI-Test geschrieben werden kann, ist eine Analyse der Layouthierarchie der zu testenden App hilfreich. Für diesen Zweck stellt Google das GUI-Tool uiautomatorviewer bereit. Es ist ebenfalls im Android-SDK enthalten und ermöglicht die Aufnahme eines Screenshots des aktuell am PC angeschlossenen Smartphonedisplays. In dem Tool selbst kann der Entwickler nun mit der Maus über die verschiedenen UI-Elemente des Screenshots fahren und bekommt zu jedem Element nützliche Detailinformationen, wie z. B. die Layoutattribute resourceId und contentDescription, angezeigt. Diese Informationen werden später beim Schreiben eines UI-Tests benötigt, um die jeweiligen UI-Elemente gezielt ansprechen zu können. Bei der Layoutentwicklung von Apps ist es daher generell ratsam, jedem Element einen individuellen Wert für das Attribut contentDescription zuzuweisen, damit es in einem Test ansprechbar ist.

Einen Test erstellen

Zum Erstellen eines neuen Tests wird zuerst ein neues Java-Projekt (wichtig: kein Android-Projekt!) angelegt. Dieses benötigt noch die beiden Dateien uiautomator.jar und android.jar, zu finden im Android-SDK im Ordner /platforms/. Sind diese beiden Bibliotheken hinzugefügt, kann die Entwicklung eines Tests beginnen. Zum Ansprechen der verschiedenen UI-Elemente innerhalb einer App dient das uiautomator-API, enthalten in der zuvor eingebundenen uiautomator.jar. Der nachfolgende Abschnitt verschafft einen kleinen Überblick über die vorhandenen Schlüsselklassen des API.

UiDevice: Die UiDevice-Klasse repräsentiert das Gerät selbst. Diese Objektinstanz erlaubt das Ausführen von gerätespezifischen Aktionen, wie z. B. das Drücken des Home-Buttons und anderer Hardwaretasten. Auch das Einstellen einer bestimmten Geräteausrichtung, Landscape oder Portrait, ist hiermit möglich.

UiSelector: UiSelector ist eine Klasse, mit der sich Suchanfragen für UI-Komponenten definieren lassen. Der Entwickler kann dabei über die Methode childSelector mehrere UiSelectoren beliebig verschachteln, um z. B. auch Elemente innerhalb einer ListView zu finden. Das folgende Beispiel zeigt, wie der erste UiSelector die erste Instanz vom Typ android.widget.ListView findet. Über einen childSelector kann ein zweiter UiSelector definiert werden, der in der ListView nach einem Element mit dem Namen „Apps“ sucht.

UiObject appItem = new UiObject(new UiSelector()
  .className(android.widget.ListView").instance(1)
  .childSelector(new UiSelector().text("Apps")));

Enthält das Layout mehrere UI-Elemente mit demselben Attributwert, so wird das erste in der Layouthierarchie zurückgegeben. Die Suche nach einem Element kann dabei auf verschiedenen Attributwerten basieren, z. B. auf der contentDescription, der resourceId oder auch auf  Klassentypen begrenzt werden.

UiObject: Der UiSelector gibt bei seiner Suche ein Objekt vom Typ UiObject zurück. Diese Klasse repräsentiert nun ein einzelnes UI-Element und bietet nutzerspezifische Funktionen an, mit denen z. B. das Anklicken des Elements simuliert werden kann.

UiCollection: Die Klasse UiCollection repräsentiert Layoutobjekte, die als eine Art Container bzw. Wrapper für untergeordnete UI-Elemente dienen, z. B. ein FrameLayout. Genau wie ein UiObject, lässt sich auch eine UiCollection über die Definition einer Suchanfrage durch einen UiSelector finden. Die Kindelemente einer solchen UiCollection lassen sich durch die Kombination verschiedener getChild-Methoden mit einem UiSelector finden und ansprechen (vgl. folgendes Listing).

UiCollection videos = new UiCollection(new UiSelector()
  .className("android.widget.FrameLayout"));
UiObject checkbox = videos.getChild(new UiSelector()
  .className("android.widget.CheckBox"));

UiScrollable: UiScrollables können das Scrollverhalten in einem Test simulieren, egal, ob horizontal oder vertikal. Sie werden häufig für ListViews verwendet, damit ein UiSelector auch die Kindelemente der ListView erreichen kann, die aktuell nicht auf dem Display sichtbar sind.

Mithilfe der vorgestellten Klassen kann ein Entwickler nun mehrere Tests erstellen und die UI-Elemente innerhalb einer App ansprechen und manipulieren. Die Klasse in Listing 2 zeigt einen Test, der für die Standard-Taschenrechner-App auf Android-Geräten prüft, ob diese zu einem bestimmten Input, in diesem Fall „3 + 5“, den richtigen Output, also als Ergebnis „8“, erzeugt.

public class CalculatorTest extends uiautomatorTestCase {
  public void testDemo() throws UiObjectNotFoundException {
    // Gehe zum Home-Screen
    getUiDevice().pressHome();

    // Öffne das "Alle Apps"-Fenster
    UiObject appsButton = new UiObject(new UiSelector().description("Apps"));
    appsButton.clickAndWaitForNewWindow();

    UiObject tabApps = new UiObject(new UiSelector().description("Apps").className("android.widget.TextView"));
    tabApps.clickAndWaitForNewWindow();

    UiScrollable appContainer = new UiScrollable(new UiSelector().scrollable(true));
    appContainer.setMaxSearchSwipes(20);
    appContainer.setAsHorizontalList();

    // Selector für die Rechner-App erstellen
    UiObject calcApp = appContainer.getChildByText(new UiSelector().className(android.widget.TextView.class.getName()), "Rechner");
    calcApp.clickAndWaitForNewWindow();

    // Berechnung 3 + 5 durchführen
    UiObject button3 = new UiObject(new UiSelector().resourceId("com.android.calculator2:id/digit_3"));
    UiObject button5 = new UiObject(new UiSelector().resourceId("com.android.calculator2:id/digit_5"));
    UiObject buttonAdd = new UiObject(new UiSelector().resourceId("com.android.calculator2:id/op_add"));
    UiObject buttonEq = new UiObject(new UiSelector().resourceId("com.android.calculator2:id/eq"));

    button3.click();
    buttonAdd.click();
    button5.click();
    buttonEq.click();

    // Prüfe, ob 8 in dem Resultfeld steht
    UiObject result = new UiObject(new UiSelector().resourceId("com.android.calculator2:id/formula"));
    assertEquals("8", result.getText());
  }
}

Testklasse ausführen

Zum Ausführen des soeben erstellten Tests, muss er in eine .jar-Datei umgewandelt, auf das Testgerät geschoben und dort ausgeführt werden. Für dieses Vorgehen sind folgende Kommandozeilenbefehle notwendig:

$ /tools/android create uitest-project -n  -t 1 -p 
$ ant build
$ adb push  /data/local/tmp/
$ adb shell uiautomator runtest  -c 
$ adb shell uiautomator runtest CalculatorTestProjekt.jar -c com.example.test.CalculatorTest

Diese Zeilen generieren die so genannte build.xml, eine Konfigurationsdatei, die zum Erzeugen der .jar-Datei benötigt wird. Der Befehl ant build erstellt anschließend die gewünschte .jar-Datei, die im Ordner /bin/ des Projekts zu finden ist. Mithilfe der dritten Zeile lässt sich die Datei nun auf das Android-Gerät verschieben und über den Befehl in der vierten Zeile ausführen. Der Parameter <JARS> dient nun zur Angabe einer oder mehrerer .jar-Dateien, die die Testfälle enthalten. Über den Parameter <CLASSES> lassen sich mehrere Testklassen oder Testmethoden innerhalb der .jar-Dateien angeben, die anschließend ausgeführt werden sollen. Eine beispielhafte Ausführung des Kommandos zeigt die fünfte Zeile, hier bezogen auf den Taschenrechner-Test aus Listing 2.

Sobald der Test gestartet wurde, kann der Entwickler den Ablauf direkt auf dem Smartphone verfolgen. Beim Auftreten eines Absturzes dokumentiert ihn uiautomator in der Konsole und bietet dem Entwickler so die Möglichkeit, den Fehler einzusehen und zu beheben. Sobald der Entwickler die entsprechenden Codepassagen angepasst hat, kann er den Test erneut ausführen und prüfen, ob der Fehler noch besteht.

Eignet sich das Tool uiautomator zum Testen in der Praxis?

Im Grunde ist uiautomator ein sehr hilfreiches Tool, um die Funktionalität von UI-Elementen in der App zu testen. Im Gegensatz zu Monkey kann der Entwickler die Testfälle hier selbst schreiben und ist nicht von der Zufallsgeneration der Events abhängig. Somit ermöglicht uiautomator einen Testablauf, der sehr viel zielgerichteter und flexibler ist. Auch im Vergleich zum manuellen Testen schneidet uiautomator besser ab. Es ist nicht unwahrscheinlich, dass beim manuellen Testen Fehler unterlaufen. Dies kann z. B. dadurch geschehen, dass eine Person bestimmte Fehlerfälle gar nicht betrachtet oder sie nicht als Fehler wahrnimmt. Außerdem kostet der Prozess des Testens viel Zeit und ist für die meisten Testpersonen eher uninteressant.

Mithilfe von uiautomator kann dieser Prozess automatisiert und dadurch beschleunigt werden. Natürlich bringt das Schreiben von Testfällen einiges an Aufwand mit sich, der Einsatz kann sich allerdings dennoch lohnen: Durch die fest definierten Testfälle in den einzelnen Testklassen ist es möglich, dieselbe Funktion wieder und wieder zu testen und somit eine hohe Qualität der App zu garantieren. Das ist vor allem dann vorteilhaft, wenn Entwickler neue Funktionen implementieren und sicherstellen wollen, dass ältere Funktionen dadurch nicht beeinträchtigt werden. Es gilt jedoch zu beachten, dass mit diesem Tool nur die Funktionalität von UI-Elementen testbar ist. Das bedeutet im Grunde, dass der Test einen bestimmten Input erzeugt und ihn mit dem zurückkommenden Output vergleicht. Jegliche interne Methodenabläufe, deren Ergebnisse nicht innerhalb des UI zu sehen sind, lassen sich mit diesem Tool nicht prüfen.

Fazit

Zusammenfassend betrachtet lässt sich feststellen, dass das Android-SDK einige nützliche Tools enthält, mit denen die Qualität und Sicherheit einer App gesteigert werden können. Dabei ist allerdings eine gute Auswahl erforderlich, denn nicht jedes Tool bringt in der Anwendung den gewünschten Erfolg.

Monkey beispielsweise bewirkt kaum eine Steigerung der App-Qualität. Da der Entwickler hier von Zufallsevents abhängig ist und nur wenig Einfluss auf die Art des Events und dessen Auslösungsort nehmen kann, sind personalisierte Testläufe und Funktionstests mit diesem Tool nicht möglich. Die Anwendung in der Praxis ist daher als suboptimal zu bewerten.

Anders sieht das bei ProGuard und uiautomator aus. Die Evaluation beider Tools zeigt, dass sie durchaus in der Lage sind, sowohl die Qualität als auch die Sicherheit einer App deutlich zu steigern. Beide Tools erfordern zwar einen zusätzlichen Aufwand, wie das Einstellen der Konfigurationsdatei (ProGuard) und das Schreiben von Testfällen (uiautomator), bieten dem Entwickler dadurch aber auch deutlich mehr Freiheiten und Flexibilität in der Anwendung der Tools.

Grundsätzlich ist es ratsam, die gerade vorgestellten und andere Hilfsprogramme vor dem Einsatz einer genauen Prüfung zu unterziehen. Die Anwendung eines Tools bringt dem Entwickler keinen Vorteil, wenn der zu leistende Aufwand höher als der spätere Nutzen ist. Aus diesem Grund kann es bei einigen Apps durchaus sinnvoll sein, zunächst nur die kritischen Passagen durch das Tool zu schützen oder zu testen. Die Entscheidung für oder gegen dieses Vorgehen obliegt dann der Verantwortung des Entwicklers.

Mobile Technology

Mobile TechnologyDieser Artikel ist im Mobile Technology erschienen. Mobile Technology ist der Wegbegleiter für alle, die sich professionell mit der Entwicklung für mobile Devices und den Möglichkeiten, die der Markt des Mobile Business und Marketing bereithält, beschäftigen.

Natürlich können Sie das Mobile Technology über den entwickler.kiosk auch digital im Browser oder auf Ihren Android- und iOS-Devices lesen. In unserem Shop ist das Mobile Technology ferner im Abonnement oder als Einzelheft erhältlich.

Aufmacherbild: Robot with cardboard box von Shutterstock.com / Urheberrecht: Palto

Unsere Redaktion empfiehlt:

Relevante Beiträge

Meinungen zu diesem Beitrag

X
- Gib Deinen Standort ein -
- or -