wake-up-neo.com

Wie testen Sie eine Android-Anwendung in mehreren Aktivitäten?

Wir bauen eine komplexe Android-Anwendung, die aus vielen Bildschirmen und Workflows besteht, die über viele Aktivitäten verteilt sind. Unsere Arbeitsabläufe ähneln denen, die Sie auf einem Bankautomaten einer Bank sehen können. Zum Beispiel gibt es eine Activity, um sich anzumelden, um in ein Hauptmenü Activity überzugehen.

Da es so viele Workflows gibt, müssen wir automatisierte Tests erstellen, die sich über mehrere Aktivitäten erstrecken, sodass wir einen Workflow von Ende zu Ende testen können. Anhand des ATM-Beispiels möchten wir beispielsweise eine gültige PIN eingeben, bestätigen, dass wir zum Hauptmenü gelangen, Bargeld abheben, prüfen, ob wir auf dem Bildschirm Bargeld abheben usw. usw. sind, und sich schließlich selbst finden wieder 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 einer einzelnen Activity zu testen Bei einigen Unit-Tests erfüllen sie nicht unsere Anforderungen an Testszenarien, die sich auf mehrere Aktivitäten beziehen.

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

78
SingleShot

Ich finde es etwas unbeholfen, meine eigene Kopfgeldfrage zu beantworten, aber hier ist es ...

Ich habe das alles nach und nach gesucht und kann nicht glauben, dass nirgendwo eine Antwort veröffentlicht wird. Ich bin sehr nahe gekommen. Ich kann jetzt definitiv Tests durchführen, die sich auf Aktivitäten erstrecken, aber bei meiner Implementierung scheint es Probleme mit dem Timing zu geben, bei denen die Tests nicht immer zuverlässig bestehen. Dies ist das einzige Beispiel, das mir Tests dieser Art über mehrere Aktivitäten hinweg erfolgreich bekannt sind. Hoffentlich führte meine Extraktion und Anonymisierung nicht zu Fehlern. Dies ist ein einfacher Test, bei dem ich einen Benutzernamen und ein Kennwort in eine Login-Aktivität eingebe und dann eine richtige Begrüßungsnachricht bei einer anderen "Willkommen" -Aktivität sehe: 

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 gut lesbar. Ich habe es tatsächlich in eine einfache Bibliothek mit einer englischähnlichen API extrahiert, sodass ich einfach folgende Dinge sagen kann:

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

Ich habe bis zu 4 Aktivitäten getestet und bin überzeugt, dass der Ansatz funktioniert, obwohl, wie gesagt, gelegentlich ein Timing-Problem vorliegt, das ich nicht ganz herausgefunden habe. Ich bin immer noch daran interessiert, von anderen Testmethoden zu hören.

64
SingleShot

Werfen Sie einen Blick auf Robotium 
'Ein Open-Source-Testframework, das erstellt wurde, um das automatische Black-Box-Testen von Android-Anwendungen wesentlich schneller und einfacher zu gestalten, als dies mit 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 unterhalten wird, für die ich arbeite

21

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

8
Renas

Ich bin überrascht, dass niemand einige der führenden automatisierten Funktionstest-Tools erwähnt hat. Im Vergleich zu Robotium ist für diese das Schreiben von Java Code nicht erforderlich.

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 Voraussetzung haben wir festgestellt, dass dies die beste Lösung ist. Es erlaubt auch Anpassung darüber hinaus, was in ihrer Skriptsprache mit Javascript getan werden kann.

Calabash-Android : ein Open-Source-Tool für Funktionen im Gurkenstil. Vorteile: Schreiben Sie Funktionen in der Sprache Gherkin, die für Unternehmen lesbar und domänenspezifisch ist. Mit dieser Sprache können Sie das Verhalten von Software beschreiben, ohne genau zu beschreiben, wie dieses Verhalten implementiert wird. Eine ähnliche, aber nicht exakte Unterstützung ist für iOS in cucumber-ios verfügbar. Die Aufnahmefähigkeiten sind nicht so gut, da sie eine binäre Ausgabe erzeugen.

Einige andere Referenzen:

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

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

3

Verwenden Sie zunächst 'ActivityInstrumentationTestCase2' und nicht 'InstrumentationTestCase' als Basisklasse. Ich verwende Robotium und teste routinemäßig mehrere Aktivitäten. Ich habe festgestellt, dass ich die Login-Aktivitä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)"

Wenn Sie die empfohlene Basisklasse verwenden, kann das Framework bestimmte Boilerplates behandeln, z. B. das Starten Ihrer Aktivität. Dies geschieht durch den Aufruf von 'getActivity ()', falls erforderlich.

3
Lew Bloch

Habe dies mit ein paar Modifikationen für nützlich befunden . Erstens wird getInstrumentation().waitForIdleSync() die Flakigkeit heilen, die SingleShot von .__ spricht. InstrumentationTestCase hat eine lauchActivity-Funktion, die die Startaktivitätslinien ersetzen kann.

3
typingduck

sie können es auf diese Weise tun, um die Wartezeiten der Flocken nicht zu synchronisieren:

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)); 
2
j2emanue

Ich arbeite an so ziemlich dasselbe, und ich werde wahrscheinlich eine Variante der akzeptierten Antwort auf diese Frage wählen, aber ich bin auf Calculuon( gitHub ) gestoßen. während meiner Suche nach einer Lösung.

1
Peter Ajtai

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

0
user643154

Diese Antwort basiert auf der akzeptierten Antwort, wurde jedoch modifiziert, um das Timing-Problem zu lösen, das für mich konstant wurde, nachdem ein halbes Dutzend Tests hinzugefügt wurde. @ pajato1 erhält den Kredit für die Lösung des Timing-Problems, wie in den akzeptierten Antwortkommentaren erwähnt.

/**
 * 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;
}
0
pajato0

Ich habe es nicht persönlich verwendet, aber ApplicationTestCase sieht so aus, als wäre es das, wonach Sie suchen.

0
Eric

Es gibt eine andere Möglichkeit, die mehrfachen Aktivitäten mithilfe der ActivityInstrumentation-Klasse ... durchzuführen. Dies ist ein normales Automatisierungsszenario. Ermitteln Sie zunächst den Fokus auf das gewünschte Objekt und senden Sie dann einen SchlüsselBeispielcode 

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

Nur das Verständnis, dass jeder API-Aufruf uns helfen wird.

0
sandeep