A Biblioteca de teste de Ações no app (AATL, na sigla em inglês) oferece recursos para que os desenvolvedores possam testar o fulfillment de Ações no app de forma programática, automatizando testes que normalmente seriam feitos usando consultas de voz reais ou a ferramenta de teste de Ações no app.
A biblioteca ajuda a garantir que a configuração shortcut.xml
esteja correta e
que a invocação da intent do Android descrita seja bem-sucedida. A Biblioteca de teste de Ações no app fornece um mecanismo para testar a capacidade do app de atender à intent e parâmetros do Google Assistente, convertendo-os em um link direto ou uma intent do Android — que pode ser declarada e usada para instanciar uma atividade do Android.
Os testes são realizados como testes Robolectric de unidade ou instrumentados no ambiente do Android. Isso permite que os desenvolvedores testem de forma abrangente o aplicativo ao emular o comportamento real do app. Para testar BIIs, intents personalizadas ou fulfillment de links diretos, qualquer framework de teste instrumentado pode ser usado (UI Automator, Espresso, JUnit4, Appium, Detox, Calabash).
Se o aplicativo for multilíngue, os desenvolvedores podem validar se a funcionalidade do aplicativo está se comportando corretamente em diferentes localidades.
Como funciona
Para integrar a biblioteca de testes de Ações no app no ambiente de teste, os desenvolvedores precisam
atualizar ou criar novos testes Robolectric ou instrumentados no módulo
app
do app.
O código de teste contém as seguintes partes:
- Inicialização da instância da biblioteca, no método de configuração comum ou em casos de teste individuais.
- Cada teste individual chama o método
fulfill
da instância da biblioteca para produzir o resultado da criação da intent. - Em seguida, o desenvolvedor declara o link direto ou aciona o fulfillment do app e executa a validação personalizada no estado dele.
Requisitos de configuração
Para usar a biblioteca de testes, algumas configurações iniciais do app são necessárias antes de adicionar os testes a ele.
Configuração
Para usar a biblioteca de teste de Ações no app, verifique se ele está configurado da seguinte maneira:
- Instale o Plug-in do Android para Gradle.
- Inclua um arquivo
shortcuts.xml
na pastares/xml
do móduloapp
. - Verifique se
AndroidManifest.xml
inclui<meta-data android:name="android.app.shortcuts" android:resource=”@xml/shortcuts” />
:- na tag
<application>
, ou - na tag
<activity>
.
- na tag
- Coloque o elemento
<capability>
dentro do<shortcuts>
emshortcuts.xml
Adicionar dependências da biblioteca de testes de Ações no app
Adicione o repositório do Google à lista de repositórios de projetos em
settings.gradle
:allprojects { repositories { … google() } }
No arquivo
build.gradle
do módulo do app, adicione as dependências da AATL:androidTestImplementation 'com.google.assistant.appactions:testing:1.0.0'
Use o número da versão da biblioteca da qual você fez o download.
Criar testes de integração
Crie novos testes em
app/src/androidTest
. Para testes Robolectric, crie-os emapp/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…") }
Se você estiver usando o Espresso, precisará modificar a forma de inicialização da Activity com base nos resultados da AATL. Confira um exemplo do Espresso usando o 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…")))
O nome e as propriedades da chave nos mapeamentos de parâmetro precisam corresponder aos parâmetros da BII. Por exemplo,
exercisePlan.forExercise.name
precisa corresponder à documentação do parâmetro emGET_EXERCISE_PLAN
.Instancie a instância de API com o parâmetro de contexto do Android (recebido de
ApplicationProvider
ouInstrumentationRegistry
):- Arquitetura do app de módulo único:
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); }
- Arquitetura do app com vários 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)); }
Execute o método
fulfill
da API e receba o objetoAppActionsFulfillmentResult
.
Executar declarações
Esta é a maneira recomendada de declarar a biblioteca de testes de Ações no app:
- Declare o tipo de fulfillment do
AppActionsFulfillmentResult
. Ele precisa serFulfillmentType.INTENT
ouFulfillmentType.UNFULFILLED
para testar como o app se comporta em caso de solicitações de BII inesperadas. - Há duas variações de fulfillment:
INTENT
eDEEPLINK
.- Normalmente, o desenvolvedor pode diferenciar entre os fulfillments
INTENT
eDEEPLINK
pela tag da intent emshortcuts.xml
que está sendo atendida pelo acionamento da biblioteca. - Se houver uma tag de modelo de URL abaixo da tag da intent, isso indica que
DEEPLINK
atende a essa intent. - Se o método
getData()
da intent de resultado retornar um objeto não nulo, isso também indica fulfillmentDEEPLINK
. Da mesma forma, segetData
retornarnull
, significa que é um fulfillmentINTENT
.
- Normalmente, o desenvolvedor pode diferenciar entre os fulfillments
- No caso de
INTENT
, faça um typecast deAppActionsFulfillmentResult
paraAppActionsIntentFulfillmentResult
, busque a intent do Android chamando o métodogetIntent
e siga um destes procedimentos:- Declare campos individuais de intents do Android.
- Declare o URI de uma intent acessada pelo método intent.getData.getHost.
- No caso de
DEEPLINK
, faça um typecast deAppActionsFulfillmentResult
paraAppActionsIntentFulfillmentResult
(o mesmo do cenárioINTENT
acima), busque a intent do Android chamando o métodogetIntent
e declare o URL do link direto (acessado pelo métodointent.getData.getHost
). - Para
INTENT
eDEEPLINK
, você pode usar a intent resultante para iniciar a atividade com o framework de teste do Android escolhido.
Internacionalização
Se o app tem várias localidades, você pode configurar os testes para executar um pré-teste em uma delas. Você também pode mudar a localidade diretamente:
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);
Confira um exemplo de teste da AATL configurado para a localidade espanhol (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"); }
Resolver problemas
Se o teste de integração falhar de forma inesperada, procure as mensagens de registro da AATL na janela "Logcat" do Android Studio para receber a mensagem de aviso ou nível de erro. Também é possível aumentar o nível de geração de registros para capturar mais saídas da biblioteca.
Limitações
Estas são as limitações atuais da biblioteca de teste de Ações no app :
- A AATL não testa recursos de processamento de linguagem natural (PLN) ou de conversão de voz em texto (STT, na sigla em inglês).
- A AATL não funciona quando os testes não estão no módulo padrão do app.
- A AATL é compatível apenas com o Android 7.0 "Nougat" (nível 24 da API) e versões mais recentes.