App Actions テスト ライブラリ

App Actions テスト ライブラリ(AATL)を使用すると、デベロッパーは、プログラムを使って App Action フルフィルメントをテストできます。通常、実際の音声クエリや App Actions テストツールを使用して行うテストを自動化できるのです。

このライブラリにより、shortcut.xml 構成の正確さが確認され、記述された Android インテントを正常に呼び出せるようになります。App Actions テスト ライブラリは、特定の Google アシスタントのインテントとパラメータのフルフィルメント機能について、アプリをテストする仕組みを提供しています。この仕組みは、それらのインテントおよびパラメータをアサーションや Android アクティビティのインスタンス化に使用できる Android ディープリンクや Android インテントに変換することで実現されています。

テストは、Android 環境において、Robolectric 単体テストまたはインストルメンテーション テストの形式で実施されます。これにより、デベロッパーは実際のアプリの動作をエミュレートして、アプリを包括的にテストできます。BII、カスタム インテント、ディープリンク フルフィルメントをテストする場合は、インストルメンテーション テスト フレームワーク(UI Automator、Espresso、JUnit4、Appium、Detox、Carabash)の使用が可能です。

アプリケーションが多言語対応の場合、デベロッパーは、複数の言語 / 地域内でアプリケーションの機能が正しく動作するかどうかを検証できます。

仕組み

デベロッパーは、アプリのテスト環境に App Actions テスト ライブラリを統合するために、アプリの app モジュールで新たな Robolectric テストやインストルメンテーション テストを作成するか、既存のものを更新する必要があります。

テストコードは以下のように構成されています。

  • 一般的なセットアップ メソッドまたは個々のテストケースで、ライブラリ インスタンスを初期化します。
  • 個々のテストごとにライブラリ インスタンスの fulfill メソッドを呼び出して、インテントの作成結果を生成します。
  • その後、デベロッパーはディープリンクをアサートするか、アプリのフルフィルメントをトリガーして、アプリの状態についてカスタム検証を行います。

セットアップの要件

テスト ライブラリを使用するには、アプリにテストを追加する前に、いくつかの初期アプリ構成が必要です。

構成

App Actions テスト ライブラリを使用するには、アプリが次のような構成になっていることを確認してください。

  • Android Gradle プラグイン(AGP)をインストールします。
  • app モジュールの res/xml フォルダに shortcuts.xml ファイルを含めます。
  • 次のいずれかにおいて、AndroidManifest.xml<meta-data android:name="android.app.shortcuts" android:resource=”@xml/shortcuts” /> が含まれていることを確認します。
    • <application> タグ
    • ランチャー <activity> タグ
  • <capability> 要素を shortcuts.xml<shortcuts> 要素内に配置します

App Actions テスト ライブラリの依存関係を追加する

  1. settings.gradle のプロジェクト リポジトリのリストに Google リポジトリを追加します。

        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 を使用している場合は、AATL の結果に基づいてアクティビティの起動方法を変更する必要があります。以下に、ActivityScenario メソッドを使用する Espresso の例を示します。

    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 のパラメータと一致するようにしてください。たとえば、order.orderedItem.nameGET_ORDER のパラメータのドキュメントと合わせます。

  3. Android コンテキスト パラメータ(ApplicationProvider または InstrumentationRegistry から取得)を使用して、API インスタンスをインスタンス化します。

    • 単一モジュール アプリのアーキテクチャ:

    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. API の fulfill メソッドを実行し、AppActionsFulfillmentResult オブジェクトを取得します。

アサーションを実行する

App Actions テスト ライブラリのアサーションについては、次の方法が推奨されます。

  1. AppActionsFulfillmentResult のフルフィルメント タイプをアサートします。予期せぬ BII リクエストが発生した場合にアプリの動作をテストするためには、FulfillmentType.INTENT または FulfillmentType.UNFULFILLED にする必要があります。
  2. フルフィルメントには、INTENT フルフィルメントと DEEPLINK フルフィルメントの 2 つのフレーバーがあります。
    • デベロッパーは通常、shortcuts.xml のインテントタグを確認することで、ライブラリをトリガーすることで行われる INTENT フルフィルメントと DEEPLINK フルフィルメントを区別できます。
    • インテントタグの下に URL テンプレート タグがある場合は、DEEPLINK がこのインテントのフルフィルメントを行うことを示しています。
    • 結果インテントの getData() メソッドが null 以外のオブジェクトを返す場合、DEEPLINK フルフィルメントであることを示しています。同様に、getDatanull を返す場合は、INTENT フルフィルメントであることを意味します。
  3. INTENT の場合、AppActionsFulfillmentResultAppActionsIntentFulfillmentResult に型変換するには、getIntent メソッドを呼び出して Android インテントを取得し、次のいずれかを行います。
    • Android インテントの個々のフィールドをアサートします。
    • intent.getData.getHost メソッドを介してアクセスされるインテントの URI をアサートします。
  4. DEEPLINK の場合、(上記の INTENT シナリオと同じように)AppActionsFulfillmentResultAppActionsIntentFulfillmentResult に型変換します。getIntent メソッドを呼び出し、(intent.getData.getHost からアクセスした)ディープリンク URL をアサートして、Android インテントを取得します。
  5. INTENTDEEPLINK の両方で、結果として得られるインテントを使用して、選択した 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);
    
  

スペイン(ES)言語 / 地域向けに構成された AATL テストの例を次に示します。

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

トラブルシューティング

統合テストで予期せぬエラーが発生した場合は、Android Studio の logcat ウィンドウで AATL ログメッセージを探し、警告またはエラーレベルのメッセージを取得できます。また、ロギングレベルを上げて、ライブラリからより多くの出力をキャプチャすることもできます。

制限事項

現在、App Actions テスト ライブラリの制限事項は次のとおりです。

  • AATL では、自然言語理解(NLU)機能や音声文字変換(STT)機能のテストは行われません。
  • テストがデフォルト アプリ モジュール以外のモジュールにある場合、AATL は機能しません。
  • AATL が互換性を持つのは、Android 7.0「Nougat」(API レベル 24)以降に限られます。