App Actions-Testbibliothek

Die App Actions-Testbibliothek (App Actions-Testbibliothek, AATL) bietet Entwicklern die Möglichkeit, die Auftragsausführung von App Action programmatisch zu testen. Dabei werden Tests automatisiert, die normalerweise mit echten Sprachabfragen oder dem App Actions-Testtool durchgeführt werden.

Die Bibliothek sorgt dafür, dass die shortcut.xml-Konfiguration korrekt ist und der beschriebene Android-Intent-Aufruf erfolgreich ist. Die App Actions-Testbibliothek bietet einen Mechanismus, mit dem du testen kannst, ob deine App bestimmte Google Assistant-Intents und -Parameter erfüllen kann. Dazu konvertierst du sie in einen Android-Deeplink oder einen Android-Intent, der bestätigt und zum Instanziieren einer Android-Aktivität verwendet werden kann.

Die Tests werden in Form von Robolectric-Einheiten oder instrumentierten Tests in der Android-Umgebung durchgeführt. Dies ermöglicht Entwicklern, ihre Anwendung umfassend zu testen, indem sie das tatsächliche App-Verhalten emulieren. Zum Testen von BIIs, benutzerdefinierten Intents oder Deeplink-Auftragsausführung kann jedes instrumentierte Test-Framework verwendet werden (UI Automator, Espresso, JUnit4, Appium, Detox, Calabash).

Wenn die App mehrsprachig ist, können Entwickler überprüfen, ob die Funktionalität der Anwendung in verschiedenen Sprachen korrekt funktioniert.

So funktionierts

Zum Einbinden der App Actions-Testbibliothek in die Testumgebung der App sollten Entwickler neue Robolectric- oder instrumentierte Tests im Modul app der App erstellen oder vorhandene aktualisieren.

Der Testcode enthält die folgenden Teile:

  • Initialisierung der Bibliotheksinstanz in der üblichen Einrichtungsmethode oder in einzelnen Testläufen.
  • Jeder einzelne Test ruft die Methode fulfill der Bibliotheksinstanz auf, um das Ergebnis der Intent-Erstellung zu generieren.
  • Der Entwickler bestätigt dann den Deeplink oder löst die App-Auftragsausführung aus und führt eine benutzerdefinierte Validierung des App-Status aus.

Einrichtungsanforderungen

Damit Sie die Testbibliothek verwenden können, ist eine Erstkonfiguration der Anwendung erforderlich, bevor Sie die Tests Ihrer Anwendung hinzufügen.

Konfiguration

Damit Sie die App Actions-Testbibliothek verwenden können, muss Ihre App so konfiguriert sein:

  • Installieren Sie das Android Gradle-Plug-in (AGP).
  • Fügen Sie die Datei shortcuts.xml im Ordner res/xml des Moduls app ein.
  • Achten Sie darauf, dass AndroidManifest.xml <meta-data android:name="android.app.shortcuts" android:resource=”@xml/shortcuts” /> unter folgenden Elementen enthält:
    • das <application>-Tag
    • das Launcher-Tag <activity>
  • Platzieren Sie das <capability>-Element innerhalb des <shortcuts>-Elements in shortcuts.xml.

Abhängigkeiten der App Actions-Testbibliothek hinzufügen

  1. Fügen Sie das Google-Repository zur Liste der Projekt-Repositories in settings.gradle hinzu:

        allprojects {
            repositories {
                …
                google()
            }
        }
    
  2. Fügen Sie in der Datei build.gradle des App-Moduls die AATL-Abhängigkeiten hinzu:

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

    Verwenden Sie die Versionsnummer der heruntergeladenen Bibliothek.

Integrationstests erstellen

  1. Erstellen Sie neue Tests unter app/src/androidTest. Erstellen Sie für Robolectric-Tests diese unter 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…")
          }
      
    

    Wenn Sie Espresso verwenden, müssen Sie anhand der AATL-Ergebnisse ändern, wie die Aktivität gestartet wird. Hier ist ein Beispiel für Espresso mit der Methode 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. Der Name und die Schlüsselattribute in den Parameterzuordnungen müssen mit den Parametern aus BII übereinstimmen. order.orderedItem.name entspricht beispielsweise der Dokumentation für den Parameter in GET_ORDER.

  3. Instanziieren Sie die API-Instanz mit dem Android-Kontextparameter (über ApplicationProvider oder InstrumentationRegistry abgerufen):

    • Anwendungsarchitektur mit einem einzelnen Modul:

    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);
          }
        
      
    • Anwendungsarchitektur mit mehreren Modulen:

    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. Führen Sie die Methode fulfill der API aus und rufen Sie das Objekt AppActionsFulfillmentResult ab.

Assertions ausführen

Die empfohlene Methode zum Bestätigen der App Actions-Testbibliothek lautet:

  1. Bestätigen Sie den Auftragsausführungstyp von AppActionsFulfillmentResult. Er muss FulfillmentType.INTENT oder FulfillmentType.UNFULFILLED sein, um zu testen, wie sich die Anwendung bei unerwarteten BII-Anfragen verhält.
  2. Es gibt zwei Arten der Auftragsausführung: INTENT- und DEEPLINK-Auftragsausführungen.
    • Normalerweise kann der Entwickler zwischen INTENT- und DEEPLINK-Auftragsausführungen unterscheiden, indem er sich das Intent-Tag in shortcuts.xml ansieht, das er erfüllt, indem er die Bibliothek auslöst.
    • Wenn sich unter dem Intent-Tag ein URL-Vorlagen-Tag befindet, bedeutet das, dass DEEPLINK diesen Intent erfüllt.
    • Wenn die Methode getData() des Ergebnis-Intents ein Objekt zurückgibt, das nicht null ist, gibt dies auch die DEEPLINK-Auftragsausführung an. Wenn getData null zurückgibt, bedeutet dies ebenfalls, dass es sich um eine INTENT-Auftragsausführung handelt.
  3. Wandeln Sie für den INTENT-Fall AppActionsFulfillmentResult in AppActionsIntentFulfillmentResult um, rufen Sie den Android-Intent durch Aufrufen der Methode getIntent ab und führen Sie einen der folgenden Schritte aus:
    • Einzelne Felder des Android-Intents erzwingen.
    • Bestätigen Sie den URI eines Intents, auf den über die Methode „intent.getData.getHost“ zugegriffen wird.
  4. Wandeln Sie im DEEPLINK-Fall AppActionsFulfillmentResult in AppActionsIntentFulfillmentResult um (wie für das INTENT-Szenario oben), rufen Sie den Android-Intent ab, indem Sie die Methode getIntent aufrufen und die Deeplink-URL bestätigen (Zugriff über intent.getData.getHost).
  5. Sowohl für INTENT als auch für DEEPLINK können Sie den resultierenden Intent verwenden, um die Aktivität mit dem ausgewählten Android-Test-Framework zu starten.

Lokalisierung

Wenn Ihre Anwendung mehrere Sprachen hat, können Sie Tests so konfigurieren, dass eine bestimmte zu testende Sprache ausgeführt wird. Alternativ können Sie das Gebietsschema direkt ändern:

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

Hier ein Beispiel für einen AATL-Test, der für die Sprache Spanisch (ES) konfiguriert ist:

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

Fehlerbehebung

Wenn Ihr Integrationstest unerwartet fehlschlägt, können Sie im Logcat-Fenster von Android Studio nach AATL-Logmeldungen suchen, um die Warn- oder Fehlermeldung zu erhalten. Sie können auch die Logging-Ebene erhöhen, um mehr Ausgabedaten aus der Bibliothek zu erfassen.

Einschränkungen

Im Folgenden sind die aktuellen Einschränkungen der App Actions-Testbibliothek aufgeführt :

  • AATL testet keine Features von Natural Language Understanding (NLU) oder Speech-to-Text (STT).
  • AATL funktioniert nicht, wenn sich Tests in anderen Modulen als dem Standard-App-Modul befinden.
  • AATL ist nur mit Android 7.0 „Nougat“ (API-Level 24) und höher kompatibel.