Flutter Android UI 自动化测试实战:基于 espresso 包的 Espresso 测试编写与运行指南(espresso_example 篇)

Flutter Android UI 自动化测试实战:基于 espresso 包的 Espresso 测试编写与运行指南(espresso_example 篇) Flutter Android UI 自动化测试实战基于 espresso 包的 Espresso 测试编写与运行指南espresso_example 篇【免费下载链接】packagesA collection of useful packages maintained by the Flutter team项目地址: https://gitcode.com/GitHub_Trending/pac/packages导读本文以 Flutter 官方插件仓库中 espresso 包的示例应用 espresso_example 为蓝本系统讲解如何在 Android 上为 Flutter 应用编写、构建并运行 Espresso 测试。你将掌握从依赖安装、明文流量配置、Java 侧测试用例编写、Dart 集成测试驱动到本地 Gradle 与 Firebase Test Lab 两种运行方式的完整链路并理解 espresso 插件在底层如何与 Flutter 引擎通信、完成查找 Widget → 执行操作 → 断言的同步机制。一、示例项目定位espresso_example 演示了什么espresso 包为 Flutter Android 应用提供 Espresso 测试绑定bindings其 包 README 明确标注支持平台仅限 Android且要求 Android SDK 24。作为演示项目espresso_example的核心价值在于完整展示一个可运行的 Espresso 测试工程结构它包含了四类关键文件Dart 侧被测应用example/lib/main.dart一个标准的计数器应用Java 侧 Espresso 测试example/android/app/src/androidTest/java/io/flutter/com/example/espresso_example/MainActivityTest.java集成测试驱动脚本example/test_driver/integration_test.dart集成测试占位用例example/integration_test/espresso_launch_test.dart。需要特别说明的一点也是示例 README 反复强调的espresso 只在 Android 上真正运行测试。示例工程虽然保留了 iOS 目录以保持 Flutter 工程结构完整但这只是为了维持 CI持续集成机器保持绿色。因此当你在自己的项目中引入 espresso 时应将其视为纯 Android 的 UI 自动化测试方案而非跨平台方案。二、环境准备依赖与构建配置2.1 pubspec.yaml 中的测试依赖在 example/pubspec.yaml 中可以看到完整的依赖声明。示例工程因为与插件同仓库开发使用path: ../引用当前插件源码真实应用则应使用版本约束espresso: ^x.y.zdev_dependencies: espresso: # When depending on this package from a real application you should use: # espresso: ^x.y.z path: ../ flutter_driver: sdk: flutter flutter_test: sdk: flutter integration_test: sdk: flutter要点espresso作为dev_dependency引入如果是测试某个包的示例应用建议也加到主包的 dev_dependencies 中必须同时引入integration_test与flutter_driver它们是运行 Espresso 测试时驱动 Flutter 引擎的配套依赖。2.2 android/app/build.gradle.kts 中的测试依赖在 example/android/app/build.gradle.kts 的dependencies块中示例工程声明了 JUnit、AndroidX Test、Truth 与 Espresso 四组依赖完整可复制的核心片段如下dependencies { testImplementation(junit:junit:4.13.2) api(androidx.test:core:1.6.1) androidTestImplementation(androidx.test:runner:1.6.1) androidTestImplementation(com.google.truth:truth:1.1.3) androidTestImplementation(androidx.test.espresso:espresso-core:3.6.1) }在该文件基础上示例还补充了espresso-contrib、espresso-intents、espresso-web、espresso-accessibility以及 idling 相关依赖espresso-idling-resource、idling-concurrent用于覆盖更丰富的 Espresso 场景。同时defaultConfig中需要设置testInstrumentationRunner androidx.test.runner.AndroidJUnitRunner这是 Android Instrumentation 测试androidTest的入口 RunnerEspresso 测试依赖它完成用例调度。三、开启明文流量network_security_config 配置espresso 插件通过WebSocket与 Flutter 引擎协调测试包 README 原文Espresso uses cleartext traffic via a websocket to coordinate testing因此必须为被测应用开启明文流量cleartext traffic许可——但官方明确建议仅在 debug 或 androidTest 构建中开启绝不能随正式包发布。配置分两步第一步在 AndroidManifest.xml 的application节点上引用网络安全配置。示例工程将其放在 debug 构建专属清单 example/android/app/src/debug/AndroidManifest.xml 中manifest xmlns:androidhttp://schemas.android.com/apk/res/android !-- Flutter needs it to communicate with the running application to allow setting breakpoints, to provide hot reload, etc. -- uses-permission android:nameandroid.permission.INTERNET/ application android:networkSecurityConfigxml/network_security_config / /manifest第二步在res/xml/目录创建 network_security_config.xmlnetwork-security-config !-- Cleartext is needed for Espresso testing. -- base-config cleartextTrafficPermittedtrue /base-config /network-security-config实践建议把INTERNET权限、networkSecurityConfig引用与上述 xml 文件都放在src/debug/或src/androidTest/下主清单 example/android/app/src/main/AndroidManifest.xml 保持干净避免将调试能力泄漏到生产包。四、编写测试从 Java 测试类到 Flutter 侧交互4.1 被测应用带 key 与 tooltip 的计数器先看被测应用 example/lib/main.dart。它是个标准 Flutter 计数器应用其中两个细节是为 Espresso 测试埋点的计数文本Text(Button tapped $_counter time(s).)携带key: const ValueKeyString(CountText)供 Espresso 用withValueKey精确匹配FloatingActionButton设置tooltip: Increment供 Espresso 用withTooltip匹配点击目标。这正是 Espresso 测试 Flutter 应用的通用套路为要交互/断言的 Widget 提供稳定的 ValueKey 或 tooltip作为跨语言Java↔Dart识别的锚点。4.2 Java 侧测试用例在 example/android/app/src/androidTest/java/io/flutter/com/example/espresso_example/MainActivityTest.java 中可以看到接入 Flutter 集成测试的 Java 骨架DartIntegrationTest RunWith(FlutterTestRunner.class) public class MainActivityTest { Rule public ActivityTestRuleMainActivity rule new ActivityTestRule(MainActivity.class, true, false); }这里DartIntegrationTest是仓库自定义的 Kotlin 注解见 android/src/androidTest/kotlin/io/flutter/plugins/DartIntegrationTest.kt用于标记这是一个需要联动 Dart 集成测试的 Java 类FlutterTestRunner则来自 integration_test 插件负责先拉起 Flutter 引擎再执行 Dart 侧用例。而包 README 给出了更典型的纯 Espresso 风格用例可直接套用于你自己的工程路径形如android/app/src/androidTest/java/com/example/MainActivityTest.javapackage com.example.espresso_example; import static androidx.test.espresso.flutter.EspressoFlutter.onFlutterWidget; import static androidx.test.espresso.flutter.action.FlutterActions.click; import static androidx.test.espresso.flutter.action.FlutterActions.syntheticClick; import static androidx.test.espresso.flutter.assertion.FlutterAssertions.matches; import static androidx.test.espresso.flutter.matcher.FlutterMatchers.isDescendantOf; import static androidx.test.espresso.flutter.matcher.FlutterMatchers.withText; import static androidx.test.espresso.flutter.matcher.FlutterMatchers.withTooltip; import static androidx.test.espresso.flutter.matcher.FlutterMatchers.withType; import static androidx.test.espresso.flutter.matcher.FlutterMatchers.withValueKey; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.fail; import androidx.test.core.app.ActivityScenario; import androidx.test.espresso.flutter.EspressoFlutter.WidgetInteraction; import androidx.test.espresso.flutter.assertion.FlutterAssertions; import androidx.test.espresso.flutter.matcher.FlutterMatchers; import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; /** Unit tests for {link EspressoFlutter}. */ RunWith(AndroidJUnit4.class) public class MainActivityTest { Before public void setUp() throws Exception { ActivityScenario.launch(MainActivity.class); } Test public void performClick() { onFlutterWidget(withTooltip(Increment)).perform(click()); onFlutterWidget(withValueKey(CountText)).check(matches(withText(Button tapped 1 time.))); } }这段用例完整演示了 espresso 的三个核心 API 层级与 android/src/main/java/androidx/test/espresso/flutter/ 下的源码一一对应API 层级静态入口源码位置作用交互入口EspressoFlutter.onFlutterWidget(matcher)EspressoFlutter.java依据 matcher 唯一匹配一个 Flutter Widget返回WidgetInteraction类比 Espresso 的onViewMatcherFlutterMatchers.withTooltip / withValueKey / withText / withType / isDescendantOfFlutterMatchers.java定位 Flutter Widget按 tooltip、ValueKey、文本、运行时类型、父子关系匹配Action / AssertionFlutterActions.click / syntheticClick、FlutterAssertions.matchesaction/、assertion/对 Widget 执行点击等操作、对 Widget 状态做断言从 EspressoFlutter.java 的源码可以看到WidgetInteraction提供了链式方法perform(...)与check(...)且perform支持传入多个 action 按序执行任一步出错都会以未检查异常中断后续动作从而保证测试的可控性。底层还引入了DEFAULT_INTERACTION_TIMEOUT定义于 common/Constants.java并额外叠加 1 秒余量作为交互超时避免在引擎尚未空闲时误判超时。4.3 Dart 侧集成测试与驱动脚本espresso 与 integration_test 的衔接方式是先跑 Dart 集成测试为空也可再由驱动脚本把测试结果回传给 Java 侧。示例工程中example/integration_test/espresso_launch_test.dart 是一个占位用例仅初始化IntegrationTestWidgetsFlutterBinding并运行一个空testWidgets。它的作用是让 Flutter 引擎以集成测试模式启动从而与 espresso 的 WebSocket 协调通道对接example/test_driver/integration_test.dart 是必需的驱动脚本内容仅数行import package:integration_test/integration_test_driver.dart; Futurevoid main() integrationDriver();包 README 明确指出你必须创建这样一个 driver 脚本放在test_driver/目录下否则flutter drive/Espresso 无法运行你的 Dart 集成测试。五、运行测试本地与云真机两种方式5.1 本地运行示例 README 给出的最简运行方式是两条命令flutter build apk --debug ./gradlew app:connectedAndroidTest第一条命令先构建 debug APKespresso 测试依赖 debug 构建中的明文流量与调试通道配置第二条命令在已连接设备/模拟器上执行 androidTest。若需指定驱动 Dart 集成测试的入口文件包 README 给出了带-Ptarget的完整形式./gradlew app:connectedAndroidTest -Ptargetpwd/../test_driver/integration_test.dart5.2 在 Firebase Test Lab 上运行当需要在 Firebase Test Lab 的云真机上批量执行测试时包 README 提供了完整流程先分别构建被测 APK 与测试 APK再通过gcloud firebase test android run提交./gradlew app:assembleAndroidTest ./gradlew app:assembleDebug -Ptargetpath_to_test.dart gcloud auth activate-service-account --key-filePATH_TO_KEY_FILE gcloud --quiet config set project PROJECT_NAME gcloud firebase test android run --type instrumentation \ --app build/app/outputs/apk/debug/app-debug.apk \ --test build/app/outputs/apks/androidTest/debug/app-debug-androidTest.apk \ --timeout 2m \ --results-bucketRESULTS_BUCKET \ --results-dirRESULTS_DIRECTORY其中--app指向被测应用 debug APK--test指向 androidTest APK--timeout 2m限制单次执行时长--results-bucket/--results-dir指定结果存储位置。六、工作机制与注意事项小结6.1 espresso 的底层工作方式从源码结构可以梳理出 espresso 插件的大致协作链路Java 侧通过EspressoFlutter.onFlutterWidget拿到与 FlutterView 绑定的WidgetInteraction插件经OkHttp WebSocketEspressoFlutter中持有一个静态OkHttpClient连接 Dart VM Service见 internal/protocol/impl/DartVmService.java把 matcher、action、assertion 序列化为 JSON-RPC 请求下发到 Flutter 引擎Flutter 侧解析 Widget 树完成匹配与操作返回结果含WidgetInfo等模型见 model/执行前后通过WaitCondition如 NoPendingFrameCondition.java、NoTransientCallbacksCondition.java等待 Flutter 进入空闲状态保证操作与断言的同步可靠性。这与 Espresso 对原生 View 的同步保证理念一致操作前确保 UI 空闲避免竞态。6.2 关键注意事项仅 Android 可用espresso 不支持 iOS、Web、桌面端iOS 目录仅为 CI 兼容保留SDK 24包 README 明确支持范围是 Android SDK 24 及以上明文流量仅限测试构建务必把INTERNET权限与networkSecurityConfig放进 debug/androidTest 清单避免泄漏到 release 包ValueKey/tooltip 是跨语言锚点为被测 Widget 提供稳定标识是编写可维护 Espresso 测试的前提driver 脚本不可省略test_driver/integration_test.dart中的integrationDriver()是 Java 测试与 Dart 集成测试之间的桥梁。至此从示例 README 的两条命令出发你已经掌握了一整套依赖配置 → 网络安全配置 → Java 用例编写 → Dart 驱动衔接 → 本地/云端运行的 Flutter Android Espresso 测试方案。参考本仓库 espresso 示例 的完整工程结构与 espresso 包源码即可快速在自己的 Flutter Android 工程中落地。【免费下载链接】packagesA collection of useful packages maintained by the Flutter team项目地址: https://gitcode.com/GitHub_Trending/pac/packages创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考