Biblioteca de pruebas de Acciones en apps

La biblioteca de pruebas de Acciones en apps (AATL) permite a los desarrolladores probar la entrega de Acciones en apps de manera programática, lo que automatiza las pruebas que normalmente se harían con consultas de voz reales o la herramienta de pruebas de Acciones en apps.

La biblioteca ayuda a garantizar que la configuración shortcut.xml sea correcta y que la invocación de intents de Android descrita se realice de forma correcta. La biblioteca de pruebas de Acciones en apps proporciona un mecanismo para probar la capacidad de tu app para entregar el intent y los parámetros del Asistente de Google, mediante la conversión de estos en un vínculo directo o un intent de Android, que se puedan confirmar y usar para crear una instancia de actividad de Android.

Las pruebas se realizan en forma de pruebas de unidades Robolectric o de instrumentación en el entorno de Android. Esto permite a los desarrolladores probar aplicaciones de forma exhaustiva emulando su comportamiento real. Para probar BIIs, intents personalizados o la entrega de vínculos directos, se puede usar cualquier framework de pruebas de instrumentación (UI Automator, Espresso, JUnit4, Appium, Detox y Calabash).

Si la aplicación es multilingüe, los desarrolladores pueden validar que su funcionalidad se comporte correctamente en diferentes configuraciones regionales.

Cómo funciona

Para integrar la biblioteca de pruebas de Acciones en apps en el entorno de pruebas de la app, los desarrolladores deben crear pruebas de Robolectric o instrumentación, o bien actualizar las existentes en el módulo app de la app.

El código de prueba contiene las siguientes partes:

  • Inicialización de la instancia de la biblioteca, en el método de configuración común o en casos de prueba individuales.
  • Cada prueba individual llama al método fulfill de la instancia de la biblioteca para producir el resultado de la creación del intent.
  • Luego, el desarrollador confirma el vínculo directo o activa la entrega de la app y ejecuta la validación personalizada en el estado de la app.

Requisitos de configuración

Para usar la biblioteca de pruebas, es necesaria la configuración inicial de la app antes de agregar las pruebas a la aplicación.

Configuración

Para usar la biblioteca de pruebas de Acciones en apps, asegúrate de que la app se configure de la siguiente manera:

  • Instala el complemento de Android para Gradle (AGP)
  • Incluye un archivo shortcuts.xml en la carpeta res/xml del módulo app.
  • Asegúrate de que AndroidManifest.xml incluya <meta-data android:name="android.app.shortcuts" android:resource=”@xml/shortcuts” /> en los siguientes casos:
    • la etiqueta <application>
    • la etiqueta <activity> del selector
  • Coloca el elemento <capability> dentro del elemento <shortcuts> en shortcuts.xml.

Cómo agregar dependencias de la biblioteca de pruebas de Acciones en apps

  1. Agrega el repositorio de Google a la lista de repositorios de proyectos en settings.gradle:

        allprojects {
            repositories {
                …
                google()
            }
        }
    
  2. En el archivo build.gradle del módulo de la app, agrega las dependencias de AATL:

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

    Asegúrate de usar el número de versión de la biblioteca que descargaste.

Crear pruebas de integración

  1. Crea pruebas nuevas en app/src/androidTest. En el caso de las de Robolectric, créalas en 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 usas Espresso, debes modificar la forma de iniciar la actividad según los resultados de AATL. A continuación, se ofrece un ejemplo para Espresso que usa el método 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. Haz que el nombre y las propiedades clave en las asignaciones de parámetros coincidan con los parámetros del BII. Por ejemplo, order.orderedItem.name coincide con la documentación del parámetro en GET_ORDER.

  3. Crea una instancia de API con el parámetro de contexto de Android (obtenido de ApplicationProvider o InstrumentationRegistry):

    • Arquitectura de la app con un solo módulo:

    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);
          }
        
      
    • Arquitectura de apps de varios módulos:

    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. Ejecuta el método fulfill de la API y obtén el objeto AppActionsFulfillmentResult.

Realizar aserciones

La forma recomendada de confirmar la Biblioteca de prueba de Acciones en apps es la siguiente:

  1. Confirma el tipo de entrega de AppActionsFulfillmentResult. Debe ser FulfillmentType.INTENT o FulfillmentType.UNFULFILLED para probar el comportamiento de la app en caso de solicitudes de BIIs inesperadas.
  2. Existen 2 tipos de entrega: INTENT y DEEPLINK.
    • Por lo general, el desarrollador puede diferenciar las entregas de INTENT y DEEPLINK si observa la etiqueta de intent en shortcuts.xml que entrega mediante la activación de la biblioteca.
    • Si hay una etiqueta de plantilla de URL en la etiqueta del intent, esto indica que el DEEPLINK entrega este intent.
    • Si el método getData() del intent del resultado muestra un objeto no nulo, esto también indica entrega por parte de DEEPLINK. Del mismo modo, si getData muestra null, significa que es una entrega de INTENT.
  3. En el caso de INTENT, escribe AppActionsFulfillmentResult a AppActionsIntentFulfillmentResult para obtener el intent de Android mediante una llamada al método getIntent y completa una de las siguientes acciones:
    • Declara campos individuales de intent de Android.
    • Confirma el URI de un intent al que se accede mediante el método intent.getData.getHost.
  4. En el caso de DEEPLINK, encasilla AppActionsFulfillmentResult en AppActionsIntentFulfillmentResult (al igual que en la situación de INTENT anterior) para obtener el Intent de Android llamando al método getIntent y declarar la URL del vínculo directo (a la que se accede a través de intent.getData.getHost).
  5. En el caso de INTENT y DEEPLINK, puedes usar el intent resultante para iniciar la actividad con el framework de pruebas de Android elegido.

Internacionalización

Si tu app tiene varias configuraciones regionales, puedes configurar pruebas para ejecutar una configuración regional en particular. Como alternativa, puedes cambiar directamente la configuración regional:

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

Este es un ejemplo de una prueba de AATL establecida para la configuración regional del español (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");
      }
      
    

Solución de problemas

Si la prueba de integración falla de forma inesperada, puedes buscar mensajes de registro AATL en la ventana de Logcat de Android Studio para obtener el mensaje de advertencia o nivel de error. También puedes aumentar el nivel de registro para capturar más resultados de la biblioteca.

Limitaciones

Estas son las limitaciones actuales de la biblioteca de prueba de Acciones en apps:

  • La AATL no prueba funciones de comprensión del lenguaje natural (CLN) ni de voz a texto (STT).
  • La AATL no funciona cuando las pruebas están en módulos que no son el predeterminado de la app.
  • La AATL solo es compatible con Android 7.0 "Nougat" (nivel de API 24) y versiones posteriores.