web-dev-qa-db-fra.com

Comment tester une application Android sur plusieurs activités?

Nous construisons une application Android complexe composée de nombreux écrans et flux de travail répartis sur de nombreuses activités. Nos flux de travail sont similaires à ce que vous pouvez voir sur le guichet automatique d'une banque. Par exemple, il existe une Activity pour vous connecter à ce qui passe dans un menu principal Activity qui peut passer à d'autres activités en fonction des choix de l'utilisateur.

Comme nous avons tellement de flux de travail, nous devons créer des tests automatisés couvrant plusieurs activités afin de pouvoir tester un flux de travail de bout en bout. Par exemple, en utilisant l'exemple du guichet automatique, nous voudrions entrer un code PIN valide, vérifier qui nous envoie au menu principal, choisir de retirer de l'argent, vérifier que nous sommes sur l'écran de retrait d'argent, etc., etc., et finalement nous retrouver retour sur le menu principal ou "déconnecté".

Nous avons manipulé les API de test fournies avec Android (par exemple, ActivityInstrumentationTestCase2) et également avec Positron , mais ni l’un ni l’autre ne semble capable de tester au-delà des limites d’un seul Activity, et nous pouvons trouver une utilité dans ces outils pour certains tests unitaires, ils ne répondront pas à nos besoins en matière de scénarios de test couvrant plusieurs activités.

Nous sommes ouverts à un framework xUnit, à des scripts, à des enregistreurs/playbacks à interface graphique, etc. et apprécierions tout conseil.

78
SingleShot

Je suis un peu gêné de répondre à ma propre question sur les primes, mais la voici ...

J'ai cherché haut et bas sur cela et ne peux pas croire qu'il n'y a pas de réponse publiée nulle part. Je suis très proche. Je peux certainement exécuter des tests qui couvrent maintenant plusieurs activités, mais mon implémentation semble présenter des problèmes de synchronisation, car les tests ne passent pas toujours de manière fiable. C’est le seul exemple que je connaisse qui teste avec succès plusieurs activités. Espérons que mon extraction et l'anonymisation de celle-ci n'introduisent pas d'erreurs. Il s'agit d'un test simpliste dans lequel je tape un nom d'utilisateur et un mot de passe dans une activité de connexion, puis j'observe qu'un message de bienvenue approprié est affiché dans une autre activité "bienvenue": 

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!"));
   }
}

Ce code n'est évidemment pas très lisible. En fait, je l'ai extraite dans une simple bibliothèque avec une API de type anglais pour pouvoir dire des choses comme celle-ci:

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

J'ai testé jusqu'à quatre activités en profondeur et je suis convaincu que l'approche fonctionne bien, même si, comme je l'ai dit, il semble y avoir un problème de synchronisation occasionnelle que je n'ai pas complètement résolu. Je suis toujours intéressé à entendre parler de toute autre façon de tester différentes activités.

64
SingleShot

Jetez un coup d'oeil à Robotium 
'' un framework de test open-source créé pour rendre les tests automatiques en boîte noire des applications Android nettement plus rapides et plus faciles que ce qui est possible avec les tests d'instrumentation Android prêts à l'emploi. '

Page d'accueil: http://www.robotium.org/
Source: http://github.com/jayway/robotium

Veuillez noter que le projet Robotium est mis à jour par l'entreprise pour laquelle je travaille}

21
Jonas Söderström

Vous pouvez toujours utiliser Robotium. Il supporte les tests Blackbox comme Selenium mais pour Android. Vous le trouverez sur Robotium.org

8
Renas

Je suis surpris que personne n'ait mentionné certains des principaux outils de test fonctionnel automatisé. Comparés à Robotium, ils ne nécessitent pas l'écriture de code Java.

MONKEYTALK: un outil open source supporté par la société Gorilla Logic. Avantages: fournit l'enregistrement ainsi qu'un langage de script de niveau supérieur plus facile pour les utilisateurs non techniques, et est multi-plateforme (y compris iOS). Compte tenu de ces avantages et exigences, nous avons trouvé que c'était la meilleure solution. Cela permet également à personnalisation _ d'aller au-delà de ce qui peut être fait dans leur langage de script en utilisant Javascript. 

Calabash-Android : un outil open source pour les fonctionnalités de style concombre. Avantages: écrire des fonctionnalités dans le langage Gherkin, qui est Business lisible, langage spécifique au domaine, qui vous permet de décrire le comportement du logiciel sans détailler la façon dont ce comportement est implémenté. Un support similaire mais non exact est disponible pour iOS dans cucumber-ios . Les capacités d'enregistrement ne sont pas aussi bonnes, car elles produisent une sortie binaire.

Quelques autres références:

4
John Lehmann

J'ai créé un outil d'enregistrement et de lecture pour Android et je l'ai mis à disposition sur GitHub . Il est facile à configurer et à utiliser, ne nécessite aucune programmation, s’exécute sur des appareils réels (qui ne doivent pas nécessairement être enracinés) et enregistre automatiquement les captures d’écran lorsqu’il exécute des tests.

3
Brian Kyckelhahn

Tout d'abord, utilisez 'ActivityInstrumentationTestCase2', et non 'InstrumentationTestCase', comme classe de base. J'utilise Robotium et je teste régulièrement plusieurs activités. J'ai trouvé que je devais spécifier l'activité de connexion en tant que type générique (et argument de classe au constructeur).

Le constructeur 'ActivityInstrumentationTestCase2' ignore l'argument du package et ne le requiert pas. Le constructeur qui prend le package est obsolète.

Depuis Javadocs: "ActivityInstrumentationTestCase2 (String pkg, Class activityClass) Ce constructeur est obsolète. Utilisez plutôt ActivityInstrumentationTestCase2 (Class)"

L'utilisation de la classe de base recommandée permet au framework de gérer certaines choses comme le démarrage de votre activité. Cela se fait par l'appel à 'getActivity ()', si nécessaire.

3
Lew Bloch

Vous avez trouvé cela utile avec quelques modifications . Premièrement, getInstrumentation().waitForIdleSync() corrigera l'inflammation que SingleShot parle de De plus, InstrumentationTestCase a une fonction lauchActivity qui peut remplacer les lignes d'activité de démarrage.

3
typingduck

vous pouvez le faire comme ceci pour éviter les temps d’attente des flocons mal synchronisés:

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

Je travaille à peu près sur la même chose, et je vais probablement donner une variante de la réponse acceptée à cette question, mais j’ai rencontré Calculuon( gitHub ) lors de mes recherches de solution.

1
Peter Ajtai

L’approche acceptée fonctionnera-t-elle avec différentes activités de différentes applications, signées par différents certificats? Sinon, Robotium est le meilleur moyen de tester les activités au sein d'une même application.

0
user643154

Cette réponse est basée sur la réponse acceptée mais modifiée pour résoudre le problème de synchronisation qui, pour moi, est devenu cohérent après l’ajout d’une demi-douzaine de tests. @ pajato1 obtient le crédit nécessaire pour résoudre le problème de synchronisation, comme indiqué dans les commentaires de réponse acceptés.

/**
 * 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

Personnellement, je ne l'ai pas utilisé, mais ApplicationTestCase semble correspondre à ce que vous recherchez.

0
Eric

Il existe un autre moyen d'effectuer plusieurs activités à l'aide de ActivityInstrumentation Class .. C'est un scénario d'automatisation normal ... Tout d'abord, concentrez-vous sur l'objet de votre choix, puis envoyez une clé Simple exemple de code 

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

La seule chose à comprendre est que chaque appel d’API nous aidera.

0
sandeep