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 folderzeres/xml
w moduleapp
. - 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
- tag
- Umieść element
<capability>
w elemencie<shortcuts>
w elemencieshortcuts.xml
Dodawanie zależności biblioteki testowej działań w aplikacji
Dodaj repozytorium Google do listy repozytoriów projektów w zadaniu
settings.gradle
:allprojects { repositories { … google() } }
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
Utwórz nowe testy w grupie
app/src/androidTest
. W przypadku testów Robolectric utwórz je w grupieapp/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…")))
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 wGET_ORDER
.Utwórz instancję interfejsu API z parametrem kontekstu Androida (otrzymanym z
ApplicationProvider
lubInstrumentationRegistry
):- 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)); }
Uruchom metodę
fulfill
interfejsu API i uzyskaj obiektAppActionsFulfillmentResult
.
Wykonywanie asercji
Zalecany sposób uzyskania dostępu do biblioteki testowej działań w aplikacji to:
- Potwierdź typ realizacji elementu
AppActionsFulfillmentResult
. ParametrFulfillmentType.INTENT
lubFulfillmentType.UNFULFILLED
pozwala przetestować zachowanie aplikacji w przypadku nieoczekiwanych żądań BII. - Są 2 rodzaje realizacji zamówień:
INTENT
iDEEPLINK
.- Zwykle deweloper może rozróżniać realizację
INTENT
iDEEPLINK
, sprawdzając tag intencji w plikushortcuts.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śligetData
zwraca wartośćnull
, oznacza to, że jest to realizacjaINTENT
.
- Zwykle deweloper może rozróżniać realizację
- W przypadku
INTENT
zastosuj metodę typuAppActionsFulfillmentResult
doAppActionsIntentFulfillmentResult
, 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.
- W przypadku typu
DEEPLINK
użyj typuAppActionsFulfillmentResult
doAppActionsIntentFulfillmentResult
(tak samo jak w powyższym scenariuszuINTENT
), pobierz intencję Androida, wywołując metodęgetIntent
i podaj adres URL precyzyjnego linku (do którego można uzyskać dostęp przezintent.getData.getHost
). - Zarówno w przypadku
INTENT
, jak iDEEPLINK
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.