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 テスト ライブラリの依存関係を追加する
settings.gradle
のプロジェクト リポジトリのリストに Google リポジトリを追加します。allprojects { repositories { … google() } }
アプリ モジュール
build.gradle
ファイルで、AATL 依存関係を追加します。androidTestImplementation 'com.google.assistant.appactions:testing:1.0.0'
ダウンロードしたライブラリのバージョン番号を使用してください。
統合テストを作成する
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…")))
パラメータ マッピングの名前とキーのプロパティは、BII のパラメータと一致するようにしてください。たとえば、
exercisePlan.forExercise.name
はGET_EXERCISE_PLAN
のパラメータのドキュメントと合わせます。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)); }
API の
fulfill
メソッドを実行し、AppActionsFulfillmentResult
オブジェクトを取得します。
アサーションを実行する
App Actions テスト ライブラリのアサーションについては、次の方法が推奨されます。
AppActionsFulfillmentResult
のフルフィルメント タイプをアサートします。予期せぬ BII リクエストが発生した場合にアプリの動作をテストするためには、FulfillmentType.INTENT
またはFulfillmentType.UNFULFILLED
にする必要があります。- フルフィルメントには、
INTENT
フルフィルメントとDEEPLINK
フルフィルメントの 2 つのフレーバーがあります。- デベロッパーは通常、
shortcuts.xml
のインテントタグを確認することで、ライブラリをトリガーすることで行われるINTENT
フルフィルメントとDEEPLINK
フルフィルメントを区別できます。 - インテントタグの下に URL テンプレート タグがある場合は、
DEEPLINK
がこのインテントのフルフィルメントを行うことを示しています。 - 結果インテントの
getData()
メソッドが null 以外のオブジェクトを返す場合、DEEPLINK
フルフィルメントであることを示しています。同様に、getData
がnull
を返す場合は、INTENT
フルフィルメントであることを意味します。
- デベロッパーは通常、
INTENT
の場合、AppActionsFulfillmentResult
をAppActionsIntentFulfillmentResult
に型変換するには、getIntent
メソッドを呼び出して Android インテントを取得し、次のいずれかを行います。- Android インテントの個々のフィールドをアサートします。
- intent.getData.getHost メソッドを介してアクセスされるインテントの URI をアサートします。
DEEPLINK
の場合、(上記のINTENT
シナリオと同じように)AppActionsFulfillmentResult
をAppActionsIntentFulfillmentResult
に型変換します。getIntent
メソッドを呼び出し、(intent.getData.getHost
からアクセスした)ディープリンク URL をアサートして、Android インテントを取得します。INTENT
とDEEPLINK
の両方で、結果として得られるインテントを使用して、選択した 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.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"); }
トラブルシューティング
統合テストで予期せぬエラーが発生した場合は、Android Studio の logcat ウィンドウで AATL ログメッセージを探し、警告またはエラーレベルのメッセージを取得できます。また、ロギングレベルを上げて、ライブラリからより多くの出力をキャプチャすることもできます。
制限事項
現在、App Actions テスト ライブラリの制限事項は次のとおりです。
- AATL では、自然言語理解(NLU)機能や音声文字変換(STT)機能のテストは行われません。
- テストがデフォルト アプリ モジュール以外のモジュールにある場合、AATL は機能しません。
- AATL が互換性を持つのは、Android 7.0「Nougat」(API レベル 24)以降に限られます。