应用操作测试库 (AATL) 提供功能,使开发者能够以编程方式测试应用操作的实现,自动执行通常使用实际语音查询或应用操作测试工具进行的测试。
该库有助于确保 shortcut.xml
配置正确,并且描述的 Android 意图调用成功。应用操作测试库提供了一种机制来测试应用程序在给定 Google Assistant 意图和参数的情况下实现的能力,方法是将它们转换为 Android 深度链接或 Android 意图,这些意图可以断言并用于实例化 Android 活动。
测试以 Robolectric 单元或 Android 环境中的仪器测试的形式执行。这使开发者可以通过模拟实际的应用程序行为来全面测试他们的应用程序。对于测试 BII、自定义意图或深度链接实现,可以使用任何仪器测试框架(UI Automator、Espresso、JUnit4、Appium、Detox、Calabash)。
如果应用程序是多语言的,开发者可以验证应用程序的功能在不同的语言环境中是否正常运行。
工作原理
为了将应用操作测试库集成到应用程序的测试环境中,开发者应该在应用程序的 app
模块上创建新的或更新现有的 Robolectric 或仪器测试。
测试代码包含以下部分
- 在通用设置方法或单个测试用例中初始化库实例。
- 每个单独的测试都会调用库实例的
fulfill
方法来生成意图创建结果。 - 然后,开发者断言深度链接或触发应用程序实现,并在应用程序状态上运行自定义验证。
设置要求
为了使用测试库,在将测试添加到应用程序之前,需要进行一些初始应用程序配置。
配置
要使用应用操作测试库,请确保您的应用程序配置如下
- 安装 Android Gradle 插件 (AGP)
- 在
app
模块的res/xml
文件夹中包含shortcuts.xml
文件。 - 确保
AndroidManifest.xml
在以下任一位置包含<meta-data android:name="android.app.shortcuts" android:resource=”@xml/shortcuts” />
- the
<application>
标签 - 启动器
<activity>
标签
- the
- 将
<capability>
元素放在shortcuts.xml
中的<shortcuts>
元素内
添加应用操作测试库依赖项
在
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 结果修改启动 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 中的参数匹配。例如,
order.orderedItem.name
与GET_ORDER
中参数的文档匹配。使用 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
对象。
执行断言
建议断言应用操作测试库的方式是
- 断言
AppActionsFulfillmentResult
的实现类型。它必须是FulfillmentType.INTENT
,或者在测试应用程序在遇到意外 BII 请求时如何运行的情况下是FulfillmentType.UNFULFILLED
。 - 实现有两种形式:
INTENT
和DEEPLINK
实现。- 通常,开发者可以通过查看要通过触发库来实现的
shortcuts.xml
中的 intent 标签来区分INTENT
和DEEPLINK
实现。 - 如果 intent 标签下有 url-template 标签,则表明
DEEPLINK
实现此 intent。 - 如果结果意图的
getData()
方法返回非空对象,则也表示DEEPLINK
满足。类似地,如果getData
返回null
,则意味着它是INTENT
满足。
- 通常,开发者可以通过查看要通过触发库来实现的
- 对于
INTENT
情况,将AppActionsFulfillmentResult
转换为AppActionsIntentFulfillmentResult
,通过调用getIntent
方法获取 Android 意图,然后执行以下操作之一- 断言 Android 意图的各个字段。
- 断言通过 intent.getData.getHost 方法访问的意图的 uri。
- 对于
DEEPLINK
情况,将AppActionsFulfillmentResult
转换为AppActionsIntentFulfillmentResult
(与上面的INTENT
场景相同),通过调用getIntent
方法获取 Android 意图,并断言深层链接 URL(通过intent.getData.getHost
访问)。 - 对于
INTENT
和DEEPLINK
,您可以使用生成的意图使用选择的 Android 测试框架启动活动。
国际化
如果您的 App 有多种语言环境,您可以配置测试以在测试中运行特定语言环境。或者,您可以直接更改语言环境
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 Test Library 的当前限制
- AATL 不测试自然语言理解 (NLU) 或语音到文本 (STT) 功能。
- 当测试位于默认应用程序模块以外的模块中时,AATL 不起作用。
- AATL 仅与 Android 7.0“牛轧糖”(API 级别 24)及更高版本兼容。