资讯详情

资讯详情

Flutter DeviceLab 实战指南:在真实设备上运行 Flutter 性能与正确性测试

跨平台前端移动开发OpenHarmony【免费下载链接】flutter_flutter本仓库是 Flutter SDK 与 Flutter Engine 的 OpenHarmony 适配版本由 CPF-Flutter 团队维护。开发者可使用熟悉的 Flutter 技术栈开发 OpenHarmony 应用3.35.7 及以后的适配版本可基于本仓库源码构建支持 OpenHarmony 的 Flutter Engine。项目地址https://gitcode.com/CPF-Flutter/flutter_flutter点击查看免费下载DeviceLab 是 Flutter 官方的物理设备测试实验室用于在真实手机上验证 Flutter 的正确性与性能。本指南以flutter_flutter仓库中 dev/devicelab/README.md 为核心骨架结合仓库源码与测试用例系统讲解 DeviceLab 的任务调度模型、本地运行方法、测试编写规范、CI 接入方式以及本仓库在 OpenHarmony 平台上的适配细节。读完本文你将能够在本地复现 CI 失败、编写自己的 DeviceLab 任务、运行引擎 A/B 性能对比测试并理解如何把新测试接入持续集成。DeviceLab 是什么物理设备上的测试框架DeviceLab 是一套在真实物理设备上测试 Flutter 的框架。本仓库中的 dev/devicelab 目录同时包含两样东西测试框架本身的代码位于 dev/devicelab/lib 下包名为flutter_devicelab见 dev/devicelab/pubspec.yaml具体测试位于 dev/devicelab/bin/tasks 下每个.dart文件就是一个独立测试。在框架 API 中这些测试统称为task任务。由于这套框架主要用于测试因此文档统一称之为测试。每个任务都会声明自己需要运行在哪种类型的设备上如linux_android、mac_ios、mac_android、windows_android等。当实验室中某台设备空闲时调度系统就会把匹配的任务分配给这台设备执行。当前各任务的运行状态可以通过 Cocoon 基础设施提供的 Flutter 仪表盘查看README 中给出了构建状态入口仪表盘的使用方式记录在 Cocoon 项目的 dashboard 用户指南中。任务执行的三种结局成功、重试与失败上报DeviceLab 的任务执行结果由测试 runner 统一处理逻辑实现在 dev/devicelab/lib/framework/runner.dart 的runTasks/rerunTask/runTask中规则如下任务成功runner 上报成功并将该任务记录的性能指标上传到 Flutter 基础设施。注意并非所有任务都会记录性能指标——只有通过benchmarkScoreKeys声明了评分键的任务才会产生可上报的指标见 dev/devicelab/lib/framework/task_result.dart 中TaskResult.success对benchmarkScoreKeys的校验逻辑。任务失败后自动重跑每当最后一次运行成功任务最终仍会被记为成功但同时会标记一个flake不稳定标志并写入测试结果。源码中重试次数由 dev/devicelab/lib/framework/metrics_result_writer.dart 的MetricsResultWriter.retryNumber 2决定即默认最多自动重试 2 次runner 在failureCount 0但最终成功时会打印flaky: true。所有重试均失败runner 将失败上报给 Flutter 基础设施此时不会收集任何性能指标最终结果记为flaky: false且进程退出码为 1。此外在 CI 场景下 runner 会通过--results-file将每次任务的执行结果写入文件供 Cocoon 的MetricsResultWriter落盘便于事后审计与回放。本地运行 DeviceLab 测试在部署到 CI 环境之前务必先在本地确认测试可以通过。下面的命令与 CI 的运行方式高度一致也常用于本地复现 CI 失败。前置条件Android 测试需要ANDROID_SDK_ROOT要在本地运行 Android 测试必须先设置环境变量ANDROID_SDK_ROOT。如果你构建过本地 Flutter engine那么 SDK 通常位于.../engine/src/third_party/android_tools/sdk。也可以通过flutter doctor -v查到你机器上 Android SDK 的安装位置。运行前的环境警告在本地运行 DeviceLab 测试会对你的环境产生影响请务必知晓它会自动在你的机器上启动和停止 Gradle当目标 Android 或 iOS 设备累计运行一定数量的测试后runner 会自动重启设备之后才继续运行后续测试。重启逻辑见 dev/devicelab/lib/framework/framework.dart 中的checkForRebootRequired()与 dev/devicelab/lib/framework/devices.dart 中的device.reboot()。从源码看重启阈值由framework.dart顶部的常量maximumRuns 30控制runner 会在设备主目录Linux/macOS 为$HOMEWindows 为%USERPROFILE%维护一个.reboot-count文件每跑完一个任务计数 1达到 30 次后删除计数文件并触发device.reboot()。同时存在一个noRebootForbidList名单名单内的设备例如需要人工干预才能重启的 32 位 iPhone会被排除在自动重启之外。运行框架单元测试test/...目录dev/devicelab/test 目录下存放的是框架自身的单元测试例如 runner、AB 测试、metrics 写入器、hdc 适配层的测试可以通过标准 Dart 测试命令运行dart test test/{NAME_OF_TEST}运行具体任务-t/--task在dev/devicelab目录下使用test_runner.dart的test子命令运行单个任务# 从 .../flutter/dev/devicelab 目录执行 ../../bin/cache/dart-sdk/bin/dart bin/test_runner.dart test -t {NAME_OF_TEST}其中NAME_OF_TEST是任务的名称也就是bin/tasks下某个文件的文件名不含 .dart 后缀例如complex_layout__start_up。也可以在根目录通过bin/run.dart运行../../bin/cache/dart-sdk/bin/dart bin/run.dart -t {NAME_OF_TEST}要一次运行多个任务只需重复-t--task选项../../bin/cache/dart-sdk/bin/dart bin/run.dart -t test1 -t test2 -t test3跳过自动重试--exit默认情况下 DeviceLab 测试内置自动重试逻辑失败的任务会被额外重试 2 次。通过--exit选项可以跳过重试、在第一次失败时立即退出../../bin/cache/dart-sdk/bin/dart bin/test_runner.dart test --exit -t {NAME_OF_TEST}针对本地引擎构建运行测试如果希望让测试基于本地构建的 Flutter engine 运行向bin/run.dart传入对应参数即可../../bin/cache/dart-sdk/bin/dart bin/run.dart --task[some_task] \ --local-engine-src-path[path_to_local]/engine/src \ --local-engine[local_engine_architecture] \ --local-engine-host[local_engine_host_architecture]--local-engine-src-path本地 engine 的src/目录路径。默认取$FLUTTER_ENGINE环境变量或在未设置时根据--flutter-root猜测位置。--local-engineengine out 目录下的某个构建产物名称例如android_debug_unopt_x86。--local-engine-host作为宿主平台使用的引擎构建名称例如host_debug_unopt。这些参数在 dev/devicelab/bin/run.dart 的createArgParser中均有完整定义会被以-DlocalEngine...、-DlocalEngineHost...、-DlocalEngineSrcPath...的形式透传给任务的 Dart VM见 dev/devicelab/lib/framework/runner.dart 的runTask。引擎改动 A/B 性能对比测试A/B 测试用于对比默认引擎与本地引擎构建的性能差异runner 会对同一个 benchmark 在两种引擎下各运行指定次数然后输出一个以 Tab 分隔的结果表并将结果存入 JSON 文件以便日后复现结果可拷贝到电子表格进一步分析JSON 文件则可用summarize.dart命令做更详细的汇总输出。../../bin/cache/dart-sdk/bin/dart bin/run.dart --ab10 \ --local-enginehost_debug_unopt \ --local-engine-hosthost_debug_unopt \ -t bin/tasks/web_benchmarks_canvaskit.dart参数含义--ab10A/B 测试共运行 10 次每组各 10 次。--ab的取值必须是数字否则run.dart会直接抛错。--local-enginehost_debug_unopt指定参与对比的本地引擎构建。A/B 模式必须指定--local-engine移动端场景还必须指定--local-engine-host否则 runner 会拒绝启动。--local-engine-hosthost_debug_unopt用于运行frontend_server此例中的同名引擎构建。-t指定唯一任务。A/B 模式只支持运行恰好一个任务传入多个任务会报错退出校验见run.dart的_runABTest前置检查。--ab-result-filefilename可指定结果 JSON 文件的输出位置默认值为ABresults#.json。文件名中的单个#字符表示若该名称的文件已存在则在此处插入递增序号不含#时同名文件会被直接覆盖去重逻辑见run.dart中的_uniqueFile。示例输出Tab 分隔Score Average A (noise) Average B (noise) Speed-up bench_card_infinite_scroll.canvaskit.drawFrameDuration.average 2900.20 (8.44%) 2426.70 (8.94%) 1.20x bench_card_infinite_scroll.canvaskit.totalUiFrame.average 4964.00 (6.29%) 4098.00 (8.03%) 1.21x draw_rect.canvaskit.windowRenderDuration.average 1959.45 (16.56%) 2286.65 (0.61%) 0.86x draw_rect.canvaskit.sceneBuildDuration.average 1969.45 (16.37%) 2294.90 (0.58%) 0.86x draw_rect.canvaskit.drawFrameDuration.average 5335.20 (17.59%) 6437.60 (0.59%) 0.83x draw_rect.canvaskit.totalUiFrame.average 6832.00 (13.16%) 7932.00 (0.34%) 0.86x输出包含每个指标的平均值与噪声其中噪声定义为总体标准差除以平均值计算实现见 dev/devicelab/lib/framework/ab.dart 的_computeStandardDeviationForPopulation与_ScoreSummary。最关键的是Speed-up列它表示本地引擎相对默认引擎快了多少倍小于 1.0 表示变慢例如0.5x表示本地引擎比默认引擎慢一倍2.0x表示快一倍数值越大越好。用summarize.dart汇总多份结果对 A/B 生成的 JSON 结果文件做进一步汇总处理../../bin/cache/dart-sdk/bin/dart bin/summarize.dart --[no-]tsv-table --[no-]raw-summary \ ABresults.json ABresults1.json ABresults2.json ...--[no-]tsv-table以 Tab 分隔的表格形式打印汇总便于直接粘贴进电子表格默认开启--[no-]raw-summary以 Tab 分隔打印 A/B 测试采集到的全部逐次运行数据默认开启。命令行末尾可跟多个结果文件名每个文件都会被依次处理。另外dev/devicelab/bin/summarize.dart 的源码中还提供了第三个选项--[no-]ascii-table默认开启用于以适合终端展示的对齐表格输出汇总。本地复现 CI 构建失败当 CI 构建失败时可以git checkout到对应的 Flutter revision 在本地复现问题。注意记录失败测试的名称——例如flutter_gallery__transition_perf然后把它作为run.dart的任务名传入../../bin/cache/dart-sdk/bin/dart bin/run.dart -t flutter_gallery__transition_perf编写一个 DeviceLab 测试Task一个测试就是一个位于bin/tasks下、使用package:flutter_devicelab/framework/framework.dart定义并运行task的简单 Dart 程序。核心 API 是framework.dart中导出的task()函数。最小示例import dart:async; import package:flutter_devicelab/framework/framework.dart; Futurevoid main() async { await task(() async { ... do something interesting ... // Aggregate results into a JSONable Map structure. MapString, dynamic testResults ...; // Report success. return new TaskResult.success(testResults); // Or you can also report a failure. return new TaskResult.failure(Something went wrong!); }); }要点解读一个程序中只允许注册一个task。framework.dart中的_isTaskRegistered标志会在重复注册时抛出StateError(A task is already registered)。不过这一个 task 内部可以运行任意数量的子测试。每个 task 都有自己的名称成功与失败相互独立上报到仪表盘时也相互独立。每个 task 运行在独立的 Dart VM 中并通过 Dart VM service 协议新版本 Dart 下退化为 stdin/stdout 的 IPC 协议由环境变量FLUTTER_DEVICELAB_IPC控制上报结果。这样设计可以保证任务之间互不干扰也让 CI 系统能够对卡住的任务做超时清理。framework.dart注册了三个 VM service 扩展点ext.cocoonRunnerReady就绪握手、ext.cocoonRunTask触发执行并可携带timeoutInMinutes、localEngine、runProcessCleanup等参数、ext.cocoonTaskResultReceived结果确认。任务执行完成后runner 还会执行进程清理对比任务运行前后机器上的dart进程快照任何被本测试泄漏出的 Dart 进程都会被终止并导致测试判为失败相关逻辑在framework.dart的run与 dev/devicelab/lib/framework/running_processes.dart 中。关于设备操作系统任务入口处通常需要声明运行平台。以 dev/devicelab/bin/tasks/complex_layout__start_up.dart 为例其第一行就是deviceOperatingSystem DeviceOperatingSystem.android;随后调用task(createComplexLayoutStartupTest())。DeviceOperatingSystem枚举定义在 dev/devicelab/lib/framework/devices.dart本仓库的适配版本中除了android、ios、fuchsia、linux、macos、windows等常见平台外还扩展了ohosOpenHarmony值。将测试接入持续集成CI只有宿主host-only类测试应添加到flutter_tools需要真实设备的任务才属于 DeviceLab 范畴。要把一个新 DeviceLab 任务接入 CI需要提交若干 PR其中_TASK_指测试名称必须与bin/tasks下文件名去掉.dart后缀后的名字一致在根目录的 .ci.yaml 中添加 target镜像一个已有的、使用devicelab/devicelab_dronerecipe 的 target。如果测试需要在多个操作系统上运行请为每个操作系统分别创建独立的 target。devicelab_drone是 DeviceLab 在 LUCI 上的标准 recipe.ci.yaml 中绝大多数物理机测试如Linux analyzer_benchmark、Linux build_aar_module_test等都使用该 recipe并通过task_name属性指向bin/tasks下的任务名。将测试加入 presubmitFlutter 的 DeviceLab 在 presubmit提交前检查中的容量有限。若希望把一个测试加入 presubmit需要先提交team-infraissue由基础设施团队评估可行性后再行添加。Build Test 分离模型更高效地利用设备资源为了充分利用有限的 DeviceLab testbed 资源并缩短提交验证时间当前框架支持把构建产物.apk/.app与测试执行分离产物在一台只有宿主无设备的 botVM 或物理机上构建测试则基于该产物在与设备相连的 testbed 上运行。迁移步骤改造任务类让任务类继承BuildTestTask并覆写以下四个方法getBuildArgs返回传给flutter build的参数getTestArgs返回传给flutter drive的参数parseTaskResult从测试结果构造TaskResultgetApplicationBinaryPath返回被测应用产物的路径。更新任务入口将bin/tasks/{TEST}.dart指向新的任务类例如在其main()中实例化并call()该任务。本地验证仅构建dart bin/test_runner.dart test -t {NAME_OR_PATH_OF_TEST} --task-args build --task-args application-binary-path{PATH_TO_ARTIFACT}仅测试dart bin/test_runner.dart test -t {NAME_OR_PATH_OF_TEST} --task-args test --task-args application-binary-path{PATH_TO_ARTIFACT}接入 CI镜像一个 platform 为Linux_build_test或Mac_build_test的 target。与普通 target 的唯一区别是artifact属性如果省略将默认使用task_name。正式启用CI 验证通过后移除 target 上的bringup: true并删除旧的未采用 buildtest 模型的target 条目即可在PROD环境启用。从 dev/devicelab/lib/tasks/build_test_task.dart 的源码可以看到BuildTestTask的完整行为构造时解析--build/--test/--application-binary-path三个参数分别对应常量kBuildOnlyFlag、kTestOnlyFlag、kApplicationBinaryPathOptionbuild()会先执行flutter clean除非runFlutterClean为 false再执行flutter build并把产物拷贝到applicationBinaryPath供 LUCI recipe 定位并上传产物到 GCStest()会先解锁设备再执行flutter drive最后调用parseTaskResult()call()中若同时传入build与test会抛出异常仅build时返回TaskResult.buildOnly()No tests run。原仓库在 gallery 相关任务上已经完成了这一迁移Linux/Android 与 Mac/iOS 各有对应的分离与切换 PR 记录。本仓库的 OpenHarmonyohos适配扩展作为 Flutter 的 OpenHarmony 适配版本本仓库在 DeviceLab 中为 OpenHarmony 平台做了系统性的扩展可从源码中确认任务集dev/devicelab/bin/tasks 下存在大量以_ohos结尾的任务文件例如complex_layout__start_up_ohos.dart、flutter_gallery__start_up_ohos.dart、platform_channels_benchmarks_ohos.dart、integration_ui_screenshot_ohos.dart、run_release_test_ohos.dart、basic_material_app_ohos__compile.dart等覆盖启动性能、滚动流畅度、内存、平台通道、集成 UI、release 运行等场景。设备抽象dev/devicelab/lib/framework/devices.dart 中新增了DeviceOperatingSystem.ohos枚举值并实现了OhosDeviceDiscovery与OhosDevice两个类设备发现通过hdc list targets完成自动过滤[Empty]与Offline行设备控制通过hdc shell系列命令实现例如用power-shell wakeup/suspend唤醒/休眠、uitest uiInput keyEvent Home模拟 Home 键、aa force-stop停进程、pidof/hidumper --mem读取内存、hilog采集日志、shell reboot重启设备等。测试覆盖dev/devicelab/test/hdc_test.dart 等框架测试验证了 hdc 命令的调用行为例如通过 hdc 发送重启命令等。这意味着在 OpenHarmony 适配版中你可以使用与 Android/iOS 完全一致的 DeviceLab 工作流来编写、运行和接入 ohos 设备上的任务只需要在任务入口设置deviceOperatingSystem DeviceOperatingSystem.ohos;并在 CI 的.ci.yaml中镜像devicelab/devicelab_dronerecipe 的 target 即可。小结DeviceLab 是 Flutter 真实设备测试的中枢任务task是独立 Dart 程序、运行在独立 VM 中通过 VM service/IPC 与 runner 通信本地可通过run.dart/test_runner.dart以-t运行单任务、以--exit关闭自动重试、以--local-engine*系列参数对接本地引擎、以--ab进行引擎性能 A/B 对比新任务写入bin/tasks后通过.ci.yaml中devicelab_dronerecipe 接入 CI并可通过BuildTestTask采用 buildtest 分离模型节省设备资源。在本仓库中这套框架已经完整支持 OpenHarmony 设备DeviceOperatingSystem.ohos hdc 工具链为在真实 OpenHarmony 硬件上持续验证 Flutter 适配质量提供了坚实底座。赞分享跨平台前端移动开发OpenHarmony【免费下载链接】flutter_flutter本仓库是 Flutter SDK 与 Flutter Engine 的 OpenHarmony 适配版本由 CPF-Flutter 团队维护。开发者可使用熟悉的 Flutter 技术栈开发 OpenHarmony 应用3.35.7 及以后的适配版本可基于本仓库源码构建支持 OpenHarmony 的 Flutter Engine。项目地址https://gitcode.com/CPF-Flutter/flutter_flutter点击查看免费下载相关推荐3 步隐藏 macOS 菜单栏图标Ice 菜单栏管理上手指南3 步隐藏 macOS 菜单栏图标Ice 菜单栏管理上手指南 MacBook 用了两年菜单栏右半边已经挤了 20 多个图标常用的三个还总被挤到刘海下面。桌面应用在 PR 上重跑 Flutter DeviceLab 测试基于 LUCI led 的完整实战指南在 PR 上重跑 Flutter DeviceLab 测试基于 LUCI led 的完整实战指南 DeviceLab设备实验室是 Flutter 在真实物跨平台移动开发前端UI组件桌面应用Flutter 团队 espresso 包实战指南在 Android 上为 Flutter 应用编写与运行 Espresso 测试Flutter 团队 espresso 包实战指南在 Android 上为 Flutter 应用编写与运行 Espresso 测试 导读 espresso 是移动开发跨平台上一篇Neovide 完全指南用 Rust 打造的 Neovim 图形界面客户端从安装到配置与源码架构下一篇CANN ops-nn ForeachZeroInplace 算子全解析张量列表原地置零的 aclnn 两段式调用与 NPU 实现原理创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →