Fortschrittliches Testing von iPhone Apps mit UIAutomation

Das gründliche Testen von iPhone Apps kann eine große Herausforderung sein. Besonders durch den kurzen Update Zyklus, sowohl von Entwicklerseite (regelmäßige Updates der eigenen App) wie auch von Apple (Veröffentlichung neuer iOS Versionen mit teils großen Änderungen an der UI) werden notwendige Tests oft zu einer sehr mühsamen und repetitiven Aufgabe.

Mit UIAutomation hat Apple deshalb ab iOS 4.0 eine Möglichkeit geschaffen, automatisierte Tests durchzuführen und dem Entwickler somit viel Arbeit zu sparen. Der Grundgedanke von UIAutomation ist dabei, bei den Tests tatsächlich die UI der App anzusprechen (d.h. es werden tatsächliche Touches simuliert), und nicht wie in klassischen Testverfahren Methoden zu schreiben, welche nur den Sourcode der App ansprechen. Es gibt somit keinen Bruch zwischen den Testfällen und der späteren tatsächlichen Bedienung durch den Benutzer.

UIAutomation ist Teil des Instruments Sets von XCode und kann sowohl Apps auf dem iPhone, iPad aber auch im iPhone Simulator automatisch bedienen. Die Tests selbst werden dabei in Javascript geschrieben und greifen auf die Accessibility Eigenschaften der UI Elemente der App zu.

Im Folgenden werden wir anhand eines kleines Testprogramms eine kurze Einführung in UIAutomation geben und demonstrieren, warum wir bei fast allen unseren Apps im Testing darauf bauen.

Erstes Beispiel in UIAutomation anhand einer Beispiel App

Erster TableViewControllerZweiter TableViewController

Für unser Beispiel haben wir eine rudimentäre Fussballverwaltungs-App implementiert. Sie besteht aus zwei TableViewController, welche einmal die angelegten Vereine und einmal die angelegten Spieler darstellen. Zwischen den Controllern kann über eine TabBar gewechselt werden. Außerdem kann man in der App einen Verein hinzufügen und Spieler löschen.

Erstellen eines neuen Skripts in Instruments

Erstellen eines neuen Skripts in Instruments

Als erstes wollen wir automatisch in das Tab „Spieler“ wechseln und dort an eine bestimmte Position scrollen. Dazu erstellen wir ein neues Skript (siehe Screenshot) und geben folgenden Code ein:

//Um Tipparbeit zu sparen, definieren wir uns hier zwei Hilfsvariablen
var target = UIATarget.localTarget();
var app = target.frontMostApp();

/*
* Es ist zu empfehlen, einzelne Tests in Funktionen auszulagern. 
* Dies steigert die Übersichtlichkeit und ermöglicht es einzelne Tests seperat durchzuführen
*/
function scrollToPlayer(){
    app.tabBar().buttons()["Spieler"].tap();
    app.mainWindow().tableViews()[0].scrollToElementWithPredicate("name beginswith 'Oliver Baumann'");
}

//An dieser Stellen rufen wir unsere definierte Testfunktion auf
scrollToPlayer();

Jedes UIKit Element der App wird in Javascript durch ein zugehöriges UIAElement repräsentiert und findet sich, je nach Position in der jeweiligen View, auf einer gewissen Position in der View Hierarchie wieder. Wenn auf ein bestimmtes Element zugegriffen werden soll, muss in der View Hierarchie zum entsprechenden Element hin abgestiegen werden. In unserem Beispiel greifen wir zum Beispiel auf die TabBar unser App zu, holen uns aus der View Hierarchie alle Schaltflächen der TabBar und weisen UIAutomation an, auf den Button mit der Bezeichnung „Spieler“ zu klicken.

Beispielhafte View-Hierarchie

Beispielhafte View-Hierarchie

Genauso selektieren wir die erste (und in diesem Fall einzige) TableView unserer View Hierarchie und weisen UIAutomation an, zu einer bestimmten Zelle dieser Tabelle zu scrollen. Wir können diese Zelle mit einem einfachen Predicate auswählen oder auch direkt bestimmen, zu welcher Zelle gescrollt werden soll.

Sinnvolles Logging und das Definieren von Testfällen

In unserem bisherigen Beispiel musste der Benutzer selbst am Handy überprüfen, ob das Gerät die gegebenen Anweisungen richtig ausgeführt. Dies natürlich nicht die Idee von automatischen Tests. Stattdessen wollen wir nun im nächsten Schritt Testfälle definieren, Logging-Ausgaben einführen und automatisch Screenshots anfertigen.

Als unseren Testfall möchten wir dazu einen neuen Verein zur Liste hinzufügen. Dazu klicken wir zunächst auf den + Knopf in der Vereinsübersicht, tragen dann den Namen des neuen Vereins in das Textfeld ein, klicken auf Speichern und überprüfen danach ob die neue Zelle in der Tabelle vorhanden ist.

In diesem Bildschirm kann ein Verein hinzugefügt werden.

In diesem Bildschirm kann ein Verein hinzugefügt werden.

Im automatisierten Javascript-Code sieht das folgendermaßen aus:

var target = UIATarget.localTarget();
var app = target.frontMostApp();

function addClub(clubName){
    //Hiermit wird ein neuer Testfall angelegt und benannt
    UIALogger.logStart("Anlegen eines neues Vereins: "+clubName);

    //Wir navigieren auf die Anlege-Seite und füllen das Formular aus
    app.navigationBar().buttons()["Add"].tap();
    app.mainWindow().textFields()[0].setValue(clubName);
    app.mainWindow().buttons()["Speichern"].tap();

    //Wir loggen das erfolgreichen Ausfüllen des Formulars
    UIALogger.logMessage("Formular erfolgreich ausgefüllt");

    //Wir selektieren die (hoffentlich) neu angelegte Zelle
    var cell = app.mainWindow().tableViews()[0].cells().firstWithPredicate("name beginswith '"+clubName+"'");

    //Wir warten kurz bis die Übergangsanimation zwischen den Screenshots beendet ist
    target.delay(1);

    //Wir machen einen Screenshot der TableView um zu sehen ob unser unteres Ergebnis auch stimmt
    target.captureScreenWithName("Tabelle nach Anlegen");

    //Eine Zelle ist dann valid, wenn Sie in der TableView exisiert und somit wurde auch unser Test bestanden
    if(cell.isValid()){
        UIALogger.logPass("Anlegen eines neues Vereins: "+clubName);
    }else{
        UIALogger.logFail("Anlegen eines neues Vereins: "+clubName);	
    }
}

addClub("SC Freiburg");

Wir haben nun also einen Testfall definiert und können beim Ausführen des Skripts in Instruments den Test verfolgen und sehen, ob der Test erfolgreich war. Zu dem können wir uns im Nachhinein alle Testfälle mit Log-Ausgaben und Screenshots anschauen und so etwaige Fehler untersuchen.

Erfolgreiche Tests können in Instruments nachvollzogen werden.

Erfolgreiche Tests können in Instruments nachvollzogen werden.

Mit diesen Instrumenten ist uns nun möglich ein Set von Tests zu definieren, welche alle erfolgreich durchlaufen werden müssen, bevor wir ein Update der App freigeben. Ein Testen der App auch nach kleinen Änderungen ist somit ohne weiteren Aufwand möglich.

Kompliziertere Aktionen

Natürlich haben wir in unseren bisherigen Beispielen nur an der Oberfläche der Fähigkeiten von UIAutomation gekratzt. Einige besondere Eigenschaften und Fähigkeiten möchten ich an dieser Stelle noch hervorheben, da in diesen Fällen nochmals die Überlegenheit von UIAutomation zu handgeschriebenen Tests klar wird:

Behandlung von Alerts
Ein großes Problem bei händischen bzw. automatischen Tests ohne UIAutomation sind zufällig auftretende Alerts (z.B. weil das Handy einen Anruf oder eine SMS empfängt). UIAutomation kann diese Alerts automatisch schließen und fährt danach an der richtige Stelle mit dem Test fort. Der Test ist also nicht automatisch unbrauchbar, nur weil ein Alert aufgetreten ist. Dies ist besonders für zeitintensive automatische Tests sehr praktisch.

UIATarget.onAlert = function onAlert(alert){
    UIALogger.logWarning("Der Alert '" + alert.name() + "' hat die Ausführung unterbrochen!");
    return false; // use default handler
}

Zu dem können eigene Alert Handler programmiert werden, um mit auftretenden Alerts umzugehen. Zum Beispiel wenn ein Verein doppelt in die Liste eingetragen wird, könnte ein Alert von der App gefeuert werden und UIAutomation in einem solchen Alert-Handler dieses Verhalten behandeln.

Multitasking Tests
Mit der Einführung des Multitaskings und dem damit möglichen Wechsel zwischen den Apps sind einige Herausforderungen beim Testen hinzugekommen, da gestestet werden muss, ob die App auch den jeweiligen Screen bzw. die jeweilige Funktion nach dem kurzzeitigen Wechseln zwischen den Apps noch richtig ausführt. UIAutomation bietet hier eine einfache Möglichkeit Multitasking durch den Benutzer zu simulieren:

UIATarget.localTarget().deactivateAppForDuration(5);

Diese Zeile schickt die App für 5 Sekunden in den Hintergrund und fährt danach mit den Tests des programmierten Skripts fort.

Ändern der App-Orientation
UIAutomation kann auch die Rotation des iPhones simulieren und somit z.B. zwischen Landscape und Portrait Modus hin- und herschalten. Zu beachten ist dabei jedoch, dass die Bewegungssensoren des iPhones dabei nicht angesteuert werden, sondern das Drehen nur simuliert ist. Wenn man also auf Bewegungssensordaten angewiesen ist, reicht diese Methode für einen Test nicht aus.

var target = UIATarget.localTarget();
var app = target.frontMostApp();

target.setDeviceOrientation(UIA_DEVICE_ORIENTATION_LANDSCAPELEFT);
UIALogger.logMessage("Das Telefon befindet sich im " + app.interfaceOrientation() + "Modus.");
target.setDeviceOrientation(UIA_DEVICE_ORIENTATION_PORTRAIT);

Multi-Touch
Bis jetzt haben wir nur einfach Taps auf dem Display ausgeführt. Viele Apps benutzen aber auch komplizierte Multi-Touch gesten zur Bedienung, und sei nur ein Swipe um einen Tabelleneintrag zu löschen. Natürlich bietet UIAutomation auch die Möglichkeit diese komplizierteren Gesten zu simulieren.
Zum Beispiel ist möglich die zwei Finger Zoom-Geste (sog. Pinch-Geste) über folgenden Aufruf durchzuführen:

UIATarget.localTarget().pinchOpenFromToForDuration({x:20, y:200}, {x:300, y:200},2);

Und eine Flick-Geste (zum Beispiel um ein Swip-To-Delete aufzurufen) wird folgendermaßen ausgeführt:

UIATarget.localTarget().flickFromTo({x:160, y:200}, {x:160, y:400});

Delay – Wenn’s mal länger dauert
Ein häufiges Problem beim Einsatz von UIAutomation ist die Einbeziehung von Wartezeiten, typischerweise weil eine Animation beendet werden soll oder gewartet werden muss, bis Daten aus dem Internet heruntergeladen worden sind. Ein einfach Hilfsmittel ist es hier, den Test einfach kurz warten zu lassen:

var target = UIATarget.localTarget();
target.delay(1);

Der Test wartet nun eine Sekunde, bevor er mit der weiteren Ausführung fortfährt.

Zusammenfassung und weiterführende Links

UIAutomation ist ein sehr mächtiges Werkzeug, welches das Testen von iPhone Apps unglaublich vereinfacht und auf eine neue Ebene gehoben hat. Es ist für jede halbwegs komplexe App zu Empfehlen Testfälle zu definieren und die Tests vor jedem Update durchlaufen zu lassen. Mit den Logging und Screenshot Funktionen ist auch eine Fehlersuche relativ intuitiv möglich.

Mehr über das UIAutomation findet man zum Einen natürlich bei Apple:

Aber auch in in einigen empfehlenswerten Blogs:

In Unsere Projekte veröffentlicht | Getaggt: , , , , ,