Wie testest du eine Android-Anwendung über mehrere Aktivitäten hinweg?

80

Wir erstellen eine komplexe Android-Anwendung, die aus vielen Bildschirmen und Workflows besteht, die auf viele Aktivitäten verteilt sind. Unsere Workflows ähneln denen, die Sie möglicherweise am Geldautomaten einer Bank sehen. Beispielsweise gibt es eine ActivityAnmeldung, die zu einem Hauptmenü übergeht, Activitydas je nach Auswahl des Benutzers zu anderen Aktivitäten übergehen kann.

Da wir so viele Workflows haben, müssen wir automatisierte Tests erstellen, die mehrere Aktivitäten umfassen, damit wir einen Workflow von Ende zu Ende testen können. Am Beispiel eines Geldautomaten möchten wir beispielsweise eine gültige PIN eingeben, überprüfen, ob wir zum Hauptmenü gelangen, Bargeld abheben auswählen, sicherstellen, dass wir uns auf dem Bildschirm für Bargeld abheben usw. usw. befinden, und uns schließlich wiederfinden zurück im Hauptmenü oder "abgemeldet".

Wir haben mit den Test-APIs gespielt, die mit Android (z. B. ActivityInstrumentationTestCase2) und auch mit Positron geliefert werden , aber beide scheinen nicht in der Lage zu sein, über die Grenzen eines einzelnen zu testen Activity, und obwohl wir in diesen Tools für einige Unit-Tests einen Nutzen finden können, haben sie gewonnen Erfüllen Sie nicht unsere Anforderungen zum Testen von Szenarien, die sich über mehrere Aktivitäten erstrecken.

Wir sind offen für ein xUnit-Framework, Skripte, GUI-Rekorder / Wiedergaben usw. und würden uns über Ratschläge freuen.

Einzelner Schuss
quelle
2
Ab Android 4.1 gibt es jetzt ein neues Testframework von Android, das das Testen über Aktivitäten und das gesamte System hinweg ermöglicht: developer.android.com/tools/testing/testing_ui.html
Christopher Orr
1
Robotium wird auch diesem Bedarf gerecht und in nur wenigen Zeilen.
Dori

Antworten:

65

Es ist mir ein bisschen unangenehm, meine eigene Kopfgeldfrage zu beantworten, aber hier ist es ...

Ich habe hoch und niedrig gesucht und kann nicht glauben, dass nirgendwo eine Antwort veröffentlicht wird. Ich bin sehr nahe gekommen. Ich kann jetzt definitiv Tests ausführen, die sich über Aktivitäten erstrecken, aber meine Implementierung scheint einige Zeitprobleme zu haben, bei denen die Tests nicht immer zuverlässig bestehen. Dies ist das einzige mir bekannte Beispiel für Tests, die mehrere Aktivitäten erfolgreich durchführen. Hoffentlich hat meine Extraktion und Anonymisierung keine Fehler verursacht. Dies ist ein vereinfachter Test, bei dem ich einen Benutzernamen und ein Kennwort in eine Anmeldeaktivität eingebe und dann beobachte, dass bei einer anderen "Willkommen" -Aktivität eine ordnungsgemäße Begrüßungsnachricht angezeigt wird:

package com.mycompany;

import android.app.*;
import android.content.*;
import android.test.*;
import android.test.suitebuilder.annotation.*;
import android.util.*;
import android.view.*;
import android.widget.*;

import static org.hamcrest.core.Is.*;
import static org.hamcrest.core.IsNull.*;
import static org.hamcrest.core.IsInstanceOf.instanceOf;
import static org.junit.Assert.*;
import static com.mycompany.R.id.*;

public class LoginTests extends InstrumentationTestCase {

   @MediumTest
   public void testAValidUserCanLogIn() {

      Instrumentation instrumentation = getInstrumentation();

      // Register we are interested in the authentication activiry...
      Instrumentation.ActivityMonitor monitor = instrumentation.addMonitor(AuthenticateActivity.class.getName(), null, false);

      // Start the authentication activity as the first activity...
      Intent intent = new Intent(Intent.ACTION_MAIN);
      intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
      intent.setClassName(instrumentation.getTargetContext(), AuthenticateActivity.class.getName());
      instrumentation.startActivitySync(intent);

      // Wait for it to start...
      Activity currentActivity = getInstrumentation().waitForMonitorWithTimeout(monitor, 5);
      assertThat(currentActivity, is(notNullValue()));

      // Type into the username field...
      View currentView = currentActivity.findViewById(username_field);
      assertThat(currentView, is(notNullValue()));
      assertThat(currentView, instanceOf(EditText.class));
      TouchUtils.clickView(this, currentView);
      instrumentation.sendStringSync("MyUsername");

      // Type into the password field...
      currentView = currentActivity.findViewById(password_field);
      assertThat(currentView, is(notNullValue()));
      assertThat(currentView, instanceOf(EditText.class));
      TouchUtils.clickView(this, currentView);
      instrumentation.sendStringSync("MyPassword");

      // Register we are interested in the welcome activity...
      // this has to be done before we do something that will send us to that
      // activity...
      instrumentation.removeMonitor(monitor);
      monitor = instrumentation.addMonitor(WelcomeActivity.class.getName(), null, false);

      // Click the login button...
      currentView = currentActivity.findViewById(login_button;
      assertThat(currentView, is(notNullValue()));
      assertThat(currentView, instanceOf(Button.class));
      TouchUtils.clickView(this, currentView);

      // Wait for the welcome page to start...
      currentActivity = getInstrumentation().waitForMonitorWithTimeout(monitor, 5);
      assertThat(currentActivity, is(notNullValue()));

      // Make sure we are logged in...
      currentView = currentActivity.findViewById(welcome_message);
      assertThat(currentView, is(notNullValue()));
      assertThat(currentView, instanceOf(TextView.class));
      assertThat(((TextView)currentView).getText().toString(), is("Welcome, MyUsername!"));
   }
}

Dieser Code ist offensichtlich nicht sehr lesbar. Ich habe es tatsächlich in eine einfache Bibliothek mit einer englischsprachigen API extrahiert, sodass ich einfach Folgendes sagen kann:

type("myUsername").intoThe(username_field);
click(login_button);

Ich habe bis zu einer Tiefe von ungefähr 4 Aktivitäten getestet und bin zufrieden, dass der Ansatz funktioniert, obwohl es, wie gesagt, gelegentlich ein Timing-Problem gibt, das ich nicht vollständig herausgefunden habe. Ich bin immer noch daran interessiert, von anderen Testmethoden für verschiedene Aktivitäten zu hören.

Einzelner Schuss
quelle
3
Sie können versuchen, die FlakyTest-Annotation hinzuzufügen, um den Test automatisch zu wiederholen, wenn Zeitprobleme dazu führen, dass er fehlschlägt. Eigentlich keine Lösung, aber in manchen Situationen eine praktikable Problemumgehung.
Carl Manaster
Danke, dass du das geschrieben hast! Ich habe für meine Tests etwas mit der Funktionalität von ActivityMonitors gesucht. Ich konnte sie einfach nicht finden.
Peter Ajtai
Soweit ich weiß, kann nichts, was Sie oben getan haben, mitActivityInstrumentationTestCase2
ericn
jede Idee, unter welcher Bedingung 'getInstrumentation (). waitForIdleSync ();' würde in Endlosschleife geraten? In Android 4.4.2_r2, auf dem die Prozessorkarte ausgeführt wird, tritt dieses Problem bei der Ausführung des CTS-Tests auf.
ArunJTS
Ich denke, mein Sohn @ pajato1 hat Ihr Timing-Problem gefunden und behoben. Sein Fix hat mein Problem gelöst. Folgendes sagte er: "Ich habe gerade im Javadoc festgestellt, dass Instrumentation.startActivitySync () blockiert wurde, bis die neue Aktivität fertig war, und sie dann zurückgegeben hat. Es scheint also, dass der Monitor nicht erforderlich war. Das Entfernen hat bewiesen, dass dies korrekt war Die Theorie besagt, dass Monitor dazu geführt hat, dass die von startActivitySync () erstellte Aktivität in einigen Fällen aufgrund einer Rennbedingung neu gestartet wurde. Ich habe einige Zeit damit verbracht, den Android-Quellcode zu lesen, aber als Ursache für die Rennbedingung ist mir nichts aufgefallen. ""
pajato0
22

Werfen Sie einen Blick auf Robotium , ein Open-Source- Testframework , das entwickelt wurde, um das automatische Black-Box-Testen von Android-Anwendungen erheblich schneller und einfacher zu machen, als dies mit sofort einsatzbereiten
Android-Instrumentierungstests möglich ist.

Homepage: http://www.robotium.org/
Quelle: http://github.com/jayway/robotium

Bitte beachten Sie, dass das Robotium-Projekt von der Firma verwaltet wird, für die ich arbeite

Jonas Söderström
quelle
Hallo, gibt es dafür ein Rekorder-Tool? Ich habe viele Websites überprüft und testdroid gefunden, das die Skripte aufzeichnet und ausführt. Leider ist es keine Freeware. Kennen Sie eine Freeware, die den Aufnahmevorgang durchführt?
Thndrkiss
@thndrkiss: Ich kenne kein solches Tool. Wenn Sie Fragen im Robotium-Forum stellen, erhalten Sie wahrscheinlich eine bessere Antwort.
Jonas Söderström
2
Robotium ist ein Lebensretter. Dadurch ist Ihr Test extrem einfach zu schreiben (Sie sprechen im Grunde genommen in einfachem Englisch: Klicken Sie darauf, drücken Sie die Zurück-Taste usw.). Sie können alles testen, müssen jedoch die winzigen Details nicht kennen. Es hat mindestens zwei Hauptvorteile: Sie können Apps testen, deren Quellcode Sie nicht haben, und es basiert auf der Benutzeroberfläche, die es sehr robust macht (Sie ändern Ihre Controller / Modelle viel mehr als Ihre Ansichten ...)
Tiktak
8

Sie könnten immer Robotium verwenden. Es unterstützt Blackbox-Tests wie Selenium, jedoch für Android. Sie finden es auf Robotium.org

Renas
quelle
1
Als ich das letzte Mal überprüft habe, dass Robotium nicht für alle Aktivitäten verwendet werden kann. Wurde das jetzt behoben? stackoverflow.com/questions/3840034/…
user77115
3
Es hat immer über Aktivitäten hinweg funktioniert, solange sie zum selben Prozess gehören.
Renas
4

Ich bin überrascht, dass niemand einige der führenden Tools für automatisierte Funktionstests erwähnt hat . Im Vergleich zu Robotium muss hierfür kein Java-Code geschrieben werden.

MonkeyTalk : Ein Open-Source-Tool, das von der Firma Gorilla Logic unterstützt wird. Vorteile: Bietet Aufzeichnung sowie eine übergeordnete Skriptsprache, die für nicht technische Benutzer einfacher ist, und ist plattformübergreifend (einschließlich iOS). Angesichts dieser Vorteile als Anforderungen haben wir festgestellt, dass dies die beste Lösung ist. Es ermöglicht auch Anpassungen, die über das hinausgehen, was in ihrer Skriptsprache mit Javascript möglich ist.

Calabash-Android : Ein Open-Source-Tool für Funktionen im Gurkenstil. Vorteile: Schreiben Sie Funktionen in der Gherkin-Sprache Business Readable, Domain Specific Language, mit der Sie das Verhalten von Software beschreiben können, ohne genau zu beschreiben, wie dieses Verhalten implementiert wird. Eine ähnliche, aber nicht genaue Unterstützung ist für iOS in Gurken-ios verfügbar. Die Aufnahmefunktionen sind nicht so gut, da sie eine Binärausgabe erzeugen.

Einige andere Referenzen:

  • Hier sind einige zusätzliche Vergleiche zwischen Robotium, Monkeytalk und Calabash. Es erwähnt TestDroid als eine andere Möglichkeit.
  • Dieser Blog erwähnt die oben genannten sowie NativeDriver und Bot-Bot.
John Lehmann
quelle
3

Ich habe ein Aufnahme- und Wiedergabetool für Android erstellt und es auf GitHub verfügbar gemacht . Es ist einfach zu konfigurieren und zu verwenden, erfordert keine Programmierung, läuft auf realen Geräten (die nicht gerootet werden müssen) und speichert automatisch Screenshots, während Tests getestet werden.

Brian Kyckelhahn
quelle
Das sieht vielversprechend aus. Für diejenigen, die den Punkt nicht sehen: Dies scheint eine ziemlich gute Lösung zu sein, um Gesten (Tippen, Ziehen und andere Dinge) zu testen
tiktak
3

Verwenden Sie zunächst 'ActivityInstrumentationTestCase2' und nicht 'InstrumentationTestCase' als Basisklasse. Ich benutze Robotium und teste routinemäßig über mehrere Aktivitäten hinweg. Ich habe festgestellt, dass ich die Anmeldeaktivität als generischen Typ (und Klassenargument für den Konstruktor) angeben muss.

Der Konstruktor 'ActivityInstrumentationTestCase2' ignoriert das Paketargument und benötigt es nicht. Der Konstruktor, der das Paket übernimmt, ist veraltet.

Aus den Javadocs: "ActivityInstrumentationTestCase2 (String pkg, Class activityClass) Dieser Konstruktor ist veraltet. Verwenden Sie stattdessen ActivityInstrumentationTestCase2 (Class)."

Durch die Verwendung der empfohlenen Basisklasse kann das Framework bestimmte Boilerplates verarbeiten, z. B. das Starten Ihrer Aktivität. Dies geschieht bei Bedarf durch den Aufruf von 'getActivity ()'.

Lew Bloch
quelle
3

Fand dies mit ein paar Modifikationen nützlich. Erstens getInstrumentation().waitForIdleSync()heilt die Flakigkeit, von der SingleShot spricht, und InstrumentationTestCasehat auch eine lauchActivityFunktion, die die Startaktivitätslinien ersetzen kann.

typingduck
quelle
2

Sie können dies folgendermaßen tun, um zu vermeiden, dass die Wartezeiten für Flocken nicht synchron sind:

final Button btnLogin = (Button) getActivity().findViewById(R.id.button);
Instrumentation instrumentation = getInstrumentation();

// Register we are interested in the authentication activity...
Instrumentation.ActivityMonitor aMonitor = 
        instrumentation.addMonitor(mynextActivity.class.getName(), null, false);

getInstrumentation().runOnMainSync(new Runnable() {
         public void run() {
             btnLogin.performClick();
         }
     });

getInstrumentation().waitForIdleSync();

//check if we got at least one hit on the new activity
assertTrue(getInstrumentation().checkMonitorHit(aMonitor, 1)); 
j2emanue
quelle
1

Ich arbeite an so ziemlich dem gleichen Thema und werde wahrscheinlich eine Variation der akzeptierten Antwort auf diese Frage vornehmen , aber ich bin bei meiner Suche nach einer Lösung auf Calculuon ( gitHub ) gestoßen .

Peter Ajtai
quelle
0

Ich habe es nicht persönlich verwendet, aber ApplicationTestCase scheint das zu sein, wonach Sie suchen.

Eric
quelle
Leider gibt es keine Beispiele dafür.
SingleShot
Ja, sieht so aus, als hättest du recht ... wurde durch den Namen ausgetrickst. Ich kann das nicht herausfinden. Der beste Ansatz, den ich bisher habe, ist die Verwendung von ActivityUnitTestCase von Positron, um zu überprüfen, ob die nächste Aktivität gestartet wird. Dies hilft Ihnen jedoch nicht dabei, zusammenhängende Geschichten zu erstellen. Alternativ können Sie mit InstrumentationTestCase.launchActivity eine beliebige Anzahl von Aktivitäten starten, aber ich versuche immer noch, das Instrumentierungsmaterial herauszufinden.
Eric
0

Funktioniert der akzeptierte Ansatz mit verschiedenen Aktivitäten aus verschiedenen Anwendungen, die von verschiedenen Zertifikaten signiert sind? Wenn nicht, ist Robotium der beste Weg, um Aktivitäten innerhalb derselben Anwendung zu testen.

user643154
quelle
0

Es gibt eine andere Möglichkeit, die Mehrfachaktivität mithilfe der ActivityInstrumentation-Klasse auszuführen. Dies ist ein normales Automatisierungsszenario. Konzentrieren Sie sich zuerst auf das gewünschte Objekt und senden Sie dann einen Schlüssel, der so einfach wie dieser Beispielcode ist

button.requestFocus();
sendKeys(KeyEvent.KEYCODE_ENTER);

Das einzige, was wir verstehen, ist, dass jeder API-Aufruf hilft.

Sandeep
quelle
0

Diese Antwort basiert auf der akzeptierten Antwort, wurde jedoch geändert, um das Timing-Problem zu lösen, das für mich nach dem Hinzufügen von etwa einem halben Dutzend Tests konsistent wurde. @ pajato1 erhält die Gutschrift für die Lösung des Timing-Problems, wie in den akzeptierten Antwortkommentaren angegeben.

/**
 * Creates a test Activity for a given fully qualified test class name.
 *
 * @param fullyQualifiedClassName The fully qualified name of test activity class.
 *
 * @return The test activity object or null if it could not be located.
 */
protected AbstractTestActivity getTestActivity(final String fullyQualifiedClassName) {
    AbstractTestActivity result = null;

    // Register our interest in the given activity and start it.
    Log.d(TAG, String.format("Running test (%s) with main class: %s.", getName(), fullyQualifiedClassName));
    instrumentation = getInstrumentation();

    Intent intent = new Intent(Intent.ACTION_MAIN);
    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    intent.setClassName(instrumentation.getTargetContext(), fullyQualifiedClassName);
    // Wait for the activity to finish starting
    Activity activity = instrumentation.startActivitySync(intent);

    // Perform basic sanity checks.
    assertTrue("The activity is null!  Aborting.", activity != null);
    String format = "The test activity is of the wrong type (%s).";
    assertTrue(String.format(format, activity.getClass().getName()), activity.getClass().getName().equals(fullyQualifiedClassName));
    result = (AbstractTestActivity) activity;

    return result;
}
pajato0
quelle
0

Probieren Sie das Monkey-Tool aus

Schritt 1:

Öffnen Sie das Android Studio Terminal (Tools-> Open Terminal)

Schritt 2:

Um monkey zu verwenden, öffnen Sie eine Eingabeaufforderung und navigieren Sie einfach in das folgende Verzeichnis.

 export PATH=$PATH:/home/adt-bundle-linux-x86-20140702/sdk/platform-tools

Schritt 3:

Fügen Sie diesen Affenbefehl in das Terminal ein und drücken Sie die Eingabetaste.

Sehen Sie die Magie in Ihrem Emulator.

adb shell monkey -p com.example.yourpackage -v 500

500 - Dies ist die Häufigkeit oder die Anzahl der Ereignisse, die zum Testen gesendet werden sollen.

Sie können diese Anzahl ändern.

Mehr Referenz,

http://www.tutorialspoint.com/android/android_testing.htm

http://androidtesting.blogspot.in/2012/04/android-testing-with-monkey-tool.html

Ranjith Kumar
quelle
Downvoter müssen den Grund für das Downvoting angeben ... dies ist Arbeitscode ... und auch die offizielle Testmethode. Wenn ein Fehler vorliegt, bin ich bereit, meine Antwort zu korrigieren.
Ranjith Kumar