Biblioteka testowa akcji w aplikacji

Biblioteka testowa działań w aplikacji (AATL) umożliwia deweloperom automatyczne testowanie realizacji działań w aplikacji w ramach automatyzacji testów, które zwykle wykonuje się za pomocą zapytań głosowych lub narzędzia testowego akcji w aplikacji.

Biblioteka pomaga zadbać o prawidłową konfigurację shortcut.xml i prawidłowe wywołanie intencji Androida. Biblioteka testowa akcji w aplikacji zapewnia mechanizm testowania zdolności aplikacji do realizacji określonych intencji i parametrów Asystenta Google przez konwersję ich na precyzyjne linki na Androida lub intencje na Androida, które można potwierdzić i wykorzystać do utworzenia instancji działania na Androidzie.

Testy przeprowadzane są w postaci jednostki Robolectric lub testów instrumentowanych w środowisku Androida. Dzięki temu mogą dokładnie przetestować aplikację przez emulację jej rzeczywistego działania. Do testowania BII, intencji niestandardowych lub realizacji precyzyjnych linków można wykorzystać dowolną platformę do testowania instrumentalnych (UI Automator, Espresso, JUnit4, Appium, Detox, Calabash).

Jeśli aplikacja jest wielojęzyczna, deweloperzy mogą sprawdzić, czy jej funkcje działają prawidłowo w różnych językach.

Jak to działa

Aby zintegrować bibliotekę testów akcji w aplikacji ze środowiskiem testowym aplikacji, deweloperzy powinni utworzyć lub zaktualizować istniejące testy Robolectric lub instrumentowane w module app aplikacji.

Kod testu składa się z tych części:

  • Inicjowanie instancji biblioteki w ramach popularnej metody konfiguracji lub w poszczególnych przypadkach testowych.
  • Każdy test wywołuje metodę fulfill instancji biblioteki, aby wygenerować wynik tworzenia intencji.
  • Następnie deweloper zgłasza precyzyjny link lub aktywuje realizację aplikacji i przeprowadza niestandardową weryfikację stanu aplikacji.

Wymagania dotyczące konfiguracji

Zanim dodasz testy do aplikacji, musisz ją najpierw skonfigurować.

Konfiguracja

Aby korzystać z biblioteki testowej działań w aplikacji, upewnij się, że aplikacja jest skonfigurowana w ten sposób:

  • Zainstaluj wtyczkę Androida do obsługi Gradle (AGP)
  • Umieść plik shortcuts.xml w folderze res/xml w module app.
  • Upewnij się, że AndroidManifest.xml uwzględnia <meta-data android:name="android.app.shortcuts" android:resource=”@xml/shortcuts” /> w jednym z tych elementów:
    • tag <application>
    • tag <activity> programu uruchamiającego
  • Umieść element <capability> w elemencie <shortcuts> w elemencie shortcuts.xml

Dodawanie zależności biblioteki testowej działań w aplikacji

  1. Dodaj repozytorium Google do listy repozytoriów projektów w zadaniu settings.gradle:

        allprojects {
            repositories {
                …
                google()
            }
        }
    
  2. W pliku build.gradle modułu aplikacji dodaj zależności AATL:

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

    Pamiętaj, by użyć numeru wersji pobranej biblioteki.

Tworzenie testów integracji

  1. Utwórz nowe testy w grupie app/src/androidTest. W przypadku testów Robolectric utwórz je w grupie 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…")
          }
      
    

    Jeśli używasz Espresso, musisz zmienić sposób uruchamiania aktywności na podstawie wyników AATL. Oto przykład użycia espresso z użyciem metody 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. Właściwości nazw i kluczowych elementów w mapowaniach parametrów muszą być zgodne z parametrami z BII. Na przykład order.orderedItem.name pasuje do dokumentacji parametru w GET_ORDER.

  3. Utwórz instancję interfejsu API z parametrem kontekstu Androida (otrzymanym z ApplicationProvider lub InstrumentationRegistry):

    • Architektura aplikacji z jednym modułem:

    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);
          }
        
      
    • Architektura aplikacji składająca się z wielu modułów:

    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. Uruchom metodę fulfill interfejsu API i uzyskaj obiekt AppActionsFulfillmentResult.

Wykonywanie asercji

Zalecany sposób uzyskania dostępu do biblioteki testowej działań w aplikacji to:

  1. Potwierdź typ realizacji elementu AppActionsFulfillmentResult. Parametr FulfillmentType.INTENT lub FulfillmentType.UNFULFILLED pozwala przetestować zachowanie aplikacji w przypadku nieoczekiwanych żądań BII.
  2. Są 2 rodzaje realizacji zamówień: INTENT i DEEPLINK.
    • Zwykle deweloper może rozróżniać realizację INTENT i DEEPLINK, sprawdzając tag intencji w pliku shortcuts.xml, które realizują przez aktywowanie biblioteki.
    • Jeśli pod tagiem intencji znajduje się tag szablonu URL-a, oznacza to, że intencję spełnia DEEPLINK.
    • Jeśli metoda getData() intencji wyniku zwraca niepusty obiekt, wskazuje to również realizację DEEPLINK. I podobnie, jeśli getData zwraca wartość null, oznacza to, że jest to realizacja INTENT.
  3. W przypadku INTENT zastosuj metodę typu AppActionsFulfillmentResult do AppActionsIntentFulfillmentResult, pobierz intencję Androida, wywołując metodę getIntent, i wykonaj jedną z tych czynności:
    • Zgłaszanie poszczególnych pól intencji Androida.
    • Zgłoś identyfikator URI intencji, do której dostęp jest uzyskiwany za pomocą metody intent.getData.getHost.
  4. W przypadku typu DEEPLINK użyj typu AppActionsFulfillmentResult do AppActionsIntentFulfillmentResult (tak samo jak w powyższym scenariuszu INTENT), pobierz intencję Androida, wywołując metodę getIntent i podaj adres URL precyzyjnego linku (do którego można uzyskać dostęp przez intent.getData.getHost).
  5. Zarówno w przypadku INTENT, jak i DEEPLINK możesz użyć powstałej w ten sposób intencji do uruchomienia działania za pomocą wybranej platformy testów na Androida.

Internacjonalizacja

Jeśli aplikacja ma wiele języków, możesz skonfigurować testy tak, aby przeprowadzały testy określonego języka. Możesz też zmienić język bezpośrednio:

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

Oto przykład testu AATL skonfigurowanego dla języka hiszpańskiego (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");
      }
      
    

Rozwiązywanie problemów

Jeśli test integracji nieoczekiwanie nie powiedzie się, poszukaj komunikatów z dziennika AATL w oknie logcat w Android Studio, aby zobaczyć ostrzeżenie lub komunikat o poziomie błędu. Możesz też zwiększyć poziom logowania, aby przechwycić więcej danych wyjściowych z biblioteki.

Ograniczenia

Oto obecne ograniczenia biblioteki testów działań w aplikacji :

  • AATL nie testuje funkcji języka naturalnego (NLU) ani zamiany mowy na tekst (STT).
  • AATL nie działa, gdy testy znajdują się w modułach innych niż domyślny moduł aplikacji.
  • AATL jest zgodne tylko z Androidem 7.0 „Nougat” (poziom interfejsu API 24) i nowszymi.