App Actions 测试库 (AATL) 提供的功能使开发者能够以编程方式测试 App Action 履行,从而自动化通常使用实际语音查询或 App Actions 测试工具进行的测试。
该库有助于确保shortcut.xml
配置正确,并且描述的 Android 意图调用成功。App Actions 测试库提供了一种机制来测试您的应用履行给定的 Google 助理意图和参数的能力,方法是将它们转换为可以断言并用于实例化 Android 活动的 Android 深层链接或 Android 意图。
测试以 Robolectric 单元测试或 Android 环境中的工具测试的形式执行。这允许开发者通过模拟实际应用行为来全面测试他们的应用。对于测试 BII、自定义意图或深层链接履行,可以使用任何工具测试框架(UI Automator、Espresso、JUnit4、Appium、Detox、Calabash)。
如果应用是多语言的,开发者可以验证应用的功能在不同的语言环境中是否正常运行。
工作原理
要在应用的测试环境中集成 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 测试库依赖项
将 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 Context 参数(从
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
履行。- 通常,开发者可以通过查看他们正在通过触发库来履行的
shortcuts.xml
中的意图标签来区分INTENT
和DEEPLINK
履行。 - 如果意图标签下有 url-template 标签,则表示
DEEPLINK
履行了此意图。 - 如果结果意图的
getData()
方法返回非空对象,这也表示DEEPLINK
完成。同样,如果getData
返回null
,则表示它是INTENT
完成。
- 通常,开发者可以通过查看他们正在通过触发库来履行的
- 对于
INTENT
情况,将AppActionsFulfillmentResult
类型转换为AppActionsIntentFulfillmentResult
,通过调用getIntent
方法获取Android Intent,然后执行以下操作之一:- 断言Android Intent的各个字段。
- 断言通过intent.getData.getHost方法访问的intent的uri。
- 对于
DEEPLINK
情况,将AppActionsFulfillmentResult
类型转换为AppActionsIntentFulfillmentResult
(与上面的INTENT
场景相同),通过调用getIntent
方法获取Android Intent,并断言深度链接URL(通过intent.getData.getHost
访问)。 - 对于
INTENT
和DEEPLINK
,您可以使用生成的intent,使用选择的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“牛轧糖”(API级别24)及更高版本兼容。