Cadre de test Espresso - Guide rapide

En général, les tests d'automatisation mobile sont une tâche difficile et stimulante. La disponibilité d'Android pour différents appareils et plates-formes rend les tests d'automatisation mobile fastidieux. Pour vous faciliter la tâche, Google a relevé le défi et développé le framework Espresso. Il fournit une API très simple, cohérente et flexible pour automatiser et tester les interfaces utilisateur dans une application Android. Les tests Espresso peuvent être écrits à la fois en Java et en Kotlin, un langage de programmation moderne pour développer une application Android.

L'API Espresso est simple et facile à apprendre. Vous pouvez facilement effectuer des tests d'interface utilisateur Android sans la complexité des tests multi-threads. Google Drive, Maps et certaines autres applications utilisent actuellement Espresso.

Caractéristiques de l'espresso

Certaines des principales fonctionnalités prises en charge par Espresso sont les suivantes,

  • API très simple et donc facile à apprendre.

  • Très évolutif et flexible.

  • Fournit un module séparé pour tester le composant Android WebView.

  • Fournit un module séparé pour valider et simuler les intentions Android.

  • Fournit une synchronisation automatique entre votre application et les tests.

Avantages de l'espresso

Voyons maintenant quels sont les avantages d'Espresso.

  • Rétrocompatibilité

  • Facile à installer.

  • Cycle de test très stable.

  • Prend également en charge les activités de test en dehors de l'application.

  • Prend en charge JUnit4

  • Automatisation de l'interface utilisateur adaptée à l'écriture de tests boîte noire.

Dans ce chapitre, voyons comment installer le framework espresso, le configurer pour écrire des tests espresso et l'exécuter dans notre application Android.

Conditions préalables

Espresso est un cadre de test d'interface utilisateur pour tester une application Android développée en langage Java / Kotlin à l'aide du SDK Android. Par conséquent, la seule exigence d'espresso est de développer l'application à l'aide du SDK Android en Java ou Kotlin et il est conseillé d'avoir le dernier Android Studio.

La liste des éléments à configurer correctement avant de commencer à travailler dans le framework espresso est la suivante -

  • Installez le dernier JDK Java et configurez la variable d'environnement JAVA_HOME.

  • Installez la dernière version d'Android Studio (version 3.2 ou supérieure).

  • Installez le dernier SDK Android à l'aide du Gestionnaire de SDK et configurez la variable d'environnement ANDROID_HOME.

  • Installez le dernier outil de construction Gradle et configurez la variable d'environnement GRADLE_HOME.

Configurer le framework EspressoTesting

Initialement, le cadre de test d'espresso est fourni dans le cadre de la bibliothèque de support Android. Plus tard, l'équipe Android fournit une nouvelle bibliothèque Android, AndroidX et déplace le dernier développement du cadre de test d'espresso dans la bibliothèque. Le dernier développement (Android 9.0, niveau d'API 28 ou supérieur) du cadre de test d'espresso sera effectué dans la bibliothèque AndroidX.

Inclure le cadre de test espresso dans un projet est aussi simple que de définir le cadre de test espresso en tant que dépendance dans le fichier gradle de l'application, app / build.gradle. La configuration complète est la suivante,

En utilisant la bibliothèque de support Android,

android {
   defaultConfig {
      testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
   }
}
dependencies {
   testImplementation 'junit:junit:4.12'
   androidTestImplementation 'com.android.support.test:runner:1.0.2'
   androidTestImplementation 'com.android.support.test.espresso:espressocore:3.0.2'
}

En utilisant la bibliothèque AndroidX,

android {
   defaultConfig {
      testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
   }
}
dependencies {
   testImplementation 'junit:junit:4.12'
   androidTestImplementation 'com.androidx.test:runner:1.0.2'
   androidTestImplementation 'com.androidx.espresso:espresso-core:3.0.2'
}

testInstrumentationRunner dans la classe android / defaultConfig définit AndroidJUnitRunner pour exécuter les tests d'instrumentation. La première ligne dans les dépendances inclut le framework de test JUnit , la deuxième ligne dans les dépendances inclut la bibliothèque de test runner pour exécuter les cas de test et enfin la troisième ligne dans les dépendances inclut le framework de test espresso.

Par défaut, le studio Android définit le cadre de test espresso (bibliothèque de support Android) comme une dépendance lors de la création du projet Android et gradle téléchargera la bibliothèque nécessaire à partir du référentiel Maven. Créons une simple application Android Hello world et vérifions si le cadre de test d'espresso est configuré correctement.

Les étapes pour créer une nouvelle application Android sont décrites ci-dessous -

  • Démarrez Android Studio.

  • Sélectionnez Fichier → Nouveau → Nouveau projet.

  • Entrez le nom de l' application (HelloWorldApp) et le domaine de l'entreprise (espressosamples.tutorialspoint.com), puis cliquez sur Suivant .

Pour créer un projet Android,

  • Sélectionnez l'API minimale comme API 15: Android 4.0.3 (IceCreamSandwich), puis cliquez sur Suivant.

Pour cibler les appareils Android,

  • Sélectionnez Activité vide , puis cliquez sur Suivant .

Pour ajouter une activité à Mobile,

  • Saisissez le nom de l'activité principale, puis cliquez sur Terminer .

Pour configurer l'activité,

  • Une fois qu'un nouveau projet est créé, ouvrez le fichier app / build.gradle et vérifiez son contenu. Le contenu du fichier est précisé ci-dessous,

apply plugin: 'com.android.application'
android {
   compileSdkVersion 28
   defaultConfig {
      applicationId "com.tutorialspoint.espressosamples.helloworldapp"
      minSdkVersion 15
      targetSdkVersion 28
      versionCode 1
      versionName "1.0"
      testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
   }
   buildTypes {
      release {
         minifyEnabled false
         proguardFiles getDefaultProguardFile('proguard-android.txt'),    'proguard-rules.pro'
      }
   }
}
dependencies {
   implementation fileTree(dir: 'libs', include: ['*.jar'])
   implementation 'com.android.support:appcompat-v7:28.0.0'
   implementation 'com.android.support.constraint:constraint-layout:1.1.3'
   testImplementation 'junit:junit:4.12'
   androidTestImplementation 'com.android.support.test:runner:1.0.2'
   androidTestImplementation 'com.android.support.test.espresso:espressocore:3.0.2'
}

La dernière ligne spécifie la dépendance du framework de test espresso. Par défaut, la bibliothèque de support Android est configurée. Nous pouvons reconfigurer l'application pour utiliser la bibliothèque AndroidX en cliquant sur RefactorMigrer vers AndroidX dans le menu.

Pour migrer vers Androidx,

  • Maintenant, l' application / build.gradle change comme spécifié ci-dessous,

apply plugin: 'com.android.application'
android {
   compileSdkVersion 28
   defaultConfig {
      applicationId "com.tutorialspoint.espressosamples.helloworldapp"
      minSdkVersion 15
      targetSdkVersion 28
      versionCode 1
      versionName "1.0"
      testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
   }
   buildTypes {
      release {
         minifyEnabled false
         proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
      }
   }
}
dependencies {
   implementation fileTree(dir: 'libs', include: ['*.jar'])
   implementation 'androidx.appcompat:appcompat:1.1.0-alpha01'
   implementation 'androidx.constraintlayout:constraintlayout:2.0.0-alpha3'
   testImplementation 'junit:junit:4.12'
   androidTestImplementation 'androidx.test:runner:1.1.1'
   androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
}

Maintenant, la dernière ligne comprend le cadre de test d'espresso de la bibliothèque AndroidX.

Réglages de l'appareil

Pendant les tests, il est recommandé de désactiver l'animation sur l'appareil Android, qui est utilisée pour les tests. Cela réduira les confusions lors de la vérification des ressources ideling.

Voyons comment désactiver l'animation sur les appareils Android - (Paramètres → Options du développeur),

  • Échelle d'animation de la fenêtre

  • Échelle d'animation de transition

  • Échelle de durée de l'animateur

Si le menu des options du développeur n'est pas disponible dans l' écran Paramètres , cliquez plusieurs fois sur Numéro de version disponible dans l' option À propos du téléphone . Cela active le menu Options du développeur .

Dans ce chapitre, voyons comment exécuter des tests avec Android Studio.

Chaque application Android a deux types de tests -

  • Tests fonctionnels / unitaires

  • Tests d'instrumentation

Le test fonctionnel n'a pas besoin que l'application Android réelle soit installée et lancée dans l'appareil ou l'émulateur et teste la fonctionnalité. Il peut être lancé dans la console elle-même sans appeler l'application réelle. Cependant, les tests d'instrumentation nécessitent que l'application réelle soit lancée pour tester les fonctionnalités telles que l'interface utilisateur et l'interaction utilisateur. Par défaut, les tests unitaires sont écrits ensrc/test/java/ dossier et les tests d'instrumentation sont écrits dans src/androidTest/java/dossier. Android Studio fournit le menu contextuel Exécuter pour les classes de test afin d'exécuter le test écrit dans les classes de test sélectionnées. Par défaut, une application Android a deux classes - ExampleUnitTest dans le dossier src / test et ExampleInstrumentedTest dans le dossier src / androidTest .

Pour exécuter le test unitaire par défaut, sélectionnez ExampleUnitTest dans le studio Android, cliquez dessus avec le bouton droit de la souris, puis cliquez sur Exécuter 'ExampleUnitTest' comme indiqué ci-dessous,

Exécuter le test unitaire

Cela exécutera le test unitaire et affichera le résultat dans la console comme dans la capture d'écran suivante -

Succès du test unitaire

Pour exécuter le test d'instrumentation par défaut, sélectionnez ExampleInstrumentationTest dans le studio Android, cliquez dessus avec le bouton droit de la souris, puis cliquez sur Exécuter 'ExampleInstrumentationTest' comme indiqué ci-dessous,

Exécuter le test d'instrumentation

Cela exécutera le test unitaire en lançant l'application dans l'appareil ou dans l'émulateur et affichera le résultat dans la console comme dans la capture d'écran suivante -

Le test d'instrumentation a réussi.

Dans ce chapitre, laissez-nous comprendre les bases de JUnit , le framework de test unitaire populaire développé par la communauté Java sur lequel le framework de test d'espresso est construit.

JUnit est le standard de facto pour les tests unitaires d'une application Java. Même s'il est populaire pour les tests unitaires, il offre également un support complet et une disposition pour les tests d'instrumentation. La bibliothèque de tests Espresso étend les classes JUnit nécessaires pour prendre en charge les tests d'instrumentation basés sur Android.

Rédiger un test unitaire simple

Créons une classe Java, Computation (Computation.java) et écrivons une opération mathématique simple, Summation et Multiplication . Ensuite, nous écrirons des cas de test en utilisant JUnit et le vérifierons en exécutant les cas de test.

  • Démarrez Android Studio.

  • Ouvrez HelloWorldApp créé dans le chapitre précédent.

  • Créez un fichier, Computation.java dans app / src / main / java / com / tutorialspoint / espressosamples / helloworldapp / et écrivez deux fonctions - Sum et Multiply comme spécifié ci-dessous,

package com.tutorialspoint.espressosamples.helloworldapp;
public class Computation {
   public Computation() {}
   public int Sum(int a, int b) {
      return a + b;
   }
   public int Multiply(int a, int b) {
      return a * b;
   }
}
  • Créez un fichier, ComputationUnitTest.java dans app / src / test / java / com / tutorialspoint / espressosamples / helloworldapp et écrivez des cas de test unitaires pour tester les fonctionnalités Sum et Multiply comme spécifié ci-dessous

package com.tutorialspoint.espressosamples.helloworldapp;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
public class ComputationUnitTest {
   @Test
   public void sum_isCorrect() {
      Computation computation = new Computation();
      assertEquals(4, computation.Sum(2,2));
   }
   @Test
   public void multiply_isCorrect() {
      Computation computation = new Computation();
      assertEquals(4, computation.Multiply(2,2));
   }
}

Ici, nous avons utilisé deux nouveaux termes - @Test et assertEquals . En général, JUnit utilise l'annotation Java pour identifier les cas de test dans une classe et des informations sur la façon d'exécuter les cas de test. @Test est l'une de ces annotations Java, qui spécifie que la fonction particulière est un cas de test junit. assertEquals est une fonction pour affirmer que le premier argument (valeur attendue) et le second argument (valeur calculée) sont égaux et identiques. JUnit fournit un certain nombre de méthodes d'assertion pour différents scénarios de test.

  • Maintenant, exécutez ComputationUnitTest dans le studio Android en cliquant avec le bouton droit de la souris sur la classe et en appelant l' option Exécuter 'ComputationUnitTest' comme expliqué dans le chapitre précédent. Cela exécutera les cas de test unitaires et signalera le succès.

Le résultat du test unitaire de calcul est comme indiqué ci-dessous -

Annotations

Le framework JUnit utilise largement l'annotation . Certaines des annotations importantes sont les suivantes -

  • @Test

  • @Before

  • @After

  • @BeforeClass

  • @AfterClass

  • @Rule

Annotation @Test

@Test est l'annotation très importante du framework JUnit . @Test est utilisé pour différencier une méthode normale de la méthode de cas de test. Une fois qu'une méthode est décorée avec l' annotation @Test , cette méthode particulière est considérée comme un cas de test et sera exécutée par JUnit Runner . JUnit Runner est une classe spéciale, qui est utilisée pour rechercher et exécuter les cas de test JUnit disponibles dans les classes java. Pour l'instant, nous utilisons l' option intégrée d'Android Studio pour exécuter les tests unitaires (qui à leur tour exécutent JUnit Runner ). Un exemple de code est le suivant,

package com.tutorialspoint.espressosamples.helloworldapp;
import org.junit.Test;
import static org.junit.Assert.assertEquals;

public class ComputationUnitTest {
   @Test
   public void multiply_isCorrect() {
      Computation computation = new Computation();
      assertEquals(4, computation.Multiply(2,2));
   }
}

@Avant

L' annotation @Before est utilisée pour faire référence à une méthode, qui doit être appelée avant d'exécuter une méthode de test disponible dans une classe de test particulière. Par exemple, dans notre exemple, l' objet Computation peut être créé dans une méthode distincte et annoté avec @Before afin qu'il s'exécute avant les cas de test sum_isCorrect et multiply_isCorrect . Le code complet est le suivant,

package com.tutorialspoint.espressosamples.helloworldapp;
import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.assertEquals;

public class ComputationUnitTest {
   Computation computation = null;
   @Before
   public void CreateComputationObject() {
      this.computation = new Computation();
   }
   @Test
   public void sum_isCorrect() {
      assertEquals(4, this.computation.Sum(2,2));
   }
   @Test
   public void multiply_isCorrect() {
      assertEquals(4, this.computation.Multiply(2,2));
   }
}

@Après

@After est similaire à @Before , mais la méthode annotée avec @After sera appelée ou exécutée après l'exécution de chaque scénario de test. L'exemple de code est le suivant,

package com.tutorialspoint.espressosamples.helloworldapp;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.assertEquals;

public class ComputationUnitTest {
   Computation computation = null;
   @Before
   public void CreateComputationObject() {
      this.computation = new Computation();
   }
   @After
   public void DestroyComputationObject() {
      this.computation = null;
   }
   @Test
   public void sum_isCorrect() {
      assertEquals(4, this.computation.Sum(2,2));
   }
   @Test
   public void multiply_isCorrect() {
      assertEquals(4, this.computation.Multiply(2,2));
   }
}

@Avant les cours

@BeforeClass est similaire à @Before , mais la méthode annotée avec @BeforeClass ne sera appelée ou exécutée qu'une seule fois avant d'exécuter tous les cas de test dans une classe particulière. Il est utile de créer un objet gourmand en ressources comme un objet de connexion à la base de données. Cela réduira le temps d'exécution d'une collection de cas de test. Cette méthode doit être statique pour fonctionner correctement. Dans notre exemple, nous pouvons créer l'objet de calcul une fois avant d'exécuter tous les cas de test comme spécifié ci-dessous,

package com.tutorialspoint.espressosamples.helloworldapp;
import org.junit.BeforeClass;
import org.junit.Test;
import static org.junit.Assert.assertEquals;

public class ComputationUnitTest {
   private static Computation computation = null;
   @BeforeClass
   public static void CreateComputationObject() {
      computation = new Computation();
   }
   @Test
   public void sum_isCorrect() {
      assertEquals(4, computation.Sum(2,2));
   }
   @Test
   public void multiply_isCorrect() {
      assertEquals(4, computation.Multiply(2,2));
   }
}

@Après les cours

@AfterClass est similaire à @BeforeClass , mais la méthode annotée avec @AfterClass ne sera appelée ou exécutée qu'une seule fois après l'exécution de tous les cas de test d'une classe particulière. Cette méthode doit également être statique pour fonctionner correctement. L'exemple de code est le suivant -

package com.tutorialspoint.espressosamples.helloworldapp;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import static org.junit.Assert.assertEquals;

public class ComputationUnitTest {
   private static Computation computation = null;
   @BeforeClass
   public static void CreateComputationObject() {
      computation = new Computation();
   }
   @AfterClass
   public static void DestroyComputationObject() {
      computation = null;
   }
   @Test
   public void sum_isCorrect() {
      assertEquals(4, computation.Sum(2,2));
   }
   @Test
   public void multiply_isCorrect() {
      assertEquals(4, computation.Multiply(2,2));
   }
}

@Règle

L' annotation @Rule est l'un des points forts de JUnit . Il est utilisé pour ajouter un comportement aux cas de test. Nous ne pouvons annoter que les champs de type TestRule . Il fournit en fait un ensemble de fonctionnalités fournies par les annotations @Before et @After, mais de manière efficace et réutilisable. Par exemple, nous pouvons avoir besoin d'un dossier temporaire pour stocker certaines données pendant un cas de test. Normalement, nous devons créer un dossier temporaire avant d'exécuter le scénario de test (en utilisant l'annotation @Before ou @BeforeClass) et le détruire après l'exécution du scénario de test (en utilisant l'annotation @After ou @AfterClass). Au lieu de cela, nous pouvons utiliser la classe TemporaryFolder (de type TestRule ) fournie par le framework JUnit pour créer un dossier temporaire pour tous nos cas de test et le dossier temporaire sera supprimé au fur et à mesure de l'exécution du scénario de test. Nous devons créer une nouvelle variable de type TemporaryFolder et annoter avec @Rule comme spécifié ci-dessous,

package com.tutorialspoint.espressosamples.helloworldapp;

import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import java.io.File;
import java.io.IOException;
import static junit.framework.TestCase.assertTrue;
import static org.junit.Assert.assertEquals;

public class ComputationUnitTest {
   private static Computation computation = null;
   @Rule
   public TemporaryFolder folder = new TemporaryFolder();
   @Test
   public void file_isCreated() throws IOException {
      folder.newFolder("MyTestFolder");
      File testFile = folder.newFile("MyTestFile.txt");
      assertTrue(testFile.exists());
   }
   @BeforeClass
   public static void CreateComputationObject() {
      computation = new Computation();
   }
   @AfterClass
   public static void DestroyComputationObject() {
      computation = null;
   }
   @Test
   public void sum_isCorrect() {
      assertEquals(4, computation.Sum(2,2));
   }
   @Test
   public void multiply_isCorrect() {
      assertEquals(4, computation.Multiply(2,2));
   }
}

Ordre d'exécution

Dans JUnit , les méthodes annotées avec des annotations différentes seront exécutées dans un ordre spécifique comme indiqué ci-dessous,

  • @BeforeClass

  • @Rule

  • @Before

  • @Test

  • @After

  • @AfterClass

Affirmation

L'assertion est un moyen de vérifier si la valeur attendue du scénario de test correspond à la valeur réelle du résultat du scénario de test. JUnit fournit une assertion pour différents scénarios; quelques affirmations importantes sont énumérées ci-dessous -

  • fail() - Pour faire échouer explicitement un cas de test.

  • assertTrue(boolean test_condition) - Vérifie que la condition test_condition est vraie

  • assertFalse(boolean test_condition) - Vérifie que la condition test_condition est fausse

  • assertEquals(expected, actual) - Vérifie que les deux valeurs sont égales

  • assertNull(object) - Vérifie que l'objet est nul

  • assertNotNull(object) - Vérifie que l'objet n'est pas nul

  • assertSame(expected, actual) - Vérifie que les deux font référence au même objet.

  • assertNotSame(expected, actual) - Vérifie que les deux font référence à un objet différent.

Dans ce chapitre, apprenons les termes du cadre de test espresso, comment écrire un cas de test espresso simple et le flux de travail complet ou l'architecture du cadre de test espresso.

Aperçu

Espresso fournit un grand nombre de classes pour tester l'interface utilisateur et l'interaction utilisateur d'une application Android. Ils peuvent être regroupés en cinq catégories comme spécifié ci-dessous -

Coureur JUnit

Le framework de test Android fournit un runner, AndroidJUnitRunner pour exécuter les cas de test espresso écrits dans les cas de test de style JUnit3 et JUnit4. Il est spécifique à l'application Android et gère de manière transparente le chargement des cas de test espresso et de l'application testée à la fois dans l'appareil réel ou dans l'émulateur, exécute les cas de test et rapporte le résultat des cas de test. Pour utiliser AndroidJUnitRunner dans le cas de test, nous devons annoter la classe de test à l'aide de l'annotation @RunWith, puis transmettre l'argument AndroidJUnitRunner comme spécifié ci-dessous -

@RunWith(AndroidJUnit4.class)
   public class ExampleInstrumentedTest {
}

Règles JUnit

Le framework de test Android fournit une règle, ActivityTestRule pour lancer une activité Android avant d'exécuter les cas de test. Il lance l'activité avant chaque méthode annotée avec @ Test` et @Before. Il mettra fin à l'activité après la méthode annotée avec @After. Un exemple de code est le suivant,

@Rule
public ActivityTestRule<MainActivity> mActivityTestRule = new ActivityTestRule<>(MainActivity.class);

Ici, MainActivity est l'activité à lancer avant d'exécuter un scénario de test et à détruire après l'exécution du scénario de test particulier.

ViewMatchers

Espresso fournit un grand nombre de classes de correspondance de vues (dans le package androidx.test.espresso.matcher.ViewMatchers ) pour faire correspondre et trouver des éléments / vues d'interface utilisateur dans la hiérarchie de vues d'un écran d'activité Android. La méthode de Espresso OnView prend un seul argument de type Matcher (Voir matchers), trouve le point de vue de l' interface utilisateur correspondant et retourne correspondant ViewInteraction objet. L' objet ViewInteraction retourné par la méthode onView peut être utilisé pour invoquer des actions telles que cliquer sur la vue correspondante ou peut être utilisé pour affirmer la vue correspondante. Un exemple de code pour rechercher la vue avec le texte "Hello World!" est comme suit,

ViewInteraction viewInteraction = Espresso.onView(withText("Hello World!"));

Ici, withText est un matcher, qui peut être utilisé pour faire correspondre la vue de l'interface utilisateur avec le texte "Hello World!"

ViewActions

Espresso fournit un grand nombre de classes d'action de vue (dans androidx.test.espresso.action.ViewActions) pour invoquer l'action différente sur la vue sélectionnée / correspondante. Une fois que onView correspond et renvoie l' objet ViewInteraction , toute action peut être appelée en appelant la méthode «perform» de l' objet ViewInteraction et en lui transmettant les actions de vue appropriées. Un exemple de code pour cliquer sur la vue correspondante est le suivant,

ViewInteraction viewInteraction = Espresso.onView(withText("Hello World!"));
viewInteraction.perform(click());

Ici, l'action de clic de la vue correspondante sera appelée.

AfficherAssertions

Semblable aux correspondances de vue et aux actions de vue, Espresso fournit un grand nombre d'assertions de vue (dans le package androidx.test.espresso.assertion.ViewAssertions ) pour affirmer que la vue correspondante est ce que nous attendions. Une fois que onView correspond et renvoie l' objet ViewInteraction , toute assertion peut être vérifiée à l'aide de la méthode check de ViewInteraction en la transmettant avec l'assertion de vue appropriée. Un exemple de code pour affirmer que la vue correspondante est la suivante,

ViewInteraction viewInteraction = Espresso.onView(withText("Hello World!"));
viewInteraction.check(matches(withId(R.id.text_view)));

Ici, les correspondances acceptent la correspondance de vue et retournent l'assertion de vue, qui peut être vérifiée par la méthode de vérification de ViewInteraction .

Flux de travail du cadre de test Espresso

Comprenons comment fonctionne le cadre de test espresso et comment il offre des options pour effectuer tout type d'interaction utilisateur de manière simple et flexible. Le flux de travail d'un cas de test expresso est décrit ci-dessous

  • Comme nous l'avons appris précédemment, Android JUnit runner, AndroidJUnit4 exécutera les cas de test Android. Les cas de test d'espresso doivent être marqués avec @RunWith (AndroidJUnut.class) . Tout d'abord, AndroidJUnit4 préparera l'environnement pour exécuter les cas de test. Il démarre le périphérique Android connecté ou l'émulateur, installe l'application et s'assure que l'application à tester est prête. Il exécutera les cas de test et rapportera les résultats.

  • Espresso a besoin d'au moins une seule règle JUnit de type ActivityTestRule pour spécifier l'activité. Le coureur Android JUnit démarrera l'activité à lancer à l'aide d' ActivityTestRule .

  • Chaque scénario de test nécessite au minimum un appel de méthode onView ou onDate (utilisé pour rechercher des vues basées sur des données comme AdapterView ) pour correspondre et trouver la vue souhaitée. onView ou onData renvoie l' objet ViewInteraction .

  • Une fois l' objet ViewInteraction renvoyé, nous pouvons soit appeler une action de la vue sélectionnée, soit vérifier la vue pour notre vue attendue à l'aide d'une assertion.

  • L'action peut être appelée à l'aide de la méthode perform de l' objet ViewInteraction en passant l'une des actions d'affichage disponibles.

  • L'assertion peut être appelée à l'aide de la méthode check de l' objet ViewInteraction en passant l'une des assertions de vue disponibles.

La représentation schématique du workflow est la suivante,

Exemple d'assertion de vue

Écrivons un cas de test simple pour trouver la vue texte ayant "Hello World!" texte dans notre application «HelloWorldApp», puis affirmez-le en utilisant l'assertion de vue. Le code complet est le suivant,

package com.tutorialspoint.espressosamples.helloworldapp;

import android.content.Context;
import androidx.test.InstrumentationRegistry;
import androidx.test.rule.ActivityTestRule;
import androidx.test.runner.AndroidJUnit4;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import static androidx.test.espresso.Espresso.onView;
import static androidx.test.espresso.matcher.ViewMatchers.withText;;
import static androidx.test.espresso.assertion.ViewAssertions.matches;
import static org.junit.Assert.*;
/**
   * Instrumented test, which will execute on an Android device.
   *
   * @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
   @Rule
   public ActivityTestRule<MainActivity> mActivityTestRule = new ActivityTestRule<>(MainActivity.class);
   @Test
   public void view_isCorrect() {
      onView(withText("Hello World!")).check(matches(isDisplayed()));
   }
   @Test
   public void useAppContext() {
      // Context of the app under test.
      Context appContext = InstrumentationRegistry.getTargetContext();
      assertEquals("com.tutorialspoint.espressosamples.helloworldapp", appContext.getPackageName());
   }
}

Ici, nous avons utilisé des correspondances de vue withText pour trouver la vue texte ayant "Hello World!" assertion de texte et de correspondance pour affirmer que la vue de texte est correctement affichée. Une fois le scénario de test appelé dans Android Studio, il exécutera le scénario de test et signalera le message de réussite comme ci-dessous.

view_isCorrect scénario de test

Le framework Espresso fournit de nombreux correspondants de vue. Le but de la correspondance est de faire correspondre une vue en utilisant différents attributs de la vue tels que l'ID, le texte et la disponibilité de la vue enfant. Chaque correspondance correspond à un attribut particulier de la vue et s'applique à un type de vue particulier. Par exemple, la correspondance withId correspond à la propriété Id de la vue et s'applique à toutes les vues, tandis que la correspondance withText correspond à la propriété Text de la vue et s'applique uniquement à TextView .

Dans ce chapitre, apprenons les différents matchers fournis par le framework de test espresso ainsi que la bibliothèque Hamcrest sur laquelle les matchers espresso sont construits.

Bibliothèque Hamcrest

La bibliothèque Hamcrest est une bibliothèque importante dans le cadre du cadre de test d'espresso. Hamcrest est lui-même un framework pour écrire des objets matcher. Le framework Espresso utilise largement la bibliothèque Hamcrest et l'étend chaque fois que nécessaire pour fournir des matchers simples et extensibles.

Hamcrest fournit une fonction simple assertThat et une collection de matchers pour affirmer n'importe quel objet. assert Cela a trois arguments et ils sont comme indiqué ci-dessous -

  • String (description du test, facultatif)

  • Objet (réel)

  • Matcher (attendu)

Écrivons un exemple simple pour tester si un objet liste a une valeur attendue.

import static org.hamcrest.Matchers.hasItem;
import static org.hamcrest.MatcherAssert.assertThat;
@Test
public void list_hasValue() {
   ArrayList<String> list = new ArrayList<String>();
   list.add("John");
   assertThat("Is list has John?", list, hasItem("John"));
}

Ici, hasItem renvoie un matcher, qui vérifie si la liste réelle a spécifié une valeur comme l'un des éléments.

Hamcrest a beaucoup de matchers intégrés et également des options pour créer de nouveaux matchers. Voici quelques-uns des apparieurs intégrés importants utiles dans le cadre de test d'espresso:

n'importe quoi - toujours assortis

Matchers basés sur la logique

  • allOf - n'acceptez un nombre quelconque de matchers et de matchs que si tous les matchers réussissent.

  • anyOf - accepter n'importe quel nombre de matchers et de matchs si un match a réussi.

  • not - n'acceptez qu'un matcher et ne correspond que si le matcher a échoué et vice versa.

Correspondances basées sur du texte

  • equalToIgnoringCase - utilisé pour tester si l'entrée réelle est égale à la chaîne attendue en ignorant la casse.

  • equalToIgnoringWhiteSpace - utilisé pour tester si l'entrée réelle est égale à la chaîne spécifiée en ignorant la casse et les espaces.

  • containsString - utilisé pour tester si l'entrée réelle contient la chaîne spécifiée.

  • endsWith - utilisé pour tester si l'entrée réelle commence par la chaîne spécifiée.

  • startsWith - utilisé pour tester si l'entrée se termine par la chaîne spécifiée.

Matchers basés sur le nombre

  • closeTo - utilisé pour tester si l'entrée réelle est proche du nombre attendu.

  • greaterThan - utilisé pour tester si l'entrée réelle est supérieure au nombre attendu.

  • greaterThanOrEqualTo - utilisé pour tester si l'entrée réelle est supérieure ou égale au nombre attendu.

  • lessThan - utilisé pour tester si l'entrée réelle est inférieure au nombre attendu.

  • lessThanOrEqualTo - utilisé pour tester si l'entrée réelle est inférieure ou égale au nombre attendu.

Matchers basés sur des objets

  • equalTo - utilisé pour tester si l'entrée réelle est égale à l'objet attendu

  • hasToString - utilisé pour tester si l'entrée réelle a la méthode toString.

  • instanceOf - utilisé pour tester si l'entrée réelle est l'instance de la classe attendue.

  • isCompatibleType - utilisé pour tester si l'entrée réelle est compatible avec le type attendu.

  • notNullValue - utilisé pour tester si l'entrée réelle n'est pas nulle.

  • sameInstance - utilisé pour tester si l'entrée réelle et attendue sont de la même instance.

  • hasProperty - utilisé pour tester si l'entrée réelle a la propriété attendue

is - Sucre ou raccourci pour égal

Matchers

Espresso fournit la méthode onView () pour faire correspondre et trouver les vues. Il accepte les correspondances de vue et renvoie l'objet ViewInteraction pour interagir avec la vue correspondante. La liste fréquemment utilisée des correspondances de vue est décrite ci-dessous -

withId ()

withId () accepte un argument de type int et l'argument fait référence à l'id de la vue. Il renvoie un matcher, qui correspond à la vue en utilisant l'id de la vue. L'exemple de code est le suivant,

onView(withId(R.id.testView))

withText ()

withText () accepte un argument de type string et l'argument fait référence à la valeur de la propriété text de la vue. Il renvoie une correspondance, qui correspond à la vue en utilisant la valeur de texte de la vue. Il s'applique uniquement à TextView . L'exemple de code est le suivant,

onView(withText("Hello World!"))

withContentDescription ()

withContentDescription () accepte un argument de type string et l'argument fait référence à la valeur de la propriété de description de contenu de la vue. Il renvoie un matcher, qui correspond à la vue en utilisant la description de la vue. L'exemple de code est le suivant,

onView(withContentDescription("blah"))

Nous pouvons également transmettre l'ID de ressource de la valeur de texte au lieu du texte lui-même.

onView(withContentDescription(R.id.res_id_blah))

hasContentDescription ()

hasContentDescription () n'a pas d'argument. Il renvoie un matcher, qui correspond à la vue qui a une description de contenu. L'exemple de code est le suivant,

onView(allOf(withId(R.id.my_view_id), hasContentDescription()))

withTagKey ()

withTagKey () accepte un argument de type string et l'argument fait référence à la clé de balise de la vue. Il renvoie un matcher, qui correspond à la vue en utilisant sa clé de balise. L'exemple de code est le suivant,

onView(withTagKey("blah"))

Nous pouvons également transmettre l'ID de ressource du nom de la balise au lieu du nom de la balise lui-même.

onView(withTagKey(R.id.res_id_blah))

withTagValue ()

withTagValue () accepte un argument de type Matcher <Object> et l'argument fait référence à la valeur de la balise de la vue. Il renvoie un matcher, qui correspond à la vue en utilisant sa valeur de balise. L'exemple de code est le suivant,

onView(withTagValue(is((Object) "blah")))

Ici, c'est Hamcrest matcher.

withClassName ()

withClassName () accepte un argument de type Matcher <String> et l'argument fait référence à la valeur du nom de classe de la vue. Il renvoie un matcher, qui correspond à la vue en utilisant son nom de classe. L'exemple de code est le suivant,

onView(withClassName(endsWith("EditText")))

Ici, se termine avec Hamcrest matcher et retourne Matcher <String>

withHint ()

withHint () accepte un argument de type Matcher <String> et l'argument fait référence à la valeur d'indication de la vue. Il renvoie une correspondance, qui correspond à la vue en utilisant l'indication de la vue. L'exemple de code est le suivant,

onView(withClassName(endsWith("Enter name")))

withInputType ()

withInputType () accepte un argument de type int et l'argument fait référence au type d'entrée de la vue. Il renvoie un matcher, qui correspond à la vue en utilisant son type d'entrée. L'exemple de code est le suivant,

onView(withInputType(TYPE_CLASS_DATETIME))

Ici, TYPE_CLASS_DATETIME fait référence aux dates et heures de prise en charge de la vue de modification.

withResourceName ()

withResourceName () accepte un argument de type Matcher <String> et l'argument fait référence à la valeur du nom de classe de la vue. Il renvoie un matcher, qui correspond à la vue en utilisant le nom de ressource de la vue. L'exemple de code est le suivant,

onView(withResourceName(endsWith("res_name")))

Il accepte également les arguments de chaîne. L'exemple de code est le suivant,

onView(withResourceName("my_res_name"))

avecAlpha ()

withAlpha () accepte un argument de type float et l'argument fait référence à la valeur alpha de la vue. Il renvoie une correspondance, qui correspond à la vue en utilisant la valeur alpha de la vue. L'exemple de code est le suivant,

onView(withAlpha(0.8))

withEffectiveVisibility ()

withEffectiveVisibility () accepte un argument de type ViewMatchers.Visibility et l'argument fait référence à la visibilité effective de la vue. Il renvoie une correspondance, qui correspond à la vue en utilisant la visibilité de la vue. L'exemple de code est le suivant,

onView(withEffectiveVisibility(withEffectiveVisibility.INVISIBLE))

withSpinnerText ()

withSpinnerText () accepte un argument de type Matcher <String> et l'argument fait référence à la valeur de la vue actuellement sélectionnée du Spinner. Il renvoie un matcher, qui correspond au spinner en fonction de la valeur toString de l'élément sélectionné. L'exemple de code est le suivant,

onView(withSpinnerText(endsWith("USA")))

Il accepte également l'argument de chaîne ou l'ID de ressource de la chaîne. L'exemple de code est le suivant,

onView(withResourceName("USA"))
onView(withResourceName(R.string.res_usa))

avecSubstring ()

withSubString () est similaire à withText () sauf qu'il permet de tester la sous-chaîne de la valeur de texte de la vue.

onView(withSubString("Hello"))

hasLinks ()

hasLinks () n'a pas d'arguments et renvoie un matcher, qui correspond à la vue ayant des liens. Il s'applique uniquement à TextView. L'exemple de code est le suivant,

onView(allOf(withSubString("Hello"), hasLinks()))

Ici, allOf est un matcher Hamcrest. allOf renvoie un matcher, qui correspond à tous les correspondants passés et ici, il est utilisé pour faire correspondre une vue et vérifier si la vue a des liens dans sa valeur de texte.

hasTextColor ()

hasTextColor () accepte un seul argument de type int et l'argument fait référence à l'ID de ressource de la couleur. Il renvoie un matcher, qui correspond à TextView en fonction de sa couleur. Il s'applique uniquement à TextView . L'exemple de code est le suivant,

onView(allOf(withSubString("Hello"), hasTextColor(R.color.Red)))

hasEllipsizedText ()

hasEllipsizedText () n'a pas d'argument. Il renvoie un matcher, qui correspond au TextView qui a un texte long et soit ellipsé (premier .. dix .. dernier) soit coupé (premier…). L'exemple de code est le suivant,

onView(allOf(withId(R.id.my_text_view_id), hasEllipsizedText()))

hasMultilineText ()

hasMultilineText () n'a aucun argument. Il retourne un matcher, qui correspond au TextView contenant un texte sur plusieurs lignes. L'exemple de code est le suivant,

onView(allOf(withId(R.id.my_test_view_id), hasMultilineText()))

hasBackground ()

hasBackground () accepte un seul argument de type int et l'argument fait référence à l'ID de ressource de la ressource d'arrière-plan. Il renvoie un matcher, qui correspond à la vue en fonction de ses ressources d'arrière-plan. L'exemple de code est le suivant,

onView(allOf(withId("image"), hasBackground(R.drawable.your_drawable)))

hasErrorText ()

hasErrorText () accepte un argument de type Matcher <String> et l'argument fait référence à la valeur de la chaîne d'erreur de la vue (EditText). Il renvoie un matcher, qui correspond à la vue en utilisant la chaîne d'erreur de la vue. Cela s'applique uniquement à EditText . L'exemple de code est le suivant,

onView(allOf(withId(R.id.editText_name), hasErrorText(is("name is required"))))

Il accepte également les arguments de chaîne. L'exemple de code est le suivant,

onView(allOf(withId(R.id.editText_name), hasErrorText("name is required")))

hasImeAction ()

hasImeAction () accepte un argument de type Matcher <Integer> et l'argument fait référence aux méthodes d'entrée prises en charge par la vue (EditText). Il renvoie un matcher, qui correspond à la vue à l'aide de la méthode d'entrée prise en charge de la vue. Cela s'applique uniquement à EditText . L'exemple de code est le suivant,

onView(allOf(withId(R.id.editText_name),
hasImeAction(is(EditorInfo.IME_ACTION_GO))))

Ici, EditorInfo.IME_ACTION_GO est l'une des options des méthodes d'entrée. hasImeAction () accepte également l'argument entier. L'exemple de code est le suivant,

onView(allOf(withId(R.id.editText_name),
hasImeAction(EditorInfo.IME_ACTION_GO)))

supportsInputMethods ()

supportsInputMethods () n'a aucun argument. Il renvoie un matcher, qui correspond à la vue s'il prend en charge les méthodes d'entrée. L'exemple de code est le suivant,

onView(allOf(withId(R.id.editText_name), supportsInputMethods()))

isRoot ()

isRoot () n'a pas d'argument. Il renvoie un matcher, qui correspond à la vue racine. L'exemple de code est le suivant,

onView(allOf(withId(R.id.my_root_id), isRoot()))

est affiché()

isDisplayed () n'a pas d'argument. Il renvoie un matcher, qui correspond à la vue actuellement affichée. L'exemple de code est le suivant,

onView(allOf(withId(R.id.my_view_id), isDisplayed()))

isDisplayingAtLeast ()

isDisplayingAtLeast () accepte un seul argument de type int. Il renvoie une correspondance, qui correspond à la vue actuellement affichée au moins au pourcentage spécifié. L'exemple de code est le suivant,

onView(allOf(withId(R.id.my_view_id), isDisplayingAtLeast(75)))

isCompletelyDisplayed ()

isCompletelyDisplayed () n'a pas d'argument. Il renvoie un matcher, qui correspond à la vue actuellement affichée complètement à l'écran. L'exemple de code est le suivant,

onView(allOf(withId(R.id.my_view_id), isCompletelyDisplayed()))

est autorisé()

isEnabled () n'a pas d'argument. Il renvoie un matcher, qui correspond à la vue activée. L'exemple de code est le suivant,

onView(allOf(withId(R.id.my_view_id), isEnabled()))

isFocusable ()

isFocusable () n'a aucun argument. Il renvoie un matcher, qui correspond à la vue qui a l'option de focus. L'exemple de code est le suivant,

onView(allOf(withId(R.id.my_view_id), isFocusable()))

hasFocus ()

hasFocus () n'a aucun argument. Il renvoie une correspondance, qui correspond à la vue actuellement focalisée. L'exemple de code est le suivant,

onView(allOf(withId(R.id.my_view_id), hasFocus()))

isClickable ()

isClickable () n'a pas d'argument. Il renvoie une correspondance, qui correspond à la vue qui est l'option de clic. L'exemple de code est le suivant,

onView(allOf(withId(R.id.my_view_id), isClickable()))

est sélectionné()

isSelected () n'a pas d'argument. Il renvoie une correspondance, qui correspond à la vue actuellement sélectionnée. L'exemple de code est le suivant,

onView(allOf(withId(R.id.my_view_id), isSelected()))

est vérifié()

isChecked () n'a pas d'argument. Il retourne un matcher, qui correspond à la vue de type CompoundButton (ou sous-type de celui-ci) et est à l'état vérifié. L'exemple de code est le suivant,

onView(allOf(withId(R.id.my_view_id), isChecked()))

isNotChecked ()

isNotChecked () est juste en face de isChecked. L'exemple de code est le suivant *,

onView(allOf(withId(R.id.my_view_id), isNotChecked()))

isJavascriptEnabled ()

isJavascriptEnabled () n'a pas d'argument. Il renvoie un matcher, qui correspond à la WebView qui évalue JavaScript. L'exemple de code est le suivant,

onView(allOf(withId(R.id.my_webview_id), isJavascriptEnabled()))

avec un parent()

withParent () accepte un argument de type Matcher <View>. L'argument fait référence à une vue. Il renvoie une correspondance, qui correspond à la vue spécifiée comme vue parente. L'exemple de code est le suivant,

onView(allOf(withId(R.id.childView), withParent(withId(R.id.parentView))))

hasSibling ()

hasSibling () accepte un argument de type Matcher> View <. L'argument fait référence à une vue. Il renvoie un matcher, qui correspond à la vue transmise par la vue est l'une de ses vues sœurs. L'exemple de code est le suivant,

onView(hasSibling(withId(R.id.siblingView)))

avecEnfant ()

withChild () accepte un argument de type Matcher <View>. L'argument fait référence à une vue. Il renvoie un matcher, qui correspond à la vue transmise comme vue enfant. L'exemple de code est le suivant,

onView(allOf(withId(R.id.parentView), withChild(withId(R.id.childView))))

hasChildCount ()

hasChildCount () accepte un argument de type int. L'argument fait référence au nombre d'enfants d'une vue. Il renvoie un matcher, qui correspond à la vue qui a exactement le même nombre de vues enfants que celui spécifié dans l'argument. L'exemple de code est le suivant,

onView(hasChildCount(4))

hasMinimumChildCount ()

hasMinimumChildCount () accepte un argument de type int. L'argument fait référence au nombre d'enfants d'une vue. Il renvoie un matcher, qui correspond à la vue qui a au moins le nombre de vues enfants comme spécifié dans l'argument. L'exemple de code est le suivant,

onView(hasMinimumChildCount(4))

hasDescendant ()

hasDescendant () accepte un argument de type Matcher <View>. L'argument fait référence à une vue. Il renvoie un matcher, qui correspond à la vue dont la vue transmise est l'une des vues descendantes dans la hiérarchie des vues. L'exemple de code est le suivant,

onView(hasDescendant(withId(R.id.descendantView)))

isDescendantOfA ()

isDescendantOfA () accepte un argument de type Matcher <View>. L'argument fait référence à une vue. Il renvoie un matcher, qui correspond à la vue dont la vue transmise est l'une des vues ancêtres dans la hiérarchie des vues. L'exemple de code est le suivant,

onView(allOf(withId(R.id.myView), isDescendantOfA(withId(R.id.parentView))))

Espresso propose diverses options pour créer nos propres matchers de vue personnalisés et il est basé sur les matchers Hamcrest. Custom matcher est un concept très puissant pour étendre le framework et aussi pour personnaliser le framework à notre goût. Certains des avantages de l'écriture de correspondances personnalisées sont les suivants:

  • Pour exploiter la fonctionnalité unique de nos propres vues personnalisées

  • Le matcher personnalisé permet dans les cas de test basés sur AdapterView de correspondre aux différents types de données sous-jacentes.

  • Pour simplifier les matchers actuels en combinant les fonctionnalités de plusieurs matcher

On peut créer un nouveau matcher au fur et à mesure de la demande et c'est assez simple. Créons un nouveau matcher personnalisé, qui renvoie un matcher pour tester à la fois l'ID et le texte d'un TextView .

Espresso fournit les deux classes suivantes pour écrire de nouveaux matchers -

  • TypeSafeMatcher

  • BoundedMatcher

Les deux classes sont de nature similaire, sauf que BoundedMatcher gère de manière transparente le transtypage de l'objet pour le type correct sans vérifier manuellement le type correct. Nous allons créer un nouveau matcher, withIdAndText en utilisant la classe BoundedMatcher . Laissez-nous vérifier les étapes pour écrire de nouveaux matchers.

  • Ajoutez la dépendance ci-dessous dans le fichier app / build.gradle et synchronisez-la.

dependencies {
   implementation 'androidx.test.espresso:espresso-core:3.1.1'
}
  • Créez une nouvelle classe pour inclure nos correspondants (méthodes) et marquez-la comme finale

public final class MyMatchers {
}
  • Déclarez une méthode statique dans la nouvelle classe avec les arguments nécessaires et définissez Matcher <View> comme type de retour.

public final class MyMatchers {
   @NonNull
   public static Matcher<View> withIdAndText(final Matcher<Integer>
   integerMatcher, final Matcher<String> stringMatcher) {
   }
}
  • Créez un nouvel objet BoundedMatcher (valeur de retour également) avec la signature ci-dessous dans la méthode statique,

public final class MyMatchers {
   @NonNull
   public static Matcher<View> withIdAndText(final Matcher<Integer>
   integerMatcher, final Matcher<String> stringMatcher) {
      return new BoundedMatcher<View, TextView>(TextView.class) {
      };
   }
}
  • Remplacer describeTo et matchesSafely méthodes dans le BoundedMatcher objet. describeTo a un argument unique de type Description sans type de retour et il est utilisé pour les informations d'erreur concernant les correspondants. matchesSafely a un seul argument de type TextView avec un type de retour booléen et il est utilisé pour correspondre à la vue.

La version finale du code est la suivante,

public final class MyMatchers {
   @NonNull
   public static Matcher<View> withIdAndText(final Matcher<Integer>
   integerMatcher, final Matcher<String> stringMatcher) {
      return new BoundedMatcher<View, TextView>(TextView.class) {
         @Override
         public void describeTo(final Description description) {
            description.appendText("error text: ");
            stringMatcher.describeTo(description);
            integerMatcher.describeTo(description);
         }
         @Override
         public boolean matchesSafely(final TextView textView) {
            return stringMatcher.matches(textView.getText().toString()) &&
            integerMatcher.matches(textView.getId());
         }
      };
   }
}
  • Enfin, nous pouvons utiliser notre mew matcher pour écrire le cas de test comme ci-dessous,

@Test
public void view_customMatcher_isCorrect() {
   onView(withIdAndText(is((Integer) R.id.textView_hello), is((String) "Hello World!")))
      .check(matches(withText("Hello World!")));
}

Comme indiqué précédemment, l'assertion de vue est utilisée pour affirmer que la vue réelle (trouvée à l'aide des correspondances de vue) et les vues attendues sont identiques. Un exemple de code est le suivant,

onView(withId(R.id.my_view)) .check(matches(withText("Hello")))

Ici,

  • onView () renvoie l' objet ViewInteration correspondant à la vue correspondante. ViewInteraction est utilisé pour interagir avec la vue correspondante.

  • withId (R.id.my_view) renvoie une correspondance de vue qui correspondra à la vue (réelle) dont les attributs id sont égaux à my_view .

  • withText («Hello») renvoie également une correspondance de vue qui correspondra à la vue (attendue) dont les attributs de texte sont égaux à Hello .

  • check est une méthode qui accepte un argument de type ViewAssertion et effectue une assertion à l'aide de l' objet ViewAssertion .

  • matches (withText («Hello»)) renvoie une assertion de vue, qui fera lereal jobd'affirmer que la vue réelle (trouvée avec withId ) et la vue attendue (trouvée avec withText ) sont une seule et même chose.

Apprenons quelques-unes des méthodes fournies par le framework de test espresso pour affirmer les objets de vue.

n'existe pas()

Renvoie une assertion de vue, qui garantit que la correspondance de vue ne trouve aucune vue correspondante.

onView(withText("Hello")) .check(doesNotExist());

Ici, le cas de test garantit qu'il n'y a pas de vue avec le texte Hello.

allumettes()

Accepte une correspondance de vue cible et renvoie une assertion de vue, qui garantit que la correspondance de vue (réelle) existe et correspond à la vue correspondant à la correspondance de vue cible.

onView(withId(R.id.textView_hello)) .check(matches(withText("Hello World!")));

Ici, le cas de test garantit que la vue ayant l'id, R.id.textView_hello existe et correspond à la vue cible avec le texte Hello World!

isBottomAlignedWith ()

Accepte une correspondance de vue cible et renvoie une assertion de vue, qui garantit que la correspondance de vue (réelle) existe et est alignée en bas avec la correspondance de vue cible.

onView(withId(R.id.view)) .check(isBottomAlignedWith(withId(R.id.target_view)))

Ici, le cas de test garantit que la vue ayant l'id, R.id.view existe et est alignée en bas avec la vue ayant l'id, R.id.target_view .

isCompletelyAbove ()

Accepte une correspondance de vue cible et renvoie une assertion de vue, ce qui garantit que la correspondance de vue (réelle) existe et est positionnée complètement au-dessus de la correspondance de vue cible.

onView(withId(R.id.view)) .check(isCompletelyAbove(withId(R.id.target_view)))

Ici, le cas de test garantit que la vue ayant l'id, R.id.view existe et est positionnée complètement au-dessus de la vue ayant l'id, R.id.target_view

isCompletelyBelow ()

Accepte une correspondance de vue cible et renvoie une assertion de vue, qui garantit que la correspondance de vue (réelle) existe et est positionnée complètement sous la correspondance de vue cible.

onView(withId(R.id.view)) .check(isCompletelyBelow(withId(R.id.target_view)))

Ici, le cas de test garantit que la vue ayant l'id, R.id.view existe et est positionnée complètement en dessous de la vue ayant l'id, R.id.target_view .

isCompletelyLeftOf ()

Accepte une correspondance de vue cible et renvoie une assertion de vue, ce qui garantit que la correspondance de vue (réelle) existe et est positionnée complètement à gauche de la correspondance de vue cible.

onView(withId(R.id.view)) .check(isCompletelyLeftOf(withId(R.id.target_view)))

Ici, le cas de test garantit que la vue ayant l'id, R.id.view existe et est positionnée complètement à gauche de la vue ayant l'id, R.id.target_view

isCompletelyRightOf ()

Accepte une correspondance de vue cible et renvoie une assertion de vue, ce qui garantit que la correspondance de vue (réelle) existe et est positionnée complètement à droite de la correspondance de vue cible.

onView(withId(R.id.view)) .check(isCompletelyRightOf(withId(R.id.target_view)))

Ici, le cas de test garantit que la vue ayant l'id, R.id.view existe et est positionnée complètement à droite de la vue ayant l'id, R.id.target_view.

isLeftAlignedWith ()

Accepte une correspondance de vue cible et renvoie une assertion de vue, ce qui garantit que la correspondance de vue (réelle) existe et est alignée à gauche avec la correspondance de vue cible.

onView(withId(R.id.view)) .check(isLeftAlignedWith(withId(R.id.target_view)))

Ici, le cas de test garantit que la vue ayant l'id, R.id.view existe et est alignée à gauche avec la vue ayant l'id, R.id.target_view

isPartiallyAbove ()

Accepte une correspondance de vue cible et renvoie une assertion de vue, qui garantit que la correspondance de vue (réelle) existe et est positionnée partiellement au-dessus de la correspondance de vue cible.

onView(withId(R.id.view)) .check(isPartiallyAbove(withId(R.id.target_view)))

Ici, le cas de test garantit que la vue ayant l'id, R.id.view existe et est positionnée partiellement au-dessus de la vue ayant l'id, R.id.target_view

isPartiallyBelow ()

Accepte une correspondance de vue cible et renvoie une assertion de vue, ce qui garantit que la correspondance de vue (réelle) existe et est positionnée partiellement sous la correspondance de vue cible.

onView(withId(R.id.view)) .check(isPartiallyBelow(withId(R.id.target_view)))

Ici, le scénario de test garantit que la vue ayant l'id, R.id.view existe et est positionnée partiellement sous la vue ayant l'id, R.id.target_view .

isPartiallyLeftOf ()

Accepte une correspondance de vue cible et renvoie une assertion de vue, qui garantit que la correspondance de vue (réelle) existe et est positionnée partiellement à gauche de la correspondance de vue cible.

onView(withId(R.id.view)) .check(isPartiallyLeftOf(withId(R.id.target_view)))

Ici, le cas de test garantit que la vue ayant l'id, R.id.view existe et est positionnée partiellement à gauche de la vue ayant l'id, R.id.target_view .

isPartiallyRightOf ()

Accepte une correspondance de vue cible et renvoie une assertion de vue, qui garantit que la correspondance de vue (réelle) existe et est positionnée partiellement à droite de la correspondance de vue cible

onView(withId(R.id.view)) .check(isPartiallyRightOf(withId(R.id.target_view)))

Ici, le cas de test garantit que la vue ayant l'id, R.id.view existe et est positionnée partiellement à droite de la vue ayant l'id, R.id.target_view .

isRightAlignedWith ()

Accepte une correspondance de vue cible et renvoie une assertion de vue, qui garantit que la correspondance de vue (réelle) existe et est alignée à droite avec la correspondance de vue cible.

onView(withId(R.id.view)) .check(isRightAlignedWith(withId(R.id.target_view)))

Ici, le scénario de test garantit que la vue ayant l'id, R.id.view existe et est alignée à droite avec la vue ayant l'id, R.id.target_view .

isTopAlignedWith ()

Accepte une correspondance de vue cible et renvoie une assertion de vue, ce qui garantit que la correspondance de vue (réelle) existe et est alignée en haut avec la correspondance de vue cible.

onView(withId(R.id.view)) .check(isTopAlignedWith(withId(R.id.target_view)))

Ici, le cas de test garantit que la vue ayant l'id, R.id.view existe et est alignée en haut avec la vue ayant l'id, R.id.target_view

noEllipsizedText ()

Renvoie une assertion de vue, qui garantit que la hiérarchie des vues ne contient pas de vues de texte en ellipse ou tronquées.

onView(withId(R.id.view)) .check(noEllipsizedText());

noMultilineButtons ()

Renvoie une assertion de vue, qui garantit que la hiérarchie de vues ne contient pas de boutons sur plusieurs lignes.

onView(withId(R.id.view)) .check(noMultilineButtons());

noOverlaps ()

Renvoie une assertion de vue, qui garantit que l'objet descendant attribuable à TextView ou ImageView ne se chevauche pas. Il a une autre option, qui accepte une correspondance de vue cible et renvoie une assertion de vue, qui garantit que la vue descendante correspondant à la vue cible ne se chevauche pas.

Comme appris précédemment, les actions d'affichage automatisent toutes les actions possibles exécutables par les utilisateurs dans une application Android. Espresso onView et «onData» fournissent la méthode perform , qui accepte les actions de vue et appelle / automatise les actions utilisateur correspondantes dans l'environnement de test. Par exemple, "click ()" est une action de vue qui, lorsqu'elle est transmise à la méthode onView ( R.id.myButton ) .perform (click ()) , déclenche l'événement click du bouton (avec l'ID: "myButton" ) dans l'environnement de test.

Dans ce chapitre, apprenons les actions d'affichage fournies par le framework de test espresso.

typeText ()

typeText () accepte un argument (texte) de type String et renvoie une action de vue. L'action de vue renvoyée tape le texte fourni dans la vue. Avant de placer le texte, il appuie une fois sur la vue. Le contenu peut être placé à une position arbitraire s'il contient déjà du texte.

onView(withId(R.id.text_view)).perform(typeText("Hello World!"))

typeTextIntoFocusedView ()

typeTextIntoFocusedView () est similaire à typeText () sauf qu'il place le texte juste à côté de la position du curseur dans la vue.

onView(withId(R.id.text_view)).perform(typeTextIntoFocusedView("Hello World!"))

replaceText ()

replaceText () est similaire à typeText () sauf qu'il remplace le contenu de la vue.

onView(withId(R.id.text_view)).perform(typeTextIntoFocusedView("Hello World!"))

effacer le texte()

clearText () n'a aucun argument et renvoie une action de vue, qui effacera le texte de la vue.

onView(withId(R.id.text_view)).perform(clearText())

appuyez sur la touche()

pressKey () accepte le code clé (par exemple KeyEvent.KEYCODE_ENTER) et retourne une action de vue, qui appuiera sur la touche correspond au code clé.

onView(withId(R.id.text_view)).perform(typeText(
   "Hello World!", pressKey(KeyEvent.KEYCODE_ENTER))

pressMenuKey ()

pressMenuKey () n'a pas d'arguments et renvoie une action de vue, qui appuiera sur la touche de menu matérielle.

onView(withId(R.id.text_view)).perform(typeText(
   "Hello World!", pressKey(KeyEvent.KEYCODE_ENTER), pressMenuKey())

closeSoftKeyboard ()

closeSoftKeyboard () n'a pas d'arguments et renvoie une action de vue, qui fermera le clavier, s'il en est ouvert.

onView(withId(R.id.text_view)).perform(typeText(
   "Hello World!", closeSoftKeyboard())

Cliquez sur()

click () n'a pas d'arguments et renvoie une action de vue, qui appellera l'action de clic de la vue.

onView(withId(R.id.button)).perform(click())

doubleCliquez ()

doubleClick () n'a pas d'arguments et renvoie une action de vue, qui appellera l'action de double-clic de la vue.

onView(withId(R.id.button)).perform(doubleClick())

longClick ()

longClick () n'a pas d'arguments et renvoie une action de vue, qui appellera l'action de clic long de la vue.

onView(withId(R.id.button)).perform(longClick())

pressBack ()

pressBack () n'a aucun argument et renvoie une action de vue, qui cliquera sur le bouton de retour.

onView(withId(R.id.button)).perform(pressBack())

pressBackUnconditionally ()

pressBackUnconditionally () n'a aucun argument et renvoie une action de vue, qui cliquera sur le bouton de retour et ne lèvera pas d'exception si l'action du bouton de retour quitte l'application elle-même.

onView(withId(R.id.button)).perform(pressBack())

ouvrir le lien()

openLink () a deux arguments. Le premier argument (texte du lien) est de type Matcher et fait référence au texte de la balise d'ancrage HTML. Le deuxième argument (url) est du type Matcher et fait référence à l'url de la balise d'ancrage HTML. Elle s'applique uniquement à TextView . Il retourne une action de vue, qui collecte toutes les balises d'ancrage HTML disponibles dans le contenu de la vue texte, trouve la balise d'ancrage correspondant au premier argument (texte du lien) et au deuxième argument (url) et ouvre enfin l'url correspondante. Considérons une vue de texte ayant le contenu comme -

<a href="http://www.google.com/">copyright</a>

Ensuite, le lien peut être ouvert et testé en utilisant le cas de test ci-dessous,

onView(withId(R.id.text_view)).perform(openLink(is("copyright"),
   is(Uri.parse("http://www.google.com/"))))

Ici, openLink obtiendra le contenu de la vue texte, trouvera le lien ayant le droit d'auteur sous forme de texte, www.google.com comme URL et ouvrira l'url dans un navigateur.

openLinkWithText ()

openLinkWithText () a un argument, qui peut être de type ** String * ou Matcher. Il s'agit simplement d'un raccourci vers la méthode openLink *.

onView(withId(R.id.text_view)).perform(openLinkWithText("copyright"))

openLinkWithUri ()

openLinkWithUri () a un argument, qui peut être de type String ou Matcher. Il est tout simplement une courte coupe à la OpenLink méthode *.

onView(withId(R.id.text_view)).perform(openLinkWithUri("http://www.google.com/"))

pressImeActionButton ()

pressImeActionButton () n'a pas d'arguments et renvoie une action de vue, qui exécutera l'action définie dans la configuration android: imeOptions . Par exemple, si android: imeOptions est égal à actionNext, cela déplacera le curseur sur la prochaine vue EditText possible à l'écran.

onView(withId(R.id.text_view)).perform(pressImeActionButton())

scrollTo ()

scrollTo () n'a pas d'arguments et retourne une action de vue, qui fera défiler le scrollView correspondant à l'écran.

onView(withId(R.id.scrollView)).perform(scrollTo())

balayez vers le bas()

swipeDown () n'a pas d'arguments et renvoie une action de vue, qui déclenchera une action de balayage vers le bas à l'écran.

onView(withId(R.id.root)).perform(swipeDown())

balayez vers le haut()

swipeUp () n'a pas d'arguments et renvoie une action de vue, qui déclenchera une action de balayage vers le haut à l'écran.

onView(withId(R.id.root)).perform(swipeUp())

swipeDroite ()

swipeRight () n'a pas d'arguments et renvoie une action de vue, qui déclenchera une action de balayage vers la droite sur l'écran.

onView(withId(R.id.root)).perform(swipeRight())

swipeGauche ()

swipeLeft () n'a pas d'arguments et renvoie une action de vue, qui déclenchera une action de balayage vers la gauche sur l'écran.

onView(withId(R.id.root)).perform(swipeLeft())

AdapterView est un type de vue spécial spécialement conçu pour restituer une collection d'informations similaires telles que la liste de produits et les contacts utilisateur extraits d'une source de données sous-jacente à l'aide d' Adaptateur . La source de données peut être une simple liste d'entrées de base de données complexes. Certaines vues dérivées de AdapterView sont ListView , GridView et Spinner .

AdapterView rend l'interface utilisateur de manière dynamique en fonction de la quantité de données disponibles dans la source de données sous-jacente. De plus, AdapterView ne rend que les données minimales nécessaires, qui peuvent être rendues dans la zone visible disponible de l'écran. AdapterView effectue cette opération pour conserver la mémoire et pour rendre l'interface utilisateur fluide même si les données sous-jacentes sont volumineuses.

Lors de l'analyse, la nature de l' architecture AdapterView rend l' option onView et ses correspondances de vue non pertinentes car la vue particulière à tester peut ne pas être rendue du tout en premier lieu. Heureusement, espresso fournit une méthode, onData ( ), qui accepte les matchers hamcrest (pertinents pour le type de données des données sous-jacentes) pour faire correspondre les données sous-jacentes et renvoie un objet de type DataInteraction correspondant à la vue des données correspondantes. Un exemple de code est le suivant,

onData(allOf(is(instanceOf(String.class)), startsWith("Apple"))).perform(click())

Ici, onData () correspond à l'entrée «Apple», si elle est disponible dans les données sous-jacentes (liste de tableaux) et renvoie l' objet DataInteraction pour interagir avec la vue correspondante (TextView correspondant à l'entrée «Apple»).

Méthodes

DataInteraction fournit les méthodes ci-dessous pour interagir avec la vue,

effectuer()

Cela accepte les actions de vue et déclenche les actions de vue transmises.

onData(allOf(is(instanceOf(String.class)), startsWith("Apple"))).perform(click())

vérifier()

Cela accepte les assertions de vue et vérifie les assertions de vue transmises.

onData(allOf(is(instanceOf(String.class)), startsWith("Apple")))
   .check(matches(withText("Apple")))

inAdapterView ()

Cela accepte les correspondances de vue. Il sélectionne l' AdaptateurView particulier en fonction des correspondances de vue transmises et renvoie l' objet DataInteraction pour interagir avec l' AdaptateurView correspondant .

onData(allOf())
   .inAdapterView(withId(R.id.adapter_view))
   .atPosition(5)
   .perform(click())

atPosition ()

Cela accepte un argument de type entier et fait référence à la position de l'élément dans les données sous-jacentes. Il sélectionne la vue correspondant à la valeur positionnelle transmise des données et renvoie l' objet DataInteraction pour interagir avec la vue correspondante. Ce sera utile, si nous connaissons le bon ordre des données sous-jacentes.

onData(allOf())
   .inAdapterView(withId(R.id.adapter_view))
   .atPosition(5)
   .perform(click())

onChildView ()

Cela accepte les correspondances de vue et correspond à la vue dans la vue enfant spécifique. Par exemple, nous pouvons interagir avec des articles spécifiques comme le bouton Acheter dans une liste de produits basée sur AdapterView .

onData(allOf(is(instanceOf(String.class)), startsWith("Apple")))
   .onChildView(withId(R.id.buy_button))
   .perform(click())

Rédiger un exemple d'application

Suivez les étapes ci-dessous pour écrire une application simple basée sur AdapterView et écrire un cas de test à l'aide de la méthode onData () .

  • Démarrez le studio Android.

  • Créez un nouveau projet comme indiqué précédemment et nommez-le MyFruitApp .

  • Migrez l'application vers le framework AndroidX à l'aide de RefactorMigrer vers le menu d'options AndroidX .

  • Supprimez la conception par défaut dans l'activité principale et ajoutez ListView . Le contenu de activity_main.xml est le suivant,

<?xml version = "1.0" encoding = "utf-8"?>
<RelativeLayout xmlns:android = "http://schemas.android.com/apk/res/android"
   xmlns:app = "http://schemas.android.com/apk/res-auto"
   xmlns:tools = "http://schemas.android.com/tools"
   android:layout_width = "match_parent"
   android:layout_height = "match_parent"
   tools:context = ".MainActivity">
   <ListView
      android:id = "@+id/listView"
      android:layout_width = "wrap_content"
      android:layout_height = "wrap_content" />
</RelativeLayout>
  • Ajoutez une nouvelle ressource de mise en page, item.xml pour spécifier le modèle d'élément de la vue de liste. Le contenu du fichier item.xml est le suivant,

<?xml version = "1.0" encoding = "utf-8"?>
<TextView xmlns:android = "http://schemas.android.com/apk/res/android"
   android:id = "@+id/name"
   android:layout_width = "fill_parent"
   android:layout_height = "fill_parent"
   android:padding = "8dp"
/>
  • Maintenant, créez un adaptateur ayant un tableau de fruits comme données sous-jacentes et définissez-le sur la vue de liste. Cela doit être fait dans le onCreate () de MainActivity comme spécifié ci-dessous,

@Override
protected void onCreate(Bundle savedInstanceState) {
   super.onCreate(savedInstanceState);
   setContentView(R.layout.activity_main);
   
   // Find fruit list view
   final ListView listView = (ListView) findViewById(R.id.listView);
   
   // Initialize fruit data
   String[] fruits = new String[]{
      "Apple", 
      "Banana", 
      "Cherry", 
      "Dates", 
      "Elderberry", 
      "Fig", 
      "Grapes", 
      "Grapefruit", 
      "Guava",
      "Jack fruit", 
      "Lemon",
      "Mango", 
      "Orange", 
      "Papaya", 
      "Pears", 
      "Peaches", 
      "Pineapple",
      "Plums", 
      "Raspberry",
      "Strawberry", 
      "Watermelon"
   };
   
   // Create array list of fruits
   final ArrayList<String> fruitList = new ArrayList<String>();
   for (int i = 0; i < fruits.length; ++i) {
      fruitList.add(fruits[i]);
   }
   
   // Create Array adapter
   final ArrayAdapter adapter = new ArrayAdapter(this, R.layout.item, fruitList);
   
   // Set adapter in list view
   listView.setAdapter(adapter);
}
  • Maintenant, compilez le code et exécutez l'application. La capture d'écran de l'application My Fruit est la suivante,

  • Maintenant, ouvrez le fichier ExampleInstrumentedTest.java et ajoutez ActivityTestRule comme spécifié ci-dessous,

@Rule
public ActivityTestRule<MainActivity> mActivityRule =
   new ActivityTestRule<MainActivity>(MainActivity.class);

Assurez-vous également que la configuration du test est effectuée dans app / build.gradle -

dependencies {
   testImplementation 'junit:junit:4.12'
   androidTestImplementation 'androidx.test:runner:1.1.1'
   androidTestImplementation 'androidx.test:rules:1.1.1'
   androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
}
  • Ajoutez un nouveau cas de test pour tester la vue de liste comme ci-dessous,

@Test
public void listView_isCorrect() {
   // check list view is visible
   onView(withId(R.id.listView)).check(matches(isDisplayed()));
   onData(allOf(is(instanceOf(String.class)), startsWith("Apple"))).perform(click());
   onData(allOf(is(instanceOf(String.class)), startsWith("Apple")))
      .check(matches(withText("Apple")));
   // click a child item
   onData(allOf())
      .inAdapterView(withId(R.id.listView))
      .atPosition(10)
      .perform(click());
}
  • Enfin, exécutez le scénario de test à l'aide du menu contextuel d'Android Studio et vérifiez si tous les scénarios de test réussissent.

WebView est une vue spéciale fournie par Android pour afficher des pages Web à l'intérieur de l'application. WebView ne fournit pas toutes les fonctionnalités d'une application de navigateur à part entière comme Chrome et Firefox. Cependant, il offre un contrôle complet sur le contenu à afficher et expose toutes les fonctionnalités Android à invoquer dans les pages Web. Il active WebView et fournit un environnement spécial dans lequel l'interface utilisateur peut être facilement conçue à l'aide de la technologie HTML et de fonctionnalités natives telles que la caméra et la composition d'un contact. Cet ensemble de fonctionnalités permet à une WebView de fournir un nouveau type d'application appelée application hybride , dans laquelle l'interface utilisateur est réalisée en HTML et la logique métier est réalisée en JavaScript ou via un point de terminaison d'API externe.

Normalement, tester une WebView doit être un défi car il utilise la technologie HTML pour ses éléments d'interface utilisateur plutôt que l'interface utilisateur / les vues natives. Espresso excelle dans ce domaine en fournissant un nouvel ensemble aux correspondants Web et aux assertions Web, qui est intentionnellement similaire aux correspondants de vue natifs et aux assertions de vue. Dans le même temps, il fournit une approche bien équilibrée en incluant également un environnement de test basé sur la technologie Web.

Espresso Web est basé sur le framework WebDriver Atom, qui est utilisé pour rechercher et manipuler des éléments Web. Atom est similaire aux actions d'affichage. Atom effectuera toutes les interactions à l'intérieur d'une page Web. WebDriver expose un ensemble prédéfini de méthodes, comme findElement () , getElement () pour rechercher des éléments Web et retourne les atomes correspondants (pour effectuer une action dans la page Web).

Une déclaration de test Web standard ressemble au code ci-dessous,

onWebView()
   .withElement(Atom)
   .perform(Atom)
   .check(WebAssertion)

Ici,

  • onWebView () - Semblable à onView (), il expose un ensemble d'API pour tester une WebView.

  • withElement () - L'une des nombreuses méthodes utilisées pour localiser des éléments Web dans une page Web à l'aide d'Atom et retourne l'objet WebInteration, qui est similaire à ViewInteraction.

  • perform () - Exécute l'action dans une page Web à l'aide d'Atom et retourne WebInteraction.

  • check () - Ceci effectue l'assertion nécessaire à l'aide de WebAssertion.

Un exemple de code de test Web est le suivant,

onWebView()
   .withElement(findElement(Locator.ID, "apple"))
   .check(webMatches(getText(), containsString("Apple")))

Ici,

  • findElement () localise un élément et renvoie un Atom

  • webMatches est similaire à la méthode matches

Rédiger un exemple d'application

Écrivons une application simple basée sur WebView et écrivons un cas de test en utilisant la méthode onWebView () . Suivez ces étapes pour écrire un exemple d'application -

  • Démarrez le studio Android.

  • Créez un nouveau projet comme indiqué précédemment et nommez-le MyWebViewApp .

  • Migrez l'application vers le framework AndroidX à l'aide de RefactorMigrer vers le menu d'options AndroidX .

  • Ajoutez l'option de configuration ci-dessous dans le fichier AndroidManifest.xml pour autoriser l'accès à Internet.

<uses-permission android:name = "android.permission.INTERNET" />
  • Espresso Web est fourni sous forme de plugin séparé. Alors, ajoutez la dépendance dans l'app / build.gradle et synchronisez-la.

dependencies {
   androidTestImplementation 'androidx.test:rules:1.1.1'
   androidTestImplementation 'androidx.test.espresso:espresso-web:3.1.1'
}
  • Supprimez la conception par défaut dans l'activité principale et ajoutez WebView. Le contenu de activity_main.xml est le suivant,

<?xml version = "1.0" encoding = "utf-8"?>
<RelativeLayout xmlns:android = "http://schemas.android.com/apk/res/android"
   xmlns:app = "http://schemas.android.com/apk/res-auto"
   xmlns:tools = "http://schemas.android.com/tools"
   android:layout_width = "match_parent"
   android:layout_height = "match_parent"
   tools:context = ".MainActivity">
   <WebView
      android:id = "@+id/web_view_test"
      android:layout_width = "fill_parent"
      android:layout_height = "fill_parent" />
</RelativeLayout>
  • Créez une nouvelle classe, ExtendedWebViewClient étendant WebViewClient et remplacez la méthode shouldOverrideUrlLoading pour charger l'action de lien dans la même WebView ; sinon, il ouvrira une nouvelle fenêtre de navigateur en dehors de l'application. Placez-le dans MainActivity.java .

private class ExtendedWebViewClient extends WebViewClient {
   @Override
   public boolean shouldOverrideUrlLoading(WebView view, String url) {
      view.loadUrl(url);
      return true;
   }
}
  • Maintenant, ajoutez le code ci-dessous dans la méthode onCreate de MainActivity . Le but du code est de trouver la WebView , de la configurer correctement et enfin de charger l'url cible.

// Find web view
WebView webView = (WebView) findViewById(R.id.web_view_test);

// set web view client
webView.setWebViewClient(new ExtendedWebViewClient());

// Clear cache
webView.clearCache(true);

// load Url
webView.loadUrl("http://<your domain or IP>/index.html");

Ici,

  • Le contenu de index.html est le suivant -

<html>
   <head>
      <title>Android Web View Sample</title>
   </head>
   <body>
      <h1>Fruits</h1>
      <ol>
         <li><a href = "apple.html" id = "apple">Apple</a></li>
         <li><a href = "banana.html" id = "banana">Banana</a></li>
         </ol>
   </body>
</html>
  • Le contenu du fichier apple.html référencé dans index.html est le suivant -

<html>
   <head>
      <title>Android Web View Sample</title>
   </head>
   
   <body>
      <h1>Apple</h1>
   </body>
</html>
  • Le contenu du fichier banana.html référencé dans banana.html est le suivant,

<html>
   <head>
      <title>Android Web View Sample</title>
   </head>
   
   <body>
      <h1>Banana</h1>
   </body>
</html>
  • Placez index.html, apple.html et banana.html dans un serveur Web

  • Remplacez l'url dans la méthode loadUrl par votre URL configurée.

  • Maintenant, exécutez l'application et vérifiez manuellement si tout va bien. Ci-dessous, la capture d'écran de l' exemple d'application WebView -

  • Maintenant, ouvrez le fichier ExampleInstrumentedTest.java et ajoutez la règle ci-dessous -

@Rule
public ActivityTestRule<MainActivity> mActivityRule =
   new ActivityTestRule<MainActivity>(MainActivity.class, false, true) {
   @Override
   protected void afterActivityLaunched() {
      onWebView(withId(R.id.web_view_test)).forceJavascriptEnabled();
   }
};

Ici, nous avons trouvé le WebView et activé JavaScript de WebView car le framework de test Web espresso fonctionne exclusivement via le moteur JavaScript pour identifier et manipuler l'élément Web.

  • Maintenant, ajoutez le cas de test pour tester notre WebView et son comportement.

@Test
public void webViewTest(){
   onWebView()
      .withElement(findElement(Locator.ID, "apple"))
      .check(webMatches(getText(), containsString("Apple")))
      .perform(webClick())
      .withElement(findElement(Locator.TAG_NAME, "h1"))
      .check(webMatches(getText(), containsString("Apple")));
}

Ici, les tests ont été effectués dans l'ordre suivant,

  • trouvé le lien, apple en utilisant son attribut id via la méthode findElement () et l' énumération Locator.ID .

  • vérifie le texte du lien en utilisant webMatches () méthode

  • effectue une action de clic sur le lien. Il ouvre la page apple.html .

  • à nouveau trouvé l'élément h1 en utilisant les méthodes findElement () et l' énumération Locator.TAG_NAME .

  • enfin vérifie à nouveau le texte de la balise h1 en utilisant la méthode webMatches () .

  • Enfin, exécutez le scénario de test à l'aide du menu contextuel d'Android Studio.

Dans ce chapitre, nous allons apprendre à tester les opérations asynchrones à l'aide des ressources Espresso Idling.

L'un des défis de l'application moderne est de fournir une expérience utilisateur fluide. Fournir une expérience utilisateur fluide implique beaucoup de travail en arrière-plan pour s'assurer que le processus de candidature ne prend pas plus de quelques millisecondes. La tâche en arrière-plan va de la tâche simple à la tâche coûteuse et complexe de récupération des données à partir d'une API / base de données distante. Pour relever le défi dans le passé, un développeur avait l'habitude d'écrire une tâche coûteuse et longue dans un thread d'arrière-plan et de se synchroniser avec l' UIThread principal une fois le thread d'arrière-plan terminé.

Si le développement d'une application multi-thread est complexe, l'écriture de cas de test est encore plus complexe. Par exemple, nous ne devons pas tester un AdapterView avant que les données nécessaires ne soient chargées à partir de la base de données. Si l'extraction des données est effectuée dans un thread séparé, le test doit attendre la fin du thread. Ainsi, l'environnement de test doit être synchronisé entre le thread d'arrière-plan et le thread d'interface utilisateur. Espresso fournit un excellent support pour tester l'application multi-thread. Une application utilise le fil des manières suivantes et espresso prend en charge tous les scénarios.

Threading de l'interface utilisateur

Il est utilisé en interne par le SDK Android pour offrir une expérience utilisateur fluide avec des éléments d'interface utilisateur complexes. Espresso prend en charge ce scénario de manière transparente et ne nécessite aucune configuration ni codage spécial.

Tâche asynchrone

Les langages de programmation modernes prennent en charge la programmation asynchrone pour effectuer des threads légers sans la complexité de la programmation des threads. La tâche Async est également prise en charge de manière transparente par le framework espresso.

Fil utilisateur

Un développeur peut démarrer un nouveau thread pour récupérer des données complexes ou volumineuses de la base de données. Pour prendre en charge ce scénario, l'espresso fournit un concept de ressource au ralenti.

Laissez l'utilisation apprendre le concept de ressource de marche au ralenti et comment l'utiliser dans ce chapitre.

Aperçu

Le concept de ressource au ralenti est très simple et intuitif. L'idée de base est de créer une variable (valeur booléenne) chaque fois qu'un processus de longue durée est démarré dans un thread séparé pour identifier si le processus est en cours d'exécution ou non et l'enregistrer dans l'environnement de test. Pendant le test, le testeur vérifiera la variable enregistrée, le cas échéant, puis trouvera son état en cours d'exécution. Si l'état d'exécution est vrai, le lanceur de test attendra que l'état devienne faux.

Espresso fournit une interface, IdlingResources, dans le but de maintenir l'état de fonctionnement. La principale méthode à implémenter est isIdleNow (). Si isIdleNow () retourne true, espresso reprendra le processus de test ou attendra jusqu'à ce que isIdleNow () retourne false. Nous devons implémenter IdlingResources et utiliser la classe dérivée. Espresso fournit également une partie de l'implémentation IdlingResources intégrée pour alléger notre charge de travail. Ils sont comme suit,

CountingIdlingResource

Cela maintient un compteur interne de la tâche en cours d'exécution. Il expose les méthodes increment () et decrement () . increment () en ajoute un au compteur et decrement () en supprime un du compteur. isIdleNow () renvoie true uniquement lorsqu'aucune tâche n'est active.

UriIdlingResource

Ceci est similaire à CounintIdlingResource, sauf que le compteur doit être à zéro pendant une période prolongée pour prendre également la latence du réseau.

IdlingThreadPoolExecutor

Il s'agit d'une implémentation personnalisée de ThreadPoolExecutor pour conserver le nombre de tâches en cours d'exécution actives dans le pool de threads actuel.

IdlingScheduledThreadPoolExecutor

Ceci est similaire à IdlingThreadPoolExecutor , mais il planifie également une tâche et une implémentation personnalisée de ScheduledThreadPoolExecutor.

Si l'une des implémentations d' IdlingResources ci-dessus ou une implémentation personnalisée est utilisée dans l'application, nous devons également l'enregistrer dans l'environnement de test avant de tester l'application à l'aide de la classe IdlingRegistry comme ci-dessous,

IdlingRegistry.getInstance().register(MyIdlingResource.getIdlingResource());

De plus, il peut être supprimé une fois le test terminé comme ci-dessous -

IdlingRegistry.getInstance().unregister(MyIdlingResource.getIdlingResource());

Espresso fournit cette fonctionnalité dans un package séparé, et le package doit être configuré comme ci-dessous dans app.gradle.

dependencies {
   implementation 'androidx.test.espresso:espresso-idling-resource:3.1.1'
   androidTestImplementation "androidx.test.espresso.idling:idlingconcurrent:3.1.1"
}

Exemple d'application

Créons une application simple pour répertorier les fruits en les récupérant à partir d'un service Web dans un thread séparé, puis testons-la en utilisant le concept de ressource inactive.

  • Démarrez le studio Android.

  • Créez un nouveau projet comme indiqué précédemment et nommez-le MyIdlingFruitApp

  • Migrez l'application vers le framework AndroidX à l'aide de Refactor → Migrer vers le menu d'options AndroidX .

  • Ajoutez la bibliothèque de ressources espresso idling dans l' app / build.gradle (et synchronisez-la) comme spécifié ci-dessous,

dependencies {
   implementation 'androidx.test.espresso:espresso-idling-resource:3.1.1'
   androidTestImplementation "androidx.test.espresso.idling:idlingconcurrent:3.1.1"
}
  • Supprimez la conception par défaut dans l'activité principale et ajoutez ListView. Le contenu de activity_main.xml est le suivant,

<?xml version = "1.0" encoding = "utf-8"?>
<RelativeLayout xmlns:android = "http://schemas.android.com/apk/res/android"
   xmlns:app = "http://schemas.android.com/apk/res-auto"
   xmlns:tools = "http://schemas.android.com/tools"
   android:layout_width = "match_parent"
   android:layout_height = "match_parent"
   tools:context = ".MainActivity">
   <ListView
      android:id = "@+id/listView"
      android:layout_width = "wrap_content"
      android:layout_height = "wrap_content" />
</RelativeLayout>
  • Ajoutez une nouvelle ressource de mise en page, item.xml pour spécifier le modèle d'élément de la vue de liste. Le contenu du fichier item.xml est le suivant,

<?xml version = "1.0" encoding = "utf-8"?>
<TextView xmlns:android = "http://schemas.android.com/apk/res/android"
   android:id = "@+id/name"
   android:layout_width = "fill_parent"
   android:layout_height = "fill_parent"
   android:padding = "8dp"
/>
  • Créez une nouvelle classe - MyIdlingResource . MyIdlingResource est utilisé pour conserver notre IdlingResource en un seul endroit et le récupérer chaque fois que nécessaire. Nous allons utiliser CountingIdlingResource dans notre exemple.

package com.tutorialspoint.espressosamples.myidlingfruitapp;
import androidx.test.espresso.IdlingResource;
import androidx.test.espresso.idling.CountingIdlingResource;

public class MyIdlingResource {
   private static CountingIdlingResource mCountingIdlingResource =
      new CountingIdlingResource("my_idling_resource");
   public static void increment() {
      mCountingIdlingResource.increment();
   }
   public static void decrement() {
      mCountingIdlingResource.decrement();
   }
   public static IdlingResource getIdlingResource() {
      return mCountingIdlingResource;
   }
}
  • Déclarez une variable globale, mIdlingResource de type CountingIdlingResource dans la classe MainActivity comme ci-dessous,

@Nullable
private CountingIdlingResource mIdlingResource = null;
  • Écrivez une méthode privée pour récupérer la liste des fruits sur le Web comme ci-dessous,

private ArrayList<String> getFruitList(String data) {
   ArrayList<String> fruits = new ArrayList<String>();
   try {
      // Get url from async task and set it into a local variable
      URL url = new URL(data);
      Log.e("URL", url.toString());
      
      // Create new HTTP connection
      HttpURLConnection conn = (HttpURLConnection) url.openConnection();
      
      // Set HTTP connection method as "Get"
      conn.setRequestMethod("GET");
      
      // Do a http request and get the response code
      int responseCode = conn.getResponseCode();
      
      // check the response code and if success, get response content
      if (responseCode == HttpURLConnection.HTTP_OK) {
         BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream()));
         String line;
         StringBuffer response = new StringBuffer();
         while ((line = in.readLine()) != null) {
            response.append(line);
         }
         in.close();
         JSONArray jsonArray = new JSONArray(response.toString());
         Log.e("HTTPResponse", response.toString());
         for(int i = 0; i < jsonArray.length(); i++) {
            JSONObject jsonObject = jsonArray.getJSONObject(i);
            String name = String.valueOf(jsonObject.getString("name"));
            fruits.add(name);
         }
      } else {
         throw new IOException("Unable to fetch data from url");
      }
      conn.disconnect();
   } catch (IOException | JSONException e) {
      e.printStackTrace();
   }
   return fruits;
}
  • Créez une nouvelle tâche dans la méthode onCreate () pour récupérer les données sur le Web à l'aide de notre méthode getFruitList suivie de la création d'un nouvel adaptateur et de sa configuration en vue liste. De plus, décrémentez la ressource inactive une fois notre travail terminé dans le thread. Le code est comme suit,

// Get data
class FruitTask implements Runnable {
   ListView listView;
   CountingIdlingResource idlingResource;
   FruitTask(CountingIdlingResource idlingRes, ListView listView) {
      this.listView = listView;
      this.idlingResource = idlingRes;
   }
   public void run() {
      //code to do the HTTP request
      final ArrayList<String> fruitList = getFruitList("http://<your domain or IP>/fruits.json");
      try {
         synchronized (this){
            runOnUiThread(new Runnable() {
               @Override
               public void run() {
                  // Create adapter and set it to list view
                  final ArrayAdapter adapter = new
                     ArrayAdapter(MainActivity.this, R.layout.item, fruitList);
                  ListView listView = (ListView)findViewById(R.id.listView);
                  listView.setAdapter(adapter);
               }
            });
         }
      } catch (Exception e) {
         e.printStackTrace();
      }
      if (!MyIdlingResource.getIdlingResource().isIdleNow()) {
         MyIdlingResource.decrement(); // Set app as idle.
      }
   }
}

Ici, l'URL du fruit est considérée comme http: // <votre domaine ou IP / fruits.json et elle est formatée en JSON. Le contenu est le suivant,

[ 
   {
      "name":"Apple"
   },
   {
      "name":"Banana"
   },
   {
      "name":"Cherry"
   },
   {
      "name":"Dates"
   },
   {
      "name":"Elderberry"
   },
   {
      "name":"Fig"
   },
   {
      "name":"Grapes"
   },
   {
      "name":"Grapefruit"
   },
   {
      "name":"Guava"
   },
   {
      "name":"Jack fruit"
   },
   {
      "name":"Lemon"
   },
   {
      "name":"Mango"
   },
   {
      "name":"Orange"
   },
   {
      "name":"Papaya"
   },
   {
      "name":"Pears"
   },
   {
      "name":"Peaches"
   },
   {
      "name":"Pineapple"
   },
   {
      "name":"Plums"
   },
   {
      "name":"Raspberry"
   },
   {
      "name":"Strawberry"
   },
   {
      "name":"Watermelon"
   }
]

Note - Placez le fichier sur votre serveur Web local et utilisez-le.

  • Maintenant, trouvez la vue, créez un nouveau thread en passant FruitTask , incrémentez la ressource inactive et enfin lancez la tâche.

// Find list view
ListView listView = (ListView) findViewById(R.id.listView);
Thread fruitTask = new Thread(new FruitTask(this.mIdlingResource, listView));
MyIdlingResource.increment();
fruitTask.start();
  • Le code complet de MainActivity est le suivant,

package com.tutorialspoint.espressosamples.myidlingfruitapp;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.appcompat.app.AppCompatActivity;
import androidx.test.espresso.idling.CountingIdlingResource;

import android.os.Bundle;
import android.util.Log;
import android.widget.ArrayAdapter;
import android.widget.ListView;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;

public class MainActivity extends AppCompatActivity {
   @Nullable
   private CountingIdlingResource mIdlingResource = null;
   @Override
   protected void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      setContentView(R.layout.activity_main);
      
      // Get data
      class FruitTask implements Runnable {
         ListView listView;
         CountingIdlingResource idlingResource;
         FruitTask(CountingIdlingResource idlingRes, ListView listView) {
            this.listView = listView;
            this.idlingResource = idlingRes;
         }
         public void run() {
            //code to do the HTTP request
            final ArrayList<String> fruitList = getFruitList(
               "http://<yourdomain or IP>/fruits.json");
            try {
               synchronized (this){
                  runOnUiThread(new Runnable() {
                     @Override
                     public void run() {
                        // Create adapter and set it to list view
                        final ArrayAdapter adapter = new ArrayAdapter(
                           MainActivity.this, R.layout.item, fruitList);
                        ListView listView = (ListView) findViewById(R.id.listView);
                        listView.setAdapter(adapter);
                     }
                  });
               }
            } catch (Exception e) {
               e.printStackTrace();
            }
            if (!MyIdlingResource.getIdlingResource().isIdleNow()) {
               MyIdlingResource.decrement(); // Set app as idle.
            }
         }
      }
      // Find list view
      ListView listView = (ListView) findViewById(R.id.listView);
      Thread fruitTask = new Thread(new FruitTask(this.mIdlingResource, listView));
      MyIdlingResource.increment();
      fruitTask.start();
   }
   private ArrayList<String> getFruitList(String data) {
      ArrayList<String> fruits = new ArrayList<String>();
      try {
         // Get url from async task and set it into a local variable
         URL url = new URL(data);
         Log.e("URL", url.toString());
         
         // Create new HTTP connection
         HttpURLConnection conn = (HttpURLConnection) url.openConnection();
         
         // Set HTTP connection method as "Get"
         conn.setRequestMethod("GET");
         
         // Do a http request and get the response code
         int responseCode = conn.getResponseCode();
         
         // check the response code and if success, get response content
         if (responseCode == HttpURLConnection.HTTP_OK) {
            BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream()));
            String line;
            StringBuffer response = new StringBuffer();
            while ((line = in.readLine()) != null) {
               response.append(line);
            }
            in.close();
            JSONArray jsonArray = new JSONArray(response.toString());
            Log.e("HTTPResponse", response.toString());
            
            for(int i = 0; i < jsonArray.length(); i++) {
               JSONObject jsonObject = jsonArray.getJSONObject(i);
               String name = String.valueOf(jsonObject.getString("name"));
               fruits.add(name);
            }
         } else {
            throw new IOException("Unable to fetch data from url");
         }
         conn.disconnect();
      } catch (IOException | JSONException e) {
         e.printStackTrace();
      }
      return fruits;
   }
}
  • Maintenant, ajoutez la configuration ci-dessous dans le fichier manifeste de l'application, AndroidManifest.xml

<uses-permission android:name = "android.permission.INTERNET" />
  • Maintenant, compilez le code ci-dessus et exécutez l'application. La capture d'écran de l'application My Idling Fruit est la suivante,

  • Maintenant, ouvrez le fichier ExampleInstrumentedTest.java et ajoutez ActivityTestRule comme spécifié ci-dessous,

@Rule
public ActivityTestRule<MainActivity> mActivityRule = 
   new ActivityTestRule<MainActivity>(MainActivity.class);
Also, make sure the test configuration is done in app/build.gradle
dependencies {
   testImplementation 'junit:junit:4.12'
   androidTestImplementation 'androidx.test:runner:1.1.1'
   androidTestImplementation 'androidx.test:rules:1.1.1'
   androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
   implementation 'androidx.test.espresso:espresso-idling-resource:3.1.1'
   androidTestImplementation "androidx.test.espresso.idling:idlingconcurrent:3.1.1"
}
  • Ajoutez un nouveau cas de test pour tester la vue de liste comme ci-dessous,

@Before
public void registerIdlingResource() {
   IdlingRegistry.getInstance().register(MyIdlingResource.getIdlingResource());
}
@Test
public void contentTest() {
   // click a child item
   onData(allOf())
   .inAdapterView(withId(R.id.listView))
   .atPosition(10)
   .perform(click());
}
@After
public void unregisterIdlingResource() {
   IdlingRegistry.getInstance().unregister(MyIdlingResource.getIdlingResource());
}
  • Enfin, exécutez le scénario de test à l'aide du menu contextuel d'Android Studio et vérifiez si tous les scénarios de test réussissent.

Android Intent est utilisé pour ouvrir une nouvelle activité, soit interne (ouverture d'un écran de détail de produit à partir de l'écran de liste de produits) soit externe (comme l'ouverture d'un composeur pour passer un appel). L'activité d'intention interne est gérée de manière transparente par le cadre de test espresso et ne nécessite aucun travail spécifique de la part de l'utilisateur. Cependant, invoquer une activité externe est vraiment un défi car cela sort de notre champ d'application, l'application testée. Une fois que l'utilisateur appelle une application externe et sort de l'application testée, les chances que l'utilisateur revienne à l'application avec une séquence d'actions prédéfinie sont plutôt moindres. Par conséquent, nous devons assumer l'action de l'utilisateur avant de tester l'application. Espresso propose deux options pour gérer cette situation. Ils sont comme suit,

prévu

Cela permet à l'utilisateur de s'assurer que l'intention correcte est ouverte à partir de l'application testée.

l'intention

Cela permet à l'utilisateur de se moquer d'une activité externe comme prendre une photo de l'appareil photo, composer un numéro à partir de la liste de contacts, etc., et revenir à l'application avec un ensemble de valeurs prédéfinies (comme l'image prédéfinie de l'appareil photo au lieu de l'image réelle) .

Installer

Espresso prend en charge l'option d'intention via une bibliothèque de plugins et la bibliothèque doit être configurée dans le fichier gradle de l'application. L'option de configuration est la suivante,

dependencies {
   // ...
   androidTestImplementation 'androidx.test.espresso:espresso-intents:3.1.1'
}

prévu()

Le plug-in d'intention Espresso fournit des correspondances spéciales pour vérifier si l'intention invoquée est l'intention attendue. Les matchers fournis et le but des matchers sont les suivants,

hasAction

Cela accepte l'action d'intention et renvoie un matcher, qui correspond à l'intention spécifiée.

hasData

Cela accepte les données et renvoie un matcher, qui fait correspondre les données fournies à l'intention lors de son appel.

toPackage

Cela accepte le nom du package d'intention et renvoie un matcher, qui correspond au nom du package de l'intention invoquée.

Maintenant, créons une nouvelle application et testons l'application pour une activité externe en utilisant intent () pour comprendre le concept.

  • Démarrez le studio Android.

  • Créez un nouveau projet comme indiqué précédemment et nommez-le IntentSampleApp.

  • Migrez l'application vers le framework AndroidX à l'aide de Refactor → Migrer vers le menu d'options AndroidX .

  • Créez une zone de texte, un bouton pour ouvrir la liste de contacts et un autre pour passer un appel en modifiant le activity_main.xml comme indiqué ci-dessous,

<?xml version = "1.0" encoding = "utf-8"?>
<RelativeLayout xmlns:android = "http://schemas.android.com/apk/res/android"
   xmlns:app = "http://schemas.android.com/apk/res-auto"
   xmlns:tools = "http://schemas.android.com/tools"
   android:layout_width = "match_parent"
   android:layout_height = "match_parent"
   tools:context = ".MainActivity">
   <EditText
      android:id = "@+id/edit_text_phone_number"
      android:layout_width = "wrap_content"
      android:layout_height = "wrap_content"
      android:layout_centerHorizontal = "true"
      android:text = ""
      android:autofillHints = "@string/phone_number"/>
   <Button
      android:id = "@+id/call_contact_button"
      android:layout_width = "wrap_content"
      android:layout_height = "wrap_content"
      android:layout_centerHorizontal = "true"
      android:layout_below = "@id/edit_text_phone_number"
      android:text = "@string/call_contact"/>
   <Button
      android:id = "@+id/button"
      android:layout_width = "wrap_content"
      android:layout_height = "wrap_content"
      android:layout_centerHorizontal = "true"
      android:layout_below = "@id/call_contact_button"
      android:text = "@string/call"/>
</RelativeLayout>
  • Ajoutez également l'élément ci-dessous dans le fichier de ressources strings.xml ,

<string name = "phone_number">Phone number</string>
<string name = "call">Call</string>
<string name = "call_contact">Select from contact list</string>
  • Maintenant, ajoutez le code ci-dessous dans l'activité principale ( MainActivity.java ) sous la méthode onCreate .

public class MainActivity extends AppCompatActivity {
   @Override
   protected void onCreate(Bundle savedInstanceState) {
      // ... code
      // Find call from contact button
      Button contactButton = (Button) findViewById(R.id.call_contact_button);
      contactButton.setOnClickListener(new View.OnClickListener() {
         @Override
         public void onClick(View view) {
            // Uri uri = Uri.parse("content://contacts");
            Intent contactIntent = new Intent(Intent.ACTION_PICK,
               ContactsContract.Contacts.CONTENT_URI);
            contactIntent.setType(ContactsContract.CommonDataKinds.Phone.CONTENT_TYPE);
            startActivityForResult(contactIntent, REQUEST_CODE);
         }
      });
      // Find edit view
      final EditText phoneNumberEditView = (EditText)
         findViewById(R.id.edit_text_phone_number);
      // Find call button
      Button button = (Button) findViewById(R.id.button);
      button.setOnClickListener(new View.OnClickListener() {
         @Override
         public void onClick(View view) {
            if(phoneNumberEditView.getText() != null) {
               Uri number = Uri.parse("tel:" + phoneNumberEditView.getText());
               Intent callIntent = new Intent(Intent.ACTION_DIAL, number);
               startActivity(callIntent);
            }
         }
      });
   }
   // ... code
}

Ici, nous avons programmé le bouton avec id, call_contact_button pour ouvrir la liste de contacts et bouton avec id, bouton pour composer l'appel.

  • Ajoutez une variable statique REQUEST_CODE dans la classe MainActivity comme indiqué ci-dessous,

public class MainActivity extends AppCompatActivity {
   // ...
   private static final int REQUEST_CODE = 1;
   // ...
}
  • Maintenant, ajoutez la méthode onActivityResult dans la classe MainActivity comme ci-dessous,

public class MainActivity extends AppCompatActivity {
   // ...
   @Override
   protected void onActivityResult(int requestCode, int resultCode, Intent data) {
      if (requestCode == REQUEST_CODE) {
         if (resultCode == RESULT_OK) {
            // Bundle extras = data.getExtras();
            // String phoneNumber = extras.get("data").toString();
            Uri uri = data.getData();
            Log.e("ACT_RES", uri.toString());
            String[] projection = {
               ContactsContract.CommonDataKinds.Phone.NUMBER, 
               ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME };
            Cursor cursor = getContentResolver().query(uri, projection, null, null, null);
            cursor.moveToFirst();
            
            int numberColumnIndex =
               cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER);
            String number = cursor.getString(numberColumnIndex);
            
            int nameColumnIndex = cursor.getColumnIndex(
               ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME);
            String name = cursor.getString(nameColumnIndex);
            Log.d("MAIN_ACTIVITY", "Selected number : " + number +" , name : "+name);
            
            // Find edit view
            final EditText phoneNumberEditView = (EditText)
               findViewById(R.id.edit_text_phone_number);
            phoneNumberEditView.setText(number);
         }
      }
   };
   // ...
}

Ici, onActivityResult sera appelé lorsqu'un utilisateur revient dans l'application après avoir ouvert la liste de contacts à l'aide du bouton call_contact_button et sélectionné un contact. Une fois que la méthode onActivityResult est appelée, elle obtient le contact sélectionné par l'utilisateur, trouve le numéro de contact et le définit dans la zone de texte.

  • Exécutez l'application et assurez-vous que tout va bien. L'aspect final de l' exemple d'application Intent est illustré ci-dessous,

  • Maintenant, configurez l'intention espresso dans le fichier gradle de l'application comme indiqué ci-dessous,

dependencies {
   // ...
   androidTestImplementation 'androidx.test.espresso:espresso-intents:3.1.1'
}
  • Cliquez sur l' option de menu Synchroniser maintenant fournie par Android Studio. Cela téléchargera la bibliothèque de test d'intention et la configurera correctement.

  • Ouvrez ExampleInstrumentedTest.java fichier et ajoutez le IntentsTestRule au lieu de normalement utilisé AndroidTestRule . IntentTestRule est une règle spéciale pour gérer les tests d'intention.

public class ExampleInstrumentedTest {
   // ... code
   @Rule
   public IntentsTestRule<MainActivity> mActivityRule =
   new IntentsTestRule<>(MainActivity.class);
   // ... code
}
  • Ajoutez deux variables locales pour définir le numéro de téléphone de test et le nom du package du numéroteur comme ci-dessous,

public class ExampleInstrumentedTest {
   // ... code
   private static final String PHONE_NUMBER = "1 234-567-890";
   private static final String DIALER_PACKAGE_NAME = "com.google.android.dialer";
   // ... code
}
  • Résolvez les problèmes d'importation en utilisant l'option Alt + Entrée fournie par Android Studio ou incluez les instructions d'importation ci-dessous,

import android.content.Context;
import android.content.Intent;

import androidx.test.InstrumentationRegistry;
import androidx.test.espresso.intent.rule.IntentsTestRule;
import androidx.test.runner.AndroidJUnit4;

import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;

import static androidx.test.espresso.Espresso.onView;
import static androidx.test.espresso.action.ViewActions.click;
import static androidx.test.espresso.action.ViewActions.closeSoftKeyboard;
import static androidx.test.espresso.action.ViewActions.typeText;
import static androidx.test.espresso.intent.Intents.intended;
import static androidx.test.espresso.intent.matcher.IntentMatchers.hasAction;
import static androidx.test.espresso.intent.matcher.IntentMatchers.hasData;
import static androidx.test.espresso.intent.matcher.IntentMatchers.toPackage;
import static androidx.test.espresso.matcher.ViewMatchers.withId;
import static org.hamcrest.core.AllOf.allOf;
import static org.junit.Assert.*;
  • Ajoutez le cas de test ci-dessous pour tester si le numéroteur est correctement appelé,

public class ExampleInstrumentedTest {
   // ... code
   @Test
   public void validateIntentTest() {
      onView(withId(R.id.edit_text_phone_number))
         .perform(typeText(PHONE_NUMBER), closeSoftKeyboard());
      onView(withId(R.id.button)) .perform(click());
      intended(allOf(
         hasAction(Intent.ACTION_DIAL),
         hasData("tel:" + PHONE_NUMBER),
         toPackage(DIALER_PACKAGE_NAME)));
   }
   // ... code
}

Ici, les matchers hasAction , hasData et toPackage sont utilisés avec le matcher allOf pour réussir uniquement si tous les matchers sont passés.

  • Maintenant, exécutez le ExampleInstrumentedTest via le menu de contenu dans le studio Android.

intention ()

Espresso fournit une méthode spéciale - l' intention () de simuler une action d'intention externe. intending () accepte le nom du package de l'intention à se moquer et fournit une méthode respondWith pour définir la manière dont l'intention fictive doit être traitée comme spécifié ci-dessous,

intending(toPackage("com.android.contacts")).respondWith(result);

Ici, respondWith () accepte le résultat d'intention de type Instrumentation.ActivityResult . Nous pouvons créer une nouvelle intention de stub et définir manuellement le résultat comme indiqué ci-dessous,

// Stub intent
Intent intent = new Intent();
intent.setData(Uri.parse("content://com.android.contacts/data/1"));
Instrumentation.ActivityResult result =
   new Instrumentation.ActivityResult(Activity.RESULT_OK, intent);

Le code complet pour tester si une application de contact est correctement ouverte est le suivant,

@Test
public void stubIntentTest() {
   // Stub intent
   Intent intent = new Intent();
   intent.setData(Uri.parse("content://com.android.contacts/data/1"));
   Instrumentation.ActivityResult result =
      new Instrumentation.ActivityResult(Activity.RESULT_OK, intent);
   intending(toPackage("com.android.contacts")).respondWith(result);
   
   // find the button and perform click action
   onView(withId(R.id.call_contact_button)).perform(click());
   
   // get context
   Context targetContext2 = InstrumentationRegistry.getInstrumentation().getTargetContext();
   
   // get phone number
   String[] projection = { ContactsContract.CommonDataKinds.Phone.NUMBER,
      ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME };
   Cursor cursor =
      targetContext2.getContentResolver().query(Uri.parse("content://com.android.cont
      acts/data/1"), projection, null, null, null);
   
   cursor.moveToFirst();
   int numberColumnIndex =
      cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER);
   String number = cursor.getString(numberColumnIndex);
   
   // now, check the data
   onView(withId(R.id.edit_text_phone_number))
   .check(matches(withText(number)));
}

Ici, nous avons créé un nouvel intent et défini la valeur de retour (lors de l'appel de l'intention) comme première entrée de la liste de contacts, content: //com.android.contacts/data/1 . Ensuite, nous avons défini la méthode d' intention pour simuler l'intention nouvellement créée à la place de la liste de contacts. Il définit et appelle notre intention nouvellement créée lorsque le package, com.android.contacts est appelé et que la première entrée par défaut de la liste est renvoyée. Ensuite, nous avons déclenché l' action click () pour démarrer l'intention fictive et enfin vérifier si le numéro de téléphone de l'appel de l'intention fictive et le numéro de la première entrée dans la liste de contacts sont identiques.

S'il y a un problème d'importation manquant, corrigez ces problèmes d'importation en utilisant l'option Alt + Entrée fournie par android studio ou incluez les instructions d'importation ci-dessous,

import android.app.Activity;
import android.app.Instrumentation;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.provider.ContactsContract;

import androidx.test.InstrumentationRegistry;
import androidx.test.espresso.ViewInteraction;
import androidx.test.espresso.intent.rule.IntentsTestRule;
import androidx.test.runner.AndroidJUnit4;

import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;

import static androidx.test.espresso.Espresso.onView;
import static androidx.test.espresso.action.ViewActions.click;
import static androidx.test.espresso.action.ViewActions.closeSoftKeyboard;
import static androidx.test.espresso.action.ViewActions.typeText;
import static androidx.test.espresso.assertion.ViewAssertions.matches;
import static androidx.test.espresso.intent.Intents.intended;
import static androidx.test.espresso.intent.Intents.intending;
import static androidx.test.espresso.intent.matcher.IntentMatchers.hasAction;
import static androidx.test.espresso.intent.matcher.IntentMatchers.hasData;
import static androidx.test.espresso.intent.matcher.IntentMatchers.toPackage;
import static androidx.test.espresso.matcher.ViewMatchers.withId;
import static androidx.test.espresso.matcher.ViewMatchers.withText;
import static org.hamcrest.core.AllOf.allOf;
import static org.junit.Assert.*;

Ajoutez la règle ci-dessous dans la classe de test pour fournir l'autorisation de lire la liste de contacts -

@Rule
public GrantPermissionRule permissionRule =
GrantPermissionRule.grant(Manifest.permission.READ_CONTACTS);

Ajoutez l'option ci-dessous dans le fichier manifeste de l'application, AndroidManifest.xml -

<uses-permission android:name = "android.permission.READ_CONTACTS" />

Maintenant, assurez-vous que la liste de contacts contient au moins une entrée, puis exécutez le test à l'aide du menu contextuel d'Android Studio.

Android prend en charge les tests d'interface utilisateur impliquant plusieurs applications. Considérons que notre application a la possibilité de passer de notre application à une application de messagerie pour envoyer un message, puis revient à notre application. Dans ce scénario, le cadre de test d'automate d'interface utilisateur nous aide à tester l'application. L'automate d'interface utilisateur peut être considéré comme un bon compagnon pour le cadre de test d'espresso. Nous pouvons exploiter l' option intending () dans le cadre de test espresso avant d'opter pour l' automate d'interface utilisateur .

Instruction de configuration

Android fournit un automate d'interface utilisateur en tant que plugin séparé. Il doit être configuré dans l' app / build.gradle comme spécifié ci-dessous,

dependencies {
   ...
   androidTestImplementation 'androidx.test.uiautomator:uiautomator:2.2.0'
}

Workflow pour l'écriture de cas de test

Laissez-nous comprendre comment écrire un cas de test basé sur UI Automator ,

  • Obtenez l' objet UiDevice en appelant la méthode getInstance () et en passant l' objet Instrumentation .

myDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
myDevice.pressHome();
  • Obtenez l' objet UiObject à l'aide de la méthode findObject () . Avant d'utiliser cette méthode, nous pouvons ouvrir l' application uiautomatorviewer pour inspecter les composants de l'interface utilisateur de l'application cible, car la compréhension de l'application cible nous permet d'écrire de meilleurs cas de test.

UiObject button = myDevice.findObject(new UiSelector()
   .text("Run")
   .className("android.widget.Button"));
  • Simulez l'interaction de l'utilisateur en appelant la méthode d'UiObject . Par exemple, setText () pour modifier un champ de texte et cliquez sur () pour déclencher un événement de clic sur un bouton.

if(button.exists() && button.isEnabled()) {
   button.click();
}
  • Enfin, nous vérifions si l'interface utilisateur reflète l'état attendu.

La rédaction d'un cas de test est un travail fastidieux. Même si espresso fournit une API très simple et flexible, l'écriture de cas de test peut être une tâche paresseuse et longue. Pour surmonter cela, le studio Android fournit une fonctionnalité permettant d'enregistrer et de générer des cas de test d'espresso. Record Espresso Test est disponible dans le menu Run .

Enregistrons un cas de test simple dans notre HelloWorldApp en suivant les étapes décrites ci-dessous,

  • Ouvrez le studio Android suivi de l' application HelloWorldApp .

  • Cliquez sur ExécuterEnregistrer le test Espresso et sélectionnez MainActivity .

  • La capture d'écran de l' enregistreur est la suivante,

  • Cliquez sur Ajouter une assertion . Cela ouvrira l'écran de l'application comme indiqué ci-dessous,

  • Cliquez sur Hello World! . L' écran de l' enregistreur pour sélectionner la vue texte est le suivant,

  • Cliquez à nouveau sur Enregistrer l'assertion Cela enregistrera l'assertion et l'affichera comme suit,

  • Cliquez sur OK . Il ouvrira une nouvelle fenêtre et demandera le nom du cas de test. Le nom par défaut est MainActivityTest

  • Modifiez le nom du scénario de test, si nécessaire.

  • Encore une fois, cliquez sur OK . Cela générera un fichier, MainActivityTest avec notre scénario de test enregistré. Le codage complet est le suivant,

package com.tutorialspoint.espressosamples.helloworldapp;

import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;

import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.hamcrest.TypeSafeMatcher;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;

import androidx.test.espresso.ViewInteraction;
import androidx.test.filters.LargeTest;
import androidx.test.rule.ActivityTestRule;
import androidx.test.runner.AndroidJUnit4;

import static androidx.test.espresso.Espresso.onView;
import static androidx.test.espresso.assertion.ViewAssertions.matches;
import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
import static androidx.test.espresso.matcher.ViewMatchers.withId;
import static androidx.test.espresso.matcher.ViewMatchers.withText;
import static org.hamcrest.Matchers.allOf;

@LargeTest
@RunWith(AndroidJUnit4.class)
public class MainActivityTest {
   @Rule
   public ActivityTestRule<MainActivity> mActivityTestRule = new ActivityTestRule<>(MainActivity.class);
   @Test
   public void mainActivityTest() {
      ViewInteraction textView = onView(
         allOf(withId(R.id.textView_hello), withText("Hello World!"),
         childAtPosition(childAtPosition(withId(android.R.id.content),
         0),0),isDisplayed()));
      textView.check(matches(withText("Hello World!")));
   }
   private static Matcher<View> childAtPosition(
      final Matcher<View> parentMatcher, final int position) {
      return new TypeSafeMatcher<View>() {
         @Override
         public void describeTo(Description description) {
            description.appendText("Child at position " + position + " in parent ");
            parentMatcher.describeTo(description);
         }
         @Override
         public boolean matchesSafely(View view) {
            ViewParent parent = view.getParent();
            return parent instanceof ViewGroup &&
               parentMatcher.matches(parent)&& view.equals(((ViewGroup)
               parent).getChildAt(position));
         }
      };
   }
}
  • Enfin, exécutez le test à l'aide du menu contextuel et vérifiez si le scénario de test est exécuté.

L'expérience utilisateur positive joue un rôle très important dans le succès d'une application. L'expérience utilisateur implique non seulement de belles interfaces utilisateur, mais aussi la rapidité avec laquelle ces belles interfaces utilisateur sont rendues et quel est le taux d'images par seconde. L'interface utilisateur doit fonctionner de manière cohérente à 60 images par seconde pour offrir une bonne expérience utilisateur.

Apprenons quelques-unes des options disponibles dans Android pour analyser les performances de l'interface utilisateur dans ce chapitre.

décharges

dumpsys est un outil intégré disponible dans l'appareil Android. Il produit des informations actuelles sur les services système. dumpsys a la possibilité de vider des informations sur une catégorie particulière. Passer gfxinfo fournira des informations d'animation du paquet fourni. La commande est la suivante,

> adb shell dumpsys gfxinfo <PACKAGE_NAME>

framestats

framestats est une option de la commande dumpsys. Une fois que dumpsys est appelé avec framestats , il videra les informations détaillées de synchronisation des images récentes. La commande est la suivante,

> adb shell dumpsys gfxinfo <PACKAGE_NAME> framestats

Il sort les informations au format CSV (valeurs séparées par des virgules). La sortie au format CSV permet de pousser facilement les données dans Excel et d'extraire ensuite des informations utiles via des formules et des graphiques Excel.

systrace

systrace est également un outil intégré disponible dans l'appareil Android. Il capture et affiche les temps d'exécution des processus d'application. systrace peut être exécuté à l'aide de la commande ci-dessous dans le terminal du studio Android,

python %ANDROID_HOME%/platform-tools/systrace/systrace.py --time=10 -o
my_trace_output.html gfx view res

La fonction d'accessibilité est l'une des fonctionnalités clés de toute application. L'application développée par un fournisseur doit prendre en charge les directives d'accessibilité minimales définies par le SDK Android pour être une application réussie et utile. Suivre la norme d'accessibilité est très important et ce n'est pas une tâche facile. Android SDK fournit une excellente prise en charge en fournissant des vues correctement conçues pour créer des interfaces utilisateur accessibles.

De même, le cadre de test Espresso fait une grande faveur à la fois au développeur et à l'utilisateur final en prenant en charge de manière transparente les fonctionnalités de test d'accessibilité dans le moteur de test de base.

Dans Espresso, un développeur peut activer et configurer les tests d'accessibilité via la classe AccessibilityChecks . L'exemple de code est le suivant,

AccessibilityChecks.enable();

Par défaut, les contrôles d'accessibilité s'exécutent lorsque vous effectuez une action d'affichage. La vérification inclut la vue sur laquelle l'action est effectuée ainsi que toutes les vues descendantes. Vous pouvez vérifier toute la hiérarchie des vues d'un écran à l'aide du code suivant -

AccessibilityChecks.enable().setRunChecksFromRootView(true);

Conclusion

Espresso est un excellent outil pour les développeurs Android pour tester complètement leur application d'une manière très simple et sans mettre d'efforts supplémentaires normalement requis par un cadre de test. Il a même un enregistreur pour créer un cas de test sans écrire le code manuellement. De plus, il prend en charge tous les types de tests d'interface utilisateur. En utilisant le cadre de test d'espresso, un développeur Android peut développer en toute confiance une application de qualité ainsi qu'une application réussie sans aucun problème dans un court laps de temps.