Библиотека тестирования действий в приложениях

Тестовая библиотека действий в приложении (AATL) позволяет разработчикам программно тестировать выполнение действий в приложении, автоматизируя тестирование, которое обычно выполняется с помощью реальных голосовых запросов или инструмента тестирования действий в приложении.

Библиотека помогает убедиться, что конфигурация shortcut.xml задана правильно и вызов описанного намерения Android выполняется успешно. Тестовая библиотека действий в приложении позволяет проверить, может ли ваше приложение выполнять заданные намерения и параметры Google Ассистента. Для этого они преобразуются в ссылку на контент в Android или намерение Android, которые можно подтвердить и использовать для создания экземпляра действия Android.

Тестирование выполняется в виде модульных или инструментальных тестов Robolectric в среде Android. Это позволяет разработчикам всесторонне протестировать свое приложение, эмулируя его фактическое поведение. Для тестирования встроенных и пользовательских намерений, а также выполнения ссылок на контент можно использовать любой фреймворк для инструментального тестирования (UI Automator, Espresso, JUnit4, Appium, Detox, Calabash).

Если приложение многоязычное, разработчики могут проверить, правильно ли работают его функции в разных регионах.

Принцип работы

Чтобы интегрировать тестовую библиотеку действий в приложении в тестовую среду приложения, разработчикам нужно создать новые или обновить существующие тесты Robolectric или инструментальные тесты в модуле app приложения.

Код теста состоит из следующих частей:

  • Инициализация экземпляра библиотеки в общем методе настройки или в отдельных тестовых примерах.
  • Каждый отдельный тест вызывает метод fulfill экземпляра библиотеки, чтобы получить результат создания намерения.
  • Затем разработчик подтверждает ссылку на контент или запускает выполнение в приложении и запускает собственную валидацию состояния приложения.

Требования по настройке

Чтобы использовать тестовую библиотеку, необходимо выполнить начальную настройку приложения, прежде чем добавлять в него тесты.

Конфигурация

Чтобы использовать библиотеку тестирования действий в приложении, убедитесь, что ваше приложение настроено следующим образом:

  • Установите плагин Android Gradle (AGP).
  • Добавьте файл shortcuts.xml в папку res/xml в модуле app.
  • Убедитесь, что AndroidManifest.xml включает <meta-data android:name="android.app.shortcuts" android:resource=”@xml/shortcuts” /> в одном из следующих разделов:
    • тег <application>
    • тег <activity>;
  • Разместите элемент <capability> внутри элемента <shortcuts> в shortcuts.xml.

Добавьте зависимости тестовой библиотеки действий в приложении

  1. Добавьте репозиторий Google в список репозиториев проекта в файле settings.gradle:

        allprojects {
            repositories {
                …
                google()
            }
        }
    
  2. В файл build.gradle модуля приложения добавьте зависимости AATL:

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

    Убедитесь, что вы используете номер версии скачанной библиотеки.

Создайте интеграционные тесты

  1. Создайте новые тесты в разделе app/src/androidTest. Для тестов Robolectric создайте их в каталоге 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…")
          }
      

    Если вы используете Espresso, вам нужно изменить способ запуска Activity на основе результатов AATL. Ниже приведен пример для Espresso с использованием метода 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. Убедитесь, что названия и ключевые свойства в сопоставлениях параметров соответствуют параметрам из BII. Например, exercisePlan.forExercise.name соответствует документации для параметра в GET_EXERCISE_PLAN.

  3. Создайте экземпляр API с параметром Android Context (полученным из ApplicationProvider или InstrumentationRegistry):

    • Архитектура приложения с одним модулем:

    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);
          }
        
      
    • Многомодульная архитектура приложения:

    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. Выполните метод fulfill API и получите объект AppActionsFulfillmentResult.

Выполнение утверждений

Рекомендуемый способ проверки библиотеки тестирования действий в приложении:

  1. Укажите тип выполнения заказа для AppActionsFulfillmentResult. Чтобы проверить, как приложение ведет себя в случае непредвиденных запросов встроенных намерений, необходимо указать FulfillmentType.INTENT или FulfillmentType.UNFULFILLED.
  2. Существует два типа выполнения: INTENT и DEEPLINK.
    • Обычно разработчик может отличить выполнение INTENT от выполнения DEEPLINK, посмотрев на тег намерения в shortcuts.xml, который он выполняет, запуская библиотеку.
    • Если под тегом intent есть тег url-template, это означает, что DEEPLINK выполняет это намерение.
    • Если метод getData() намерения результата возвращает ненулевой объект, это также указывает на выполнение DEEPLINK. Аналогично, если getData возвращает null, это означает, что заказ выполняется INTENT.
  3. Для INTENT приведите AppActionsFulfillmentResult к AppActionsIntentFulfillmentResult, получите Android Intent, вызвав метод getIntent, и выполните одно из следующих действий:
    • Проверьте отдельные поля Android Intent.
    • Проверьте URI намерения, доступ к которому получен с помощью метода intent.getData.getHost.
  4. Для случая DEEPLINK приведите тип AppActionsFulfillmentResult к AppActionsIntentFulfillmentResult (как в сценарии INTENT выше), получите намерение Android, вызвав метод getIntent, и подтвердите URL ссылки на контент (доступный через intent.getData.getHost).
  5. В обоих случаях (INTENT и DEEPLINK) вы можете использовать полученный объект Intent, чтобы запустить действие с помощью выбранного фреймворка для тестирования Android.

Языковые настройки

Если ваше приложение поддерживает несколько языков, вы можете настроить тесты для запуска определенного языка. Вы также можете изменить региональные настройки напрямую:

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

Ниже приведен пример теста AATL, настроенного для испанского языка (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");
      }
      
    

Устранение неполадок

Если интеграционный тест неожиданно завершился сбоем, вы можете найти сообщения в журнале AATL в окне logcat Android Studio, чтобы получить сообщение об уровне предупреждения или ошибки. Вы также можете повысить уровень ведения журнала, чтобы получать больше выходных данных из библиотеки.

Ограничения

Текущие ограничения тестовой библиотеки действий в приложениях :

  • AATL не тестирует функции понимания естественного языка (NLU) и преобразования речи в текст (STT).
  • AATL не работает, если тесты находятся в модулях, отличных от модуля приложения по умолчанию.
  • AATL совместима только с Android 7.0 Nougat (уровень API 24) и более поздними версиями.