news 2026/9/18 10:44:33

Pigeon 平台测试中的共享 Dart 代码包:shared_test_plugin_code 如何统一两个原生插件的测试骨架

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Pigeon 平台测试中的共享 Dart 代码包:shared_test_plugin_code 如何统一两个原生插件的测试骨架

Pigeon 平台测试中的共享 Dart 代码包:shared_test_plugin_code 如何统一两个原生插件的测试骨架

【免费下载链接】packagesA collection of useful packages maintained by the Flutter team项目地址: https://gitcode.com/GitHub_Trending/pac/packages

shared_test_plugin_code是 Pigeon 仓库platform_tests目录下负责“跨插件共享 Dart 代码”的内部包:它集中存放 Pigeon 生成的 Dart 输出、示例 App 与集成测试,供test_pluginalternate_language_test_plugin两个原生测试插件复用。读完本文,你可以理解 Flutter 插件体系在“一个平台只能注册一种语言实现”的约束下,如何通过共享包消除两侧 Dart 测试逻辑的重复,并能准确找到生成物、集成测试入口、mock 再生成脚本以及测试驱动脚本在仓库中的位置。

为什么需要一个共享代码包

Pigeon 的平台级测试(platform_tests)由两个插件工程组成:

  • test_plugin:各平台“默认(首选)插件语言”的统一测试骨架,覆盖 Kotlin(Android)、Swift(iOS/macOS)、C++(Linux)与 C++(Windows);
  • alternate_language_test_plugin:面向多语言平台的“备选语言”,目前覆盖 Android 的 Java 与 iOS 的 Objective-C。

shared_test_plugin_code 的 README 给出了这个共享包存在的根本原因:

这两个插件项目在设计上是完全一致的(intended to be identical),之所以必须拆成两个独立项目,仅仅是因为 Java/Kotlin 与 Obj-C/Swift 的语言重叠使得它们无法合并到同一个插件里;因此,几乎所有的 Dart 代码都应该放在这个包中,而不是放在插件内部。

从 Flutter 插件机制看这一约束是成立的:插件注册(pubspec.yamlflutter.plugin.platforms)每个平台只能指定一个pluginClasstest_plugin的 pubspec.yaml 中,Android 注册com.example.test_plugin.TestPlugin(Kotlin 实现)、Windows 注册TestPluginCApi;而 alternate_language_test_plugin 的 pubspec.yaml 中 Android 注册com.example.alternate_language_test_plugin.AlternateLanguageTestPlugin(Java 实现)。同一个平台不能同时挂 Kotlin 与 Java 两套实现,所以测试骨架被迫拆成两个插件工程。共享包的设计正是为了在这种拆分下保证 Dart 侧只有一份事实来源(single source of truth)。

说明:README 中写作alternate_language_shared_plugin,而仓库中对应的实际目录名为 alternate_language_test_plugin。

共享包里到底放了什么

对照 shared_test_plugin_code 目录结构,可以把它的内容归纳为四类,恰好与 README 所说的“generated Pigeon output, example app, integration tests”一一对应。

1. Pigeon 生成的 Dart 输出(lib/src/generated/

lib/src/generated/ 下有 15 个.gen.dart文件,覆盖了 Pigeon 各类生成场景的 Dart 端:

生成文件覆盖的 Pigeon 特性
core_tests.gen.dart核心 API/数据类互调(与两侧原生.gen文件对应)
enum.gen.dart枚举映射
event_channel_tests.gen.dartEventChannel 风格的流式回调
event_channel_without_classes_tests.gen.dart不依赖类的 EventChannel
message.gen.dart基础消息结构
multiple_arity.gen.dart参数数量较多的方法(arity)
native_interop_tests.gen.dart 及 .ffi / .jni 变体Native Interop(FFI/JNI 路径)
non_null_fields.gen.dart / null_fields.gen.dart可空/非空字段语义
nullable_returns.gen.dart可空返回值
primitive.gen.dart原始类型
proxy_api_tests.gen.dartProxy API(仅 Kotlin/Swift 支持,见下文)
flutter_unittests.gen.dartFlutter 单测场景

这些生成物对外通过一个 barrel 文件 lib/generated.dart 统一导出(如HostIntegrationCoreApiProxyApiTestClass等),各插件的示例 App 与集成测试只需import 'generated.dart'即可。

2. 集成测试逻辑(lib/*.dart

lib/integration_tests.dart(约 2600 行)是共享包的主体,它按“目标生成器”参数化地组织所有集成测试。其中两个关键定义:

/// Possible host languages that test can target. enum TargetGenerator { cpp, // Windows C++ gobject, // Linux GObject java, kotlin, // Android objc, swift, // iOS/macOS } /// Host languages that support generating Proxy APIs. const Set<TargetGenerator> proxyApiSupportedLanguages = <TargetGenerator>{ TargetGenerator.kotlin, TargetGenerator.swift, };

(见 integration_tests.dart#L22-L46。)入口函数runPigeonIntegrationTests(TargetGenerator targetGenerator)由两个插件的 example 集成测试调用(L49),从而用同一套 Dart 断言去驱动 Java/Kotlin 或 Obj-C/Swift 的原生实现。同目录下的 native_interop_integration_tests.dart、proxy_api_integration_tests.dart、test_types.dart 与 comparison_benchmarks.dart 分别承载 FFI 互操作测试、Proxy API 测试、共享测试类型与对比基准。

3. 示例 App(example app)

lib/example_app.dart 提供一个极简ExampleApp:在initPlatformState中创建HostIntegrationCoreApi并调用api.noop(),用于“验证 Pigeon 能够成功调用原生代码”,界面上显示Calling.../Success!/Failed: ...。其源码注释明确说明:真正的测试全部在集成测试中完成,它们运行在 example app 的上下文里,但并不依赖这个 UI 类。

4. Dart 单元测试(test/

test/ 下是一批纯 Dart 单元测试(如 primitive_test.dart、multiple_arity_test.dart、null_fields_test.dart、proxy_api_overrides_test.dart 等),配合 mockito 生成的.mocks.dart文件对生成代码的 Dart 端行为做回归验证。

包配置:几处值得注意的细节

pubspec.yaml:为什么 flutter_test / integration_test 是普通依赖

pubspec.yaml 的关键信息:

name: shared_test_plugin_code description: Common code for test_plugin and alternate_language_test_plugin version: 0.0.1 publish_to: none # 纯内部包,不发布到 pub.dev environment: sdk: ^3.10.0 flutter: ">=3.38.0" dependencies: build_runner: ^2.1.10 ffi: ^2.2.0 flutter: {sdk: flutter} # These are normal dependencies rather than dev_dependencies because the # package exports the integration test code to be shared by the integration # tests for the two plugins. flutter_test: {sdk: flutter} integration_test: {sdk: flutter} jni: ^1.0.3 meta: ^1.17.0 mockito: ^5.4.4 objective_c: ^9.5.0 dev_dependencies: ffigen: ^22.0.0 jnigen: ^1.0.0 leak_tracker: any

其中两条注释解释了设计取舍:

  1. flutter_testintegration_test之所以放在dependencies而非dev_dependencies,是因为本包要把集成测试代码导出给两个插件的集成测试使用——dev_dependencies不会传递给依赖方;
  2. ffijniobjective_c是 Dart 侧直接调原生(Native Interop 路径)所需的运行期依赖;ffigenjnigen则作为 dev 依赖用于在本地重新生成 FFI/JNI 绑定(相关配置位于 test_plugin/tool/pigeon/ 下的native_interop_tests_ffigen_config.dartnative_interop_tests_jnigen_config.dart)。

另外注意版本基线差异:共享包要求 Dart^3.10.0/ Flutter>=3.38.0,而 test_plugin 要求 Dart^3.12.0/ Flutter>=3.44.0,说明统一测试骨架对 Flutter 版本的要求更高。

dart_test.yaml 与 mock 再生成

  • dart_test.yaml 只有一行test_on: vm,即包内单元测试只在 Dart VM 上运行,不涉及浏览器/WASM;
  • regenerate_mocks.sh 的内容是dart run build_runner build --delete-conflicting-outputs,用于在修改 mock 注解后重新生成test/*.mocks.dart

两个消费方如何接入

test_plugin(首选语言)

  • 插件注册(pubspec.yaml#L10-L25):Android →com.example.test_plugin.TestPlugin;iOS/macOS →TestPluginsharedDarwinSource: true;Linux →TestPlugin;Windows →TestPluginCApi
  • 各平台生成物:Android 的 CoreTests.gen.kt 等 Kotlin 文件、macOS/iOS 的 CoreTests.gen.swift、Linux 的 core_tests.gen.cc、Windows 的 core_tests.gen.cpp;
  • 原生单元测试:Android 侧 android/src/test/kotlin/ 下的AsyncHandlersTest.ktMultipleArityTests.kt等,iOS/macOS 侧 example/ios/RunnerTests/ 下的 Swift 测试;
  • 集成测试入口:example/integration_test/test.dart(以对应平台的TargetGenerator调用共享包的runPigeonIntegrationTests),Dart 侧的示例 App 即共享包的 example_app.dart。

alternate_language_test_plugin(备选语言)

  • 平台范围更小:仅 Android 与 iOS/macOS(pubspec.yaml#L10-L21),没有 Linux/Windows 配置;
  • Android 实现为 Java(CoreTests.java),原生单测在 android/src/test/java/ 下的PrimitiveTest.javaAsyncTest.java等;
  • iOS 实现为 Objective-C(CoreTests.gen.m 与 AlternateLanguageTestPlugin.m),原生单测为 example/ios/RunnerTests/ 下的.m测试;
  • 其集成测试入口同样是 example/integration_test/test.dart。

由于两侧 example 的 Dart 入口、生成物与测试断言全部来自共享包,新增或修改 Pigeon 的 Dart 端测试逻辑时只需改一处,两侧插件自动同步——这正是 README 所说“almost all Dart code should be in this package”的落地方式。

如何运行这些测试

platform_tests 的 README 说明了完整的驱动方式:

  1. 推荐方式:使用test.dart一键运行。该脚本会先从 pigeons/ 目录中的 Pigeon 定义重新生成原生代码,写入各平台测试骨架(如上文列出的CoreTests.gen.kt/CoreTests.gen.m/core_tests.gen.cc等),然后驱动对应平台的原生测试与集成测试;
  2. IDE 内直接运行:如果是在平台 IDE 中直接执行,可先用generate.dart生成必要的 Pigeon 输出,再手动运行原生/集成测试。

此外,README 还保留了flutter_null_safe_unit_tests一节:这是 NNBD 成为 Pigeon 唯一支持模式之前的遗留 Dart 单测结构,其计划是“folded back into the main tests”(合并回主测试),阅读仓库时遇到该历史命名不必当作新机制。

小结

  • shared_test_plugin_code解决的是结构性重复问题:两个原生测试插件因 Java/Kotlin、Obj-C/Swift 无法合并而被迫拆分,但 Dart 侧的生成物、示例 App 与集成测试必须保持一致,因此集中收口到这一个publish_to: none的内部包;
  • 它的内容由四块构成:lib/src/generated/下 15 个 Pigeon Dart 生成物、integration_tests.dart等按TargetGenerator参数化的集成测试、极简验证用的ExampleApp、以及test/下的 Dart 单元测试与 mockito 产物;
  • 配置上有两个易被忽视的点:flutter_test/integration_test必须作为普通依赖才能传递给消费方,dart_test.yamltest_on: vm限定了单元测试运行环境;
  • 运行入口统一收敛到 tool/test.dart(重新生成 + 驱动)或 tool/generate.dart(仅生成);
  • 维护约定:今后新增 Dart 端测试代码时,优先放入共享包而非两个插件各自的lib/,以保持“两侧插件 intended to be identical”的设计前提不被破坏。

【免费下载链接】packagesA collection of useful packages maintained by the Flutter team项目地址: https://gitcode.com/GitHub_Trending/pac/packages

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/18 10:44:20

抽烟检测数据集与YOLO11训练:从标注格式到模型调参

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/18 10:44:17

Flutter智能验证码在OpenHarmony的适配实践

1. 项目背景与核心价值在移动应用开发领域&#xff0c;用户认证流程的便捷性直接影响着产品的用户体验和转化率。Flutter 作为跨平台开发框架&#xff0c;其生态中的 smart_auth 库通过智能验证码自动填充功能&#xff0c;显著提升了移动端认证流程的效率。然而&#xff0c;随着…

作者头像 李华
网站建设 2026/9/18 10:43:58

GPU、FPGA、NPU加速器选型指南:从架构原理到实战避坑

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/18 10:43:49

从CRDT到Canvas:MiroFish多人实时协作画布实践

做多人实时协作画布这件事&#xff0c;我从两年前就开始琢磨了。市面上的协作白板工具确实好用&#xff0c;但当团队需求变得"奇怪"一点——比如要把画布和我们自己的任务系统打通、要私有化部署、要接入内部的权限体系——现成产品就开始处处别扭。MiroFish 就是在这…

作者头像 李华
网站建设 2026/9/18 10:42:48

Redis高级实战:持久化、主从哨兵、分片集群、缓存治理与分布式锁

先把话说在前面&#xff1a;如果你只是会在 Spring Boot 里写一个 RedisTemplate&#xff0c;set 一个字符串再 get 出来&#xff0c;那 Redis 对你来说还是单机玩具。真正让我意识到必须系统学一遍 Redis 高级内容&#xff0c;是第一次把服务部署到多台机器之后——session 不…

作者头像 李华
网站建设 2026/9/18 10:40:55

飞书文档进 WeKnora,TaoToken 给 Agent 问答发 Key

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华