Biblioteca de teste de Ações no app

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 pasta res/xml do módulo app.
  • Verifique se AndroidManifest.xml inclui <meta-data android:name="android.app.shortcuts" android:resource=”@xml/shortcuts” />:
    • na tag <application>, ou
    • na tag <activity>.
  • Coloque o elemento <capability> dentro do <shortcuts> em shortcuts.xml

Adicionar dependências da biblioteca de testes de Ações no app

  1. Adicione o repositório do Google à lista de repositórios de projetos em settings.gradle:

        allprojects {
            repositories {
                …
                google()
            }
        }
    
  2. 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

  1. Crie novos testes em app/src/androidTest. Para testes Robolectric, crie-os em 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…")
          }
      
    

    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…")))
        
    
  2. O nome e as propriedades da chave nos mapeamentos de parâmetro precisam corresponder aos parâmetros da BII. Por exemplo, order.orderedItem.name precisa corresponder à documentação do parâmetro em GET_ORDER.

  3. Instancie a instância de API com o parâmetro de contexto do Android (recebido de ApplicationProvider ou InstrumentationRegistry):

    • 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));
          }
        
      
  4. Execute o método fulfill da API e receba o objeto AppActionsFulfillmentResult.

Executar declarações

Esta é a maneira recomendada de declarar a biblioteca de testes de Ações no app:

  1. Declare o tipo de fulfillment do AppActionsFulfillmentResult. Ele precisa ser FulfillmentType.INTENT ou FulfillmentType.UNFULFILLED para testar como o app se comporta em caso de solicitações de BII inesperadas.
  2. Há duas variações de fulfillment: INTENT e DEEPLINK.
    • Normalmente, o desenvolvedor pode diferenciar entre os fulfillments INTENT e DEEPLINK pela tag da intent em shortcuts.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 fulfillment DEEPLINK. Da mesma forma, se getData retornar null, significa que é um fulfillment INTENT.
  3. No caso de INTENT, faça um typecast de AppActionsFulfillmentResult para AppActionsIntentFulfillmentResult, busque a intent do Android chamando o método getIntent 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.
  4. No caso de DEEPLINK, faça um typecast de AppActionsFulfillmentResult para AppActionsIntentFulfillmentResult (o mesmo do cenário INTENT acima), busque a intent do Android chamando o método getIntent e declare o URL do link direto (acessado pelo método intent.getData.getHost).
  5. Para INTENT e DEEPLINK, 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.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");
      }
      
    

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.