public class FirstTest extends ActivityInstrumentationTestCase2{
private Solo solo;
public FirstTest() {
// Die zu testende Activity muss angegeben werden
super(Main.class);
}
protected void setUp() throws Exception {
solo = new Solo(getInstrumentation(), getActivity());
}
protected void tearDown() throws Exception {
// Alle Activities schliessen
solo.finishOpenedActivities();
}
public void testFirst() {
// Momentane Activity pruefen
solo.assertCurrentActivity("Nicht in Main", Main.class);
// Auf Element der Liste klicken, in dem "Tabs" steht
solo.clickOnText("Tabs");
solo.assertCurrentActivity("Nicht in Tabs", Tabs.class);
// Simuliert Swipe von rechts nach links und wechselt dadurch die Tabs
solo.drag(300, 50, 300, 300, 1);
solo.drag(300, 50, 300, 300, 1);
boolean checkedBefore = solo.isToggleButtonChecked(0); // Ist der Button gerade "an" oder "aus"?
// Klick auf den ersten ToggleButton
List toggleButtons = solo.getCurrentViews(ToggleButton.class);
solo.clickOnView(toggleButtons.get(0));
// Ueberpruefen, ob sich der Wert geaendert hat
boolean checkedAfter = solo.isToggleButtonChecked(0);
assertTrue("Der ToggleButton wurde nicht angeklickt", checkedBefore!=checkedAfter);
}
}
In dem Test wird in die Tabs-Activity der zu testenden App gewechselt und dort mithilfe der drag-Methode vom ersten zum letzten Tab geswipet. Hierbei ist zu beachten, dass die Methode durch die Angabe von absoluten X- und Y-Werten stark von der Auflösung des Testgeräts abhängig ist. Die Daten sollten also möglichst dynamisch an das Gerät angepasst werden. Danach wird auf den ToggleButton, der sich in dem Tab befindet, geklickt. Die meisten Views können auf drei verschiedene Arten abgerufen, angeklickt oder geändert werden. Es kann direkt das Objekt übergeben werden, es kann der Text, den der View darstellt, angegeben werden oder der View kann über die Angabe des Index identifiziert werden. Damit ist gemeint, der wie vielte View das Objekt ist. Da der Text des ToggleButtons von seinem An/Aus-Status abhängig ist, ist es ungünstig darüber zu navigieren. Jedoch bietet Solo keine Methode an, mit der ToggleButtons über den Index oder das eigentliche Objekt angesprochen werden können. Stattdessen gibt es diese nur für allgemeine Views oder normale Buttons. Da aber nicht unbedingt bekannt ist, der wie vielte Button der gesuchte ToggleButton ist, ist es ungünstig diese Funktion zu nutzen. Darum wird in diesem Fall als erstes nach allen ToggleButtons in der Activity gesucht und danach der erste (und einzige) über die Methode „clickOnView“ angeklickt. Zum Schluss wird überprüft, ob der Button-Wert tatsächlich geändert wurde.
NewContactTest
In der Klasse „NewContactTest“ wird getestet, ob neue Kontakte über das Formular erfolgreich hinzugefügt werden können, bzw. ob fehlerhafte Daten abgelehnt werden.
Da bei allen Testfällen zuerst aus der Main zum Formular navigiert werden muss, wurde dieser Vorgang in eine eigene Funktion ausgelagert:
Eigentlich kann die zu testende Activity direkt in der Test-Klasse angegeben werden, sodass nicht zu dieser hin navigiert werden muss. Jedoch ist es bei diesen Tests nötig, dass die vorherige Activity bekannt ist, da nach dem erfolgreichen Speichern wieder zu dieser zurück gewechselt wird. Darum wird in diesem Test trotzdem die Main als zu testende (bzw. „Startactivity“) genutzt.
private void goToNewContact() {
solo.clickOnText("Tabs"); // Auf Element der Liste klicken, in dem "Tabs" steht
solo.assertCurrentActivity("Nicht in Tabs", Tabs.class);
solo.clickOnButton("neuer Kontakt");
solo.assertCurrentActivity("Nicht in NewContact", NewContact.class);
}
Um zu testen, ob gültige Daten in der Datenbank gespeichert werden, wurde folgender Test geschrieben:
Als erstes wird die Aufnahme von Screenshots begonnen. Mit ihnen kann man den Testverlauf dokumentieren. Sie werden auf der SD-Karte des Geräts, auf dem getestet wird, gespeichert. Darum ist hierfür die Permission „WRITE_EXTERNAL_STORAGE“ in der zu testenden App nötig.
public void testValidData() {
// Aufnahme von Screenshots beginnen
solo.startScreenshotSequence("testNewContact", 50, 250, 100);
goToNewContact(); // Zur NewContact-Activity navigieren
String firstname = "Peter";
String lastname = "Meier2";
String phone = "0123456789";
int year = 1971;
int month = 9;
int day = 22;
// Werte in die EditText-Felder schreiben
solo.enterText(0, firstname);
solo.enterText(1, lastname);
solo.enterText(2, phone);
// Geburtsdatum im DatePicker-Dialog angeben
solo.clickOnButton(0); // Auf den erste Button klicken (Geburtsdatum)
solo.setDatePicker(0, year, month, day); // Das Datum des DatePickers aendern
solo.clickOnButton(0); // Auf den Bestaetigungsbutton des Dialogs klicken
solo.clickOnButton("Kontakt speichern");
solo.assertCurrentActivity("Nicht zurueck in Tabs", Tabs.class);
// Die eingegebenen Daten muessten jetzt in der Liste aller Kontakte angezeigt werden.
// Wenn ein Wert nicht gefunden wird, ist ein Fehler beim Speichern aufgetreten.
String failMessage = "Der Eintrag wurde nicht gespeichert oder wird nicht korrekt auf der Oberflaeche angezeigt.";
assertTrue(failMessage + " (Vorname)", solo.searchText(firstname));
assertTrue(failMessage + " (Nachname)", solo.searchText(lastname));
assertTrue(failMessage + " (Telefonnr)", solo.searchText(phone));
String birthdate = createDateString(year, month + 1, day);
assertTrue(failMessage + " (Geburtsdatum)", solo.searchText(birthdate));
solo.stopScreenshotSequence(); // Aufnahme von Screenshots beenden
}
Danach werden die Testdaten in das Formular eingetragen. Die einfachen Textfelder werden über die Methode „enterText()“ befüllt. Danach wird das Geburtsdatum angegeben, indem auf den Button klickt, der das momentan eingestellte Datum darstellt. Daraufhin öffnet sich ein DatePicker, der über die Methode „setDatePicker()“ konfiguriert wird. Nachdem die Daten ohne Beanstandung abgeschickt wurden, wechselt die App zurück in die Tab-Activity. Hier wird nun überprüft, ob die gerade eingegebenen Daten in der Liste wiedergefunden werden.
Zuletzt wird noch die Screenshot-Aufnahme beendet. Die Screenshots werden im Ordner „Robotium-Screenshots“ gespeichert. Solo bietet keine Funktion, mit der die Screenshots gelöscht werden können (z.B. wenn der Testfall erfolgreich war und die Screenshots deswegen nicht benötigt werden). Bei einem erneuten Testdurchlauf werden sie aber überschrieben.
Zusätzlich zum erfolgreichen Speichern der Daten wird auch getestet, was bei ungültigen Daten passiert. Der Vorname eines Kontakts darf nicht leer sein. Dies wird in folgendem Testfall überprüft:
Da die Daten nicht übernommen werden dürfen, wird auch nicht zurück in die Tab-Activity geleitet. Stattdessen wird ein Toast als Fehlermeldung ausgegeben. Es wird überprüft, ob dieser Toast angezeigt wird, indem die Solo-Methode „waitForText“ genutzt wird.
public void testEmptyFirstname() {
goToNewContact(); // Zur NewContact-Activity navigieren
solo.enterText(0, ""); // Vorname-Feld suchen und Wert eingeben
solo.clickOnButton("Kontakt speichern");
solo.assertCurrentActivity("Nicht mehr in NewContact", NewContact.class);
// Toast-Message ueberpruefen
boolean toastShown = solo.waitForText("Es muss ein Vorname angegeben werden.");
assertTrue("Der Toast wurde nicht angezeigt.", toastShown);
}
SaveTestdataTest
Neben der Datenbank wird auch die Speicherung in den SharedPreferences im Testdaten-Tab getestet.
Dazu wurde im ersten Test überprüft, ob die geänderten Daten wieder zurückgesetzt werden, wenn die Activity beendet wird, ohne dass auf den Speichern-Button gedrückt wurde.
Ähnlich wie beim „NewContactTest“ wurde als erstes zur richtigen Activity navigiert, da auch hier die vorherige Activity benötigt wird.
public void testChangeButDontSave() {
goToTestdata(); // zum Testdaten-Tab gehen
solo.waitForActivity(Main.class, 1500); // Warten bis Views geladen wurden
// Bisherige Daten
List
Danach wird mit der Solo-Methode „waitForActivity“ 1,5 Sekunden darauf gewartet, dass die Views geladen wurden. Wird dies nicht getan, werden bei „getCurrentViews“ keine Objekte gefunden.
Nach der Wartezeit werden die aktuellen Daten ausgelesen und gespeichert, um sie später vergleichen zu können. Als nächstes werden neue Werte angegeben. Diese werden aber nicht mit einem Klick auf den Speichern-Button bestätigt. Stattdessen wird die Activity beendet, indem auf den zurück-Button in der ActionBar geklickt wird. Er hat immer die ID „android.R.id.home“ und kann darüber gefunden werden. Von dort aus wird wieder zum Tab navigiert. Es wird wieder kurz auf die Views gewartet. Dann können die zuvor ausgelesenen Werte mit den tatsächlichen momentanen Werten verglichen werden. Sie sollten gleich sein, da die Änderungen verworfen werden sollten.
Im zweiten Testfall wird das Speichern der Daten überprüft.
Er verläuft ähnlich wie der vorherige Testfall. Es wird zum Tab navigiert, die neuen Daten werden eingegeben und danach über einen Klick auf den „Speichern“-Button bestätigt. Danach wird die Activity beendet und neu gestartet. Hier muss wieder kurz gewartet werden, bevor die angezeigten Daten mit den zuvor eingetragenen Werten verglichen werden.
public void testChangeAndSave() {
goToTestdata(); // zum Testdaten-Tab gehen
// Daten festlegen
int seekbarValue = 2;
String text = "Mein Test-Text";
// Neue Daten eingeben und speichern
solo.setProgressBar(0, seekbarValue);
List
Blackbox-Testing
Zum Blackbox-Testing wird die APK der zu testenden App benötigt. Außerdem muss sie eine Debug-Signatur haben. Ist dies nicht der Fall, muss sie re-signed werden. Dazu kann das Tool Re-Signer von troido genutzt werden (Download hier).
Danach muss die neu signierte App auf dem Gerät bzw. Emulator installiert werden.
Nun kann das Testprojekt erstellt werden. Dabei muss beim Punkt „Select Test Target“ „This project“ ausgewählt werden.
Bei einem neuen Test Case muss folgendes beachtet werden: Da das Testprojekt nicht die Klassen der zu testenden App kennt, können diese nicht mehr direkt angegeben werden. Darum kann ActivityInstrumentationTestCase2 nicht parametisiert werden. Um die App zu starten, muss jedoch im Konstruktor die Main-Activity angegeben werden. Deshalb muss der Klassenname als String erstellt werden, um dann mit der Methode Class.forName(
Die von oben bekannte erste Test-Klasse sieht als Blackbox-Test folgendermaßen aus:
public class BlackboxTest extends ActivityInstrumentationTestCase2 {
private static final String LAUNCHER_ACTIVITY_FULL_CLASSNAME = "de.hsos.androidtests.Main";
private Solo solo;
private static Class launcherActivityClass;
static {
try {
launcherActivityClass = Class.forName(LAUNCHER_ACTIVITY_FULL_CLASSNAME);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
public BlackboxTest() {
// Die zu testende Activity muss angegeben werden
super(launcherActivityClass);
}
protected void setUp() throws Exception {
solo = new Solo(getInstrumentation(), getActivity());
}
protected void tearDown() throws Exception {
// Alle Activities schliessen
solo.finishOpenedActivities();
}
public void testFirst() {
// Momentane Activity pruefen
solo.assertCurrentActivity("Nicht in Main", launcherActivityClass);
// Auf Element der Liste klicken, in dem "Tabs" steht
solo.clickOnText("Tabs");
solo.assertCurrentActivity("Nicht in Tabs", "Tabs");
// Simuliert Swipe von rechts nach links und wechselt dadurch die Tabs
solo.drag(300, 50, 300, 300, 1);
solo.drag(300, 50, 300, 300, 1);
boolean checkedBefore = solo.isToggleButtonChecked(0); // Ist der Button gerade "an" oder "aus"?
// Klick auf den ersten ToggleButton
List