Bibliothèque de test des actions dans les applications

La bibliothèque de test des actions dans les applications (AATL) permet aux développeurs de tester le traitement des actions dans les applications par programmation, en automatisant les tests normalement effectués à l'aide de requêtes vocales réelles ou via l'outil de test des actions dans les applications.

Cette bibliothèque permet de s'assurer que la configuration shortcut.xml est correcte et que l'appel d'intent Android décrit aboutit. La bibliothèque de test des actions dans les applications fournit un mécanisme permettant de tester la capacité de votre application à traiter certains intents et paramètres de l'Assistant Google, en les convertissant en lien profond ou en intent Android, pouvant faire l'objet d'une assertion ou être utilisé pour instancier une activité Android.

Les tests sont effectués sous la forme de tests unitaires Robolectric ou de tests d'instrumentation dans l'environnement Android. Cela permet aux développeurs de tester de manière exhaustive leur application en émulant son comportement réel. Pour tester le traitement des intents intégrés, des intents personnalisés ou des liens profonds, vous pouvez utiliser n'importe quel framework de test d'instrumentation (UI Automator, Espresso, JUnit4, Appium, Detox, Calabash).

Si l'application est multilingue, les développeurs peuvent vérifier son fonctionnement dans différents paramètres régionaux.

Fonctionnement

Pour intégrer la bibliothèque de test des actions dans les applications, les développeurs doivent créer ou mettre à jour des tests Robolectric ou instrumentés existants au niveau du module app de l'application.

Le code de test contient les éléments suivants :

  • Initialisation de l'instance de bibliothèque, dans la méthode de configuration courante ou dans des scénarios de test individuels.
  • Chaque test appelle la méthode fulfill de l'instance de la bibliothèque pour générer le résultat de la création de l'intent.
  • Le développeur effectue ensuite une assertion du lien profond ou déclenche le traitement de l'application, puis exécute une validation personnalisée sur l'état de l'application.

Configuration requise

Pour utiliser la bibliothèque de test, vous devez configurer l'application avant de lui ajouter les tests.

Configuration

Pour utiliser la bibliothèque de test des actions dans les applications, assurez-vous que votre application est configurée comme suit :

  • Installez le plug-in Android Gradle (AGP).
  • Ajoutez un fichier shortcuts.xml dans le dossier res/xml du module app.
  • Assurez-vous qu'AndroidManifest.xml inclut <meta-data android:name="android.app.shortcuts" android:resource=”@xml/shortcuts” /> sous :
    • la balise <application>
    • la balise <activity> du lanceur
  • Placez l'élément <capability> dans l'élément <shortcuts> de shortcuts.xml.

Ajouter des dépendances de la bibliothèque de test des actions dans les applications

  1. Ajoutez le dépôt Google à la liste des dépôts de projets dans settings.gradle :

        allprojects {
            repositories {
                …
                google()
            }
        }
    
  2. Dans le fichier build.gradle du module d'application, ajoutez les dépendances AATL :

        androidTestImplementation 'com.google.assistant.appactions:testing:1.0.0'
    

    Veillez à utiliser le numéro de version de la bibliothèque que vous avez téléchargée.

Créer des tests d'intégration

  1. Créez des tests sous app/src/androidTest. Pour les tests Robolectric, créez-les sous app/src/test :

    Kotlin

      
        import android.content.Context
        import android.content.Intent
        import android.widget.TextView
        import androidx.test.core.app.ApplicationProvider
        import androidx.test.core.app.ActivityScenario
        import com.google.assistant.appactions.testing.aatl.AppActionsTestManager
        import com.google.assistant.appactions.testing.aatl.fulfillment.AppActionsFulfillmentIntentResult
        import com.google.assistant.appactions.testing.aatl.fulfillment.AppActionsFulfillmentResult
        import com.google.assistant.appactions.testing.aatl.fulfillment.FulfillmentType
        import com.google.common.collect.ImmutableMap
        import org.junit.Assert.assertEquals
        import org.junit.Before
        import org.junit.runner.RunWith
        import org.junit.Test
        import org.robolectric.RobolectricTestRunner
        …
        @Test
        fun IntentTestExample() {
          val intentParams = mapOf("feature" to "settings")
          val intentName = "actions.intent.OPEN_APP_FEATURE"
          val result = aatl.fulfill(intentName, intentParams)
    
          assertEquals(FulfillmentType.INTENT, result.getFulfillmentType())
    
          val intentResult = result as AppActionsFulfillmentIntentResult
          val intent = intentResult.intent
    
          // Developer can choose to assert different relevant properties of the returned intent, such as the action, activity, package, scheme and so on
          assertEquals("youtube", intent.scheme)
          assertEquals("settings", intent.getStringExtra("featureParam"))
          assertEquals("actions.intent.OPEN_APP_FEATURE", intent.action)
          assertEquals("com.google.android.youtube/.MainActivity",
              intent.component.flattenToShortString())
          assertEquals("com.google.myapp", intent.package)
    
          // Developers can choose to use returned Android Intent to launch and assess the activity. Below are examples for how it will look like for Robolectric and Espresso tests.
          // Please note that the below part is just a possible example of how Android tests are validating Activity functionality correctness for given Android Intent.
    
          // Robolectric example:
          val activity = Robolectric.buildActivity(MainActivity::class.java,
            intentResult.intent).create().resume().get()
    
          val title: TextView = activity.findViewById(R.id.startActivityTitle)
          assertEquals(title?.text?.toString(), "Launching…")
        }
      
    

    Java

      
        import android.content.Context;
        import android.content.Intent;
        import android.widget.TextView;
        import androidx.test.core.app.ApplicationProvider;
        import androidx.test.core.app.ActivityScenario;
        import com.google.assistant.appactions.testing.aatl.AppActionsTestManager;
        import com.google.assistant.appactions.testing.aatl.fulfillment.AppActionsFulfillmentIntentResult;
        import com.google.assistant.appactions.testing.aatl.fulfillment.AppActionsFulfillmentResult;
        import com.google.assistant.appactions.testing.aatl.fulfillment.FulfillmentType;
        import com.google.common.collect.ImmutableMap;
        import org.junit.Assert.assertEquals;
        import org.junit.Before;
        import org.junit.runner.RunWith;
        import org.junit.Test;
        import org.robolectric.RobolectricTestRunner;
        ...
        @Test
          public void IntentTestExample() throws Exception {
            Map<String, String> intentParams = ImmutableMap.of("feature", "settings");
            String intentName = "actions.intent.OPEN_APP_FEATURE";
            AppActionsFulfillmentResult result = aatl.fulfill(intentName, intentParams);
    
            assertEquals(FulfillmentType.INTENT, result.getFulfillmentType());
    
            AppActionsFulfillmentIntentResult intentResult = (AppActionsFulfillmentIntentResult) result;
    
            Intent intent = intentResult.getIntent();
    
            // Developer can choose to assert different relevant properties of the returned intent, such as the action, activity, package, or scheme
            assertEquals("settings", intent.getStringExtra("featureParam"));
            assertEquals("actions.intent.OPEN_APP_FEATURE", intent.getAction());
            assertEquals("com.google.android.youtube/.MainActivity", intent.getComponent().flattenToShortString());
            assertEquals("com.google.myapp", intent.getPackage());
    
            // Developers can choose to use returned Android Intent to launch and assess the   activity. Below are examples for how it will look like for Robolectric and  Espresso tests.
            // Please note that the below part is just a possible example of how Android tests are validating Activity functionality correctness for given Android Intent.
    
            // Robolectric example:
            MainActivity activity = Robolectric.buildActivity(MainActivity.class,intentResult.intent).create().resume().get();
    
            TextView title: TextView = activity.findViewById(R.id.startActivityTitle)
            assertEquals(title?.getText()?.toString(), "Launching…")
          }
      
    

    Si vous utilisez Espresso, vous devez modifier le mode de lancement de l'activité en fonction des résultats AATL. Voici un exemple d'utilisation d'Espresso à l'aide de la méthode ActivityScenario :

    Kotlin

        
        ActivityScenario.launch<MainActivity>(intentResult.intent);
        Espresso.onView(ViewMatchers.withId(R.id.startActivityTitle))
          .check(ViewAssertions.matches(ViewMatchers.withText("Launching…")))
        
    

    Java

        
          ActivityScenario.launch<MainActivity>(intentResult.intent);
          Espresso.onView(ViewMatchers.withId(R.id.startActivityTitle))
            .check(ViewAssertions.matches(ViewMatchers.withText("Launching…")))
        
    
  2. Assurez-vous que les propriétés de nom et de clé dans les mappages de paramètres correspondent aux paramètres de l'intent intégré. Par exemple, order.orderedItem.name correspond à la documentation du paramètre dans GET_ORDER.

  3. Instanciez une instance d'API avec le paramètre de contexte Android (obtenu à partir d'ApplicationProvider ou InstrumentationRegistry) :

    • Architecture d'application à module unique :

    Kotlin

        
          private lateinit var aatl: AppActionsTestManager
          @Before
          fun init() {
            val appContext = ApplicationProvider.getApplicationContext()
            aatl = AppActionsTestManager(appContext)
          }
        
      

    Java

        
          private AppActionsTestManager aatl;
    
          @Before
          public void init() {
            Context appContext = ApplicationProvider.getApplicationContext();
            aatl = new AppActionsTestManager(appContext);
          }
        
      
    • Architecture d'application à plusieurs modules :

    Kotlin

        
          private lateinit var aatl: AppActionsTestManager
    
          @Before
          fun init() {
            val appContext = ApplicationProvider.getApplicationContext()
            val lookupPackages = listOf("com.myapp.mainapp", "com.myapp.resources")
            aatl = AppActionsTestManager(appContext, lookupPackages)
          }
        
      

    Java

        
          private AppActionsTestManager aatl;
    
          @Before
          public void init() throws Exception {
    
            Context appContext = ApplicationProvider.getApplicationContext();
            List<String> lookupPackages = Arrays.asList("com.myapp.mainapp","com.myapp.resources");
            aatl = new AppActionsTestManager(appContext, Optional.of(lookupPackages));
          }
        
      
  4. Exécutez la méthode fulfill de l'API et obtenez l'objet AppActionsFulfillmentResult.

Effectuer des assertions

Voici la méthode recommandée pour effectuer des assertions sur la bibliothèque de test des actions dans les applications :

  1. Effectuez l'assertion du type de traitement d'AppActionsFulfillmentResult. Il doit s'agir de FulfillmentType.INTENT ou FulfillmentType.UNFULFILLED afin de tester le comportement de l'application en cas de requêtes d'intent intégré inattendues.
  2. Il existe deux types de traitement : INTENT et DEEPLINK.
    • Normalement, le développeur peut différencier les traitements INTENT et DEEPLINK en examinant la balise d'intent dans shortcuts.xml qu'il traite en déclenchant la bibliothèque.
    • Si une balise de modèle d'URL se trouve sous la balise d'intent, cela signifie que DEEPLINK répond à cet intent.
    • Si la méthode getData() de l'intent de résultat renvoie un objet non nul, cela indique également le traitement DEEPLINK. De même, si getData renvoie null, cela signifie qu'il s'agit d'un traitement INTENT.
  3. Dans le cas d'INTENT, convertissez AppActionsFulfillmentResult en AppActionsIntentFulfillmentResult, récupérez l'intent Android en appelant la méthode getIntent et effectuez l'une des opérations suivantes :
    • Effectuez l'assertion des champs individuels de l'intent Android.
    • Effectuez l'assertion de l'URI d'un intent accessible via la méthode intent.getData.getHost.
  4. Dans le cas de DEEPLINK, convertissez AppActionsFulfillmentResult en AppActionsIntentFulfillmentResult (comme pour le scénario INTENT ci-dessus), récupérez l'intent Android en appelant getIntent et effectuez l'assertion de l'URL du lien profond (accessible via intent.getData.getHost).
  5. Pour INTENT et DEEPLINK, vous pouvez utiliser l'intent obtenu pour lancer l'activité avec le framework de test Android choisi.

Internationalisation

Si votre application inclut plusieurs paramètres régionaux, vous pouvez configurer les tests pour qu'ils exécutent une langue spécifique. Vous pouvez également modifier directement la langue :

Kotlin

    
    import android.content.res.Configuration
    import java.util.Locale
    ...
    val newLocale = Locale("es")
    val conf = context.resources.configuration
    conf = Configuration(conf)
    conf.setLocale(newLocale)
    
  

Java

    
    Locale newLocale = new Locale("es");
    Configuration conf = context.getResources().getConfiguration();
    conf = new Configuration(conf);
    conf.setLocale(newLocale);
    
  

Voici un exemple de test AATL configuré pour l'espagnol (ES) :

Kotlin

      
      import com.google.common.truth.Truth.assertThat
      import org.junit.Assert.assertEquals
      import android.content.Context
      import android.content.res.Configuration
      import androidx.test.platform.app.InstrumentationRegistry
      import com.google.assistant.appactions.testing.aatl.AppActionsTestManager
      import com.google.assistant.appactions.testing.aatl.fulfillment.AppActionsFulfillmentIntentResult
      import com.google.assistant.appactions.testing.aatl.fulfillment.AppActionsFulfillmentResult
      import com.google.assistant.appactions.testing.aatl.fulfillment.FulfillmentType
      import com.google.common.collect.ImmutableMap
      import java.util.Locale
      import org.junit.Before
      import org.junit.Test
      import org.junit.runner.RunWith
      import org.robolectric.RobolectricTestRunner

      @RunWith(RobolectricTestRunner::class)
      class ShortcutForDifferentLocaleTest {

        @Before
        fun setUp() {
          val context = InstrumentationRegistry.getInstrumentation().getContext()

          // change the device locale to 'es'
          val newLocale = Locale("es")
          val conf = context.resources.configuration
          conf = Configuration(conf)
          conf.setLocale(newLocale)

          val localizedContext = context.createConfigurationContext(conf)
        }

        @Test
        fun shortcutForDifferentLocale_succeeds() {
          val aatl = AppActionsTestManager(localizedContext)
          val intentName = "actions.intent.ORDER_MENU_ITEM"
          val intentParams = ImmutableMap.of("menuItem.name", "hamburguesa con queso")

          val result = aatl.fulfill(intentName, intentParams)
          assertThat(result.getFulfillmentType()).isEqualTo(FulfillmentType.INTENT)

          val intentResult = result as AppActionsFulfillmentIntentResult

          assertThat(intentResult.getIntent().getData().toString())
            .isEqualTo("myfoodapp://browse?food=food_hamburger")
        }
      }
      
    

Java

      
      import static com.google.common.truth.Truth.assertThat;
      import static org.junit.Assert.assertEquals;

      import android.content.Context;
      import android.content.res.Configuration;
      import androidx.test.platform.app.InstrumentationRegistry;
      import com.google.assistant.appactions.testing.aatl.AppActionsTestManager;
      import com.google.assistant.appactions.testing.aatl.fulfillment.AppActionsFulfillmentIntentResult;
      import com.google.assistant.appactions.testing.aatl.fulfillment.AppActionsFulfillmentResult;
      import com.google.assistant.appactions.testing.aatl.fulfillment.FulfillmentType;
      import com.google.common.collect.ImmutableMap;
      import java.util.Locale;
      import org.junit.Before;
      import org.junit.Test;
      import org.junit.runner.RunWith;
      import org.robolectric.RobolectricTestRunner;

      @Test
      public void shortcutForDifferentLocale_succeeds() throws Exception {
        Context context = InstrumentationRegistry.getInstrumentation().getContext();

        // change the device locale to 'es'
        Locale newLocale = new Locale("es");
        Configuration conf = context.getResources().getConfiguration();
        conf = new Configuration(conf);
        conf.setLocale(newLocale);

        Context localizedContext = context.createConfigurationContext(conf);

        AppActionsTestManager aatl = new AppActionsTestManager(localizedContext);
        String intentName = "actions.intent.ORDER_MENU_ITEM";
        ImmutableMap<String, String> intentParams = ImmutableMap.of("menuItem.name", "hamburguesa con queso");

        AppActionsFulfillmentResult result = aatl.fulfill(intentName, intentParams);
        assertThat(result.getFulfillmentType()).isEqualTo(FulfillmentType.INTENT);

        AppActionsFulfillmentIntentResult intentResult = (AppActionsFulfillmentIntentResult) result;

        assertThat(intentResult.getIntent().getData().toString())
          .isEqualTo("myfoodapp://browse?food=food_hamburger");
      }
      
    

Dépannage

Si votre test d'intégration échoue de manière inattendue, vous pouvez rechercher les messages de journal AATL dans la fenêtre Logcat d'Android Studio afin de récupérer l'avertissement ou le message d'erreur. Vous pouvez également augmenter le niveau de journalisation pour capturer plus de sorties de la bibliothèque.

Limites

Voici les limites actuelles de la bibliothèque de test des actions dans les applications :

  • AATL ne teste pas les fonctionnalités de compréhension du langage naturel (NLU) ni de reconnaissance vocale.
  • AATL ne fonctionne pas lorsque les tests se trouvent dans des modules autres que le module d'application par défaut.
  • AATL n'est compatible qu'avec Android 7.0 "Nougat" (niveau d'API 24) ou version ultérieure.