与应用有关的 Action 测试库 (AATL) 提供多项功能,可让开发者以编程方式测试与应用有关的 Action 执行方式,从而自动执行通常使用实际语音查询或与应用有关的 Action 测试工具完成的测试。
该库有助于确保 shortcut.xml
配置正确无误且成功调用所述的 Android intent。与应用有关的 Action 测试库提供一种机制,可测试您的应用执行给定 Google 助理 intent 和参数的能力,方法是将这些 intent 和参数转换为可被断言并用于实例化 Android activity 的 Android 深层链接或 Android intent。
在 Android 环境中,测试形式为 Robolectric 单元测试或插桩测试。这样一来,开发者便可以通过模拟实际应用行为来全面测试其应用。如需测试 BII、自定义 intent 或深层链接执行方式,开发者可以使用任意插桩测试框架(UI Automator、Espresso、JUnit4、Appium、Detox、Calabash)。
如果应用支持多种语言,开发者可以验证应用的功能在不同的语言区域中是否正常运行。
运作方式
如需在应用的测试环境中集成与应用有关的 Action 测试库,开发者应在应用的 app
模块中创建新的或更新现有的 Robolectric 测试或插桩测试。
测试代码包含以下部分:
- 在通用设置方法或单个测试用例中初始化库实例。
- 每个单个测试都会调用库实例的
fulfill
方法,以生成 intent 创建结果。 - 然后,开发者断言深层链接或触发应用执行方式,并针对应用状态运行自定义验证。
设置要求
为了使用测试库,您必须先调整一些初始应用配置,然后才能将测试添加到应用。
配置
如需使用与应用有关的 Action 测试库,请确保按如下方式配置您的应用:
- 安装 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>
元素内
添加与应用有关的 Action 测试库依赖项
将 Google 仓库添加到
settings.gradle
中的项目制品库列表中: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 结果修改启动 activity 的方式。以下是使用
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
对象。
执行断言
断言与应用有关的 Action 测试库的推荐方法是:
- 断言
AppActionsFulfillmentResult
的执行方式类型。它必须是FulfillmentType.INTENT
或FulfillmentType.UNFULFILLED
,才能测试应用在出现意外 BII 请求时的行为。 - 执行方式有两种:
INTENT
和DEEPLINK
执行方式。- 通常,如需区分
INTENT
和DEEPLINK
执行方式,开发者只需查看shortcuts.xml
中通过触发库实现的 intent 标记。 - 如果 intent 标记下具有网址模板标记,则表示
DEEPLINK
会执行此 intent。 - 如果结果 intent 的
getData()
方法返回非 null 对象,也表示DEEPLINK
执行方式。同样,如果getData
返回null
,则表示它是INTENT
执行方式。
- 通常,如需区分
- 对于
INTENT
情形,将AppActionsFulfillmentResult
转换为AppActionsIntentFulfillmentResult
,然后通过调用getIntent
方法提取 Android intent,并执行以下其中一项操作:- 断言 Android intent 的各个字段。
- 断言通过 intent.getData.getHost 方法访问的 intent 的 URI。
- 对于
DEEPLINK
情形,将AppActionsFulfillmentResult
转换为AppActionsIntentFulfillmentResult
(与上面的INTENT
情形相同),通过调用getIntent
方法提取 Android intent,并断言深层链接网址(通过intent.getData.getHost
访问)。 - 对于
INTENT
和DEEPLINK
,您可以使用生成的 intent 启动拥有所选 Android 测试框架的 activity。
国际化
如果您的应用支持多种语言,您可以将配置为在特定受测语言区域中运行测试。或者,您也可以直接更改语言区域:
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 日志消息,以获取警告或错误级别的消息。您还可以提高日志记录级别,从库中截取更多输出。
限制
以下是与应用有关的 Action 测试库的当前限制:
- AATL 不会测试自然语言理解 (NLU) 或语音转文字 (STT) 功能。
- 如果测试位于默认应用模块以外的模块中,则 AATL 不起作用。
- AATL 仅与 Android 7.0“Nougat”(API 级别 24)及更高版本兼容。