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 carpetares/xml
del móduloapp
. - 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
- la etiqueta
- Coloca el elemento
<capability>
dentro del elemento<shortcuts>
enshortcuts.xml
.
Cómo agregar dependencias de la biblioteca de pruebas de Acciones en apps
Agrega el repositorio de Google a la lista de repositorios de proyectos en
settings.gradle
:allprojects { repositories { … google() } }
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
Crea pruebas nuevas en
app/src/androidTest
. En el caso de las de Robolectric, créalas enapp/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…")))
Haz que el nombre y las propiedades clave en las asignaciones de parámetros coincidan con los parámetros del BII. Por ejemplo,
exercisePlan.forExercise.name
coincide con la documentación del parámetro enGET_EXERCISE_PLAN
.Crea una instancia de API con el parámetro de contexto de Android (obtenido de
ApplicationProvider
oInstrumentationRegistry
):- 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)); }
Ejecuta el método
fulfill
de la API y obtén el objetoAppActionsFulfillmentResult
.
Realizar aserciones
La forma recomendada de confirmar la Biblioteca de prueba de Acciones en apps es la siguiente:
- Confirma el tipo de entrega de
AppActionsFulfillmentResult
. Debe serFulfillmentType.INTENT
oFulfillmentType.UNFULFILLED
para probar el comportamiento de la app en caso de solicitudes de BIIs inesperadas. - Existen 2 tipos de entrega:
INTENT
yDEEPLINK
.- Por lo general, el desarrollador puede diferenciar las entregas de
INTENT
yDEEPLINK
si observa la etiqueta de intent enshortcuts.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 deDEEPLINK
. Del mismo modo, sigetData
muestranull
, significa que es una entrega deINTENT
.
- Por lo general, el desarrollador puede diferenciar las entregas de
- En el caso de
INTENT
, escribeAppActionsFulfillmentResult
aAppActionsIntentFulfillmentResult
para obtener el intent de Android mediante una llamada al métodogetIntent
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.
- En el caso de
DEEPLINK
, encasillaAppActionsFulfillmentResult
enAppActionsIntentFulfillmentResult
(al igual que en la situación deINTENT
anterior) para obtener el Intent de Android llamando al métodogetIntent
y declarar la URL del vínculo directo (a la que se accede a través deintent.getData.getHost
). - En el caso de
INTENT
yDEEPLINK
, 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.GET_EXERCISE_PLAN" val intentParams = ImmutableMap.of("exercisePlan.forExercise.name", "Running") val result = aatl.fulfill(intentName, intentParams) assertThat(result.getFulfillmentType()).isEqualTo(FulfillmentType.INTENT) val intentResult = result as AppActionsFulfillmentIntentResult assertThat(intentResult.getIntent().getData().toString()) .isEqualTo("myexercise://browse?plan=running_weekly") } }
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.GET_EXERCISE_PLAN"; ImmutableMap<String, String> intentParams = ImmutableMap.of("exercisePlan.forExercise.name", "Running"); AppActionsFulfillmentResult result = aatl.fulfill(intentName, intentParams); assertThat(result.getFulfillmentType()).isEqualTo(FulfillmentType.INTENT); AppActionsFulfillmentIntentResult intentResult = (AppActionsFulfillmentIntentResult) result; assertThat(intentResult.getIntent().getData().toString()) .isEqualTo("myexercise://browse?plan=running_weekly"); }
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.