shared_preferences_web 平台实现测试应用解析:基于 integration_test 的浏览器端集成测试实战指南

shared_preferences_web 平台实现测试应用解析:基于 integration_test 的浏览器端集成测试实战指南 shared_preferences_web 平台实现测试应用解析基于 integration_test 的浏览器端集成测试实战指南【免费下载链接】packagesA collection of useful packages maintained by the Flutter team项目地址: https://gitcode.com/GitHub_Trending/pac/packages本文以packages/shared_preferences/shared_preferences_web/example/README.md为骨架深入讲解 Flutter 官方 federated plugin 体系中 Web 平台实现shared_preferences_web的测试应用它不是一个面向插件使用者的演示 Demo而是专为平台实现本身提供手动与自动化集成测试的测试台。读完本文你将理解该测试应用的文件结构、pubspec 依赖关系、基于package:integration_test的浏览器测试写法以及测试背后 localStorage 存储、flutter.前缀过滤与 JSON 编解码的底层原理。这个 Example 到底是什么平台实现测试应用而非使用示例关联文档开篇就明确界定了定位This is a test app for manual testing and automated integration testing of this platform implementation.这是一个用于对该平台实现进行手动测试和自动化集成测试的测试应用。它的核心设计意图是测试对象是平台实现包shared_preferences_web而不是插件用户直接面对的 app-facing 包shared_preferences它不用于演示该包的实际使用方式因为插件客户端应当使用面向应用的shared_preferences包该包属于 endorsed federated plugin通过 共享偏好主包 自动引入除非你正在修改shared_preferences_web实现本身否则这个 example 通常与你无关——这是对插件贡献者的提示而非普通使用者的入门教程。理解这一定位是读懂后续所有测试代码的前提测试直接针对SharedPreferencesStorePlatform与SharedPreferencesAsyncPlatform这两个平台接口的 Web 实现而非业务层 API。Example 应用的文件结构与依赖配置该测试应用位于 example其目录结构为example/ ├── README.md # 本文依托的说明文档 ├── pubspec.yaml # 测试应用依赖清单 ├── integration_test/ │ └── shared_preferences_web_test.dart # 浏览器集成测试主体 ├── lib/ │ └── main.dart # 最小测试占位 App ├── test_driver/ │ └── integration_test.dart # flutter drive 驱动入口 └── web/ └── index.html # Web 宿主页面查看 example/pubspec.yaml 可以看到依赖关系name: shared_preferences_web_integration_tests publish_to: none environment: sdk: ^3.10.0 flutter: 3.38.0 dependencies: flutter: sdk: flutter shared_preferences_platform_interface: ^2.4.0 shared_preferences_web: path: ../ web: ^1.0.0 dev_dependencies: flutter_test: sdk: flutter integration_test: sdk: flutter关键点解读shared_preferences_web: path: ../以本地路径依赖方式直接引用上层平台实现包保证测试的就是当前工作区代码integration_test来自 Flutter SDK是浏览器集成测试的官方基础设施shared_preferences_platform_interface提供SharedPreferencesStorePlatform、SharedPreferencesAsyncPlatform等接口类型测试代码通过这些接口而非具体实现类进行断言publish_to: none表明该应用仅供仓库内测试使用不会发布。测试应用的宿主入口 example/web/index.html 是最简 HTML5 页面通过flutter_bootstrap.js引导 Flutter Web 应用启动!DOCTYPE html html head meta charsetUTF-8 titleexample/title /head body script srcflutter_bootstrap.js async/script /body /html占位 App控制台输出测试结果的极简界面example/lib/main.dart 构建了一个只有文本提示的占位界面class _MyAppState extends StateMyApp { override Widget build(BuildContext context) { return const Directionality( textDirection: TextDirection.ltr, child: Text(Testing... Look at the console output for results!), ); } }注意这里的取舍页面只显示Testing... Look at the console output for results!没有业务 UI。因为集成测试的判定结果通过integration_test框架回传到驱动端控制台界面本身无需展示任何交互组件。这一设计印证了关联文档中用于测试而非演示使用的定位。集成测试主体覆盖存储接口全生命周期的测试用例核心测试文件是 integration_test/shared_preferences_web_test.dart它用IntegrationTestWidgetsFlutterBinding.ensureInitialized()初始化绑定随后分两大组进行测试。测试前置每次清空 localStorage每个用例之前都执行setUp(() { html.window.localStorage.clear(); });由于 Web 实现的数据落点是浏览器localStorage测试前清空存储可以保证用例之间互不污染这与SharedPreferencesPlugin.clear()只清理flutter.前缀键的行为形成对照详见下文底层实现一节。注册测试验证插件自注册机制testWidgets(registers itself, (WidgetTester tester) async { SharedPreferencesStorePlatform.instance MethodChannelSharedPreferencesStore(); expect(SharedPreferencesStorePlatform.instance, isNot(isASharedPreferencesPlugin())); SharedPreferencesPlugin.registerWith(null); expect(SharedPreferencesStorePlatform.instance, isASharedPreferencesPlugin()); });该用例验证 Web 插件在registerWith后能够替换平台接口的默认实现先手动注入一个非 Web 实现再调用registerWith(null)断言SharedPreferencesStorePlatform.instance变为SharedPreferencesPlugin。这与 lib/shared_preferences_web.dart 中registerWith的实现一一对应static void registerWith(Registrar? registrar) { SharedPreferencesStorePlatform.instance SharedPreferencesPlugin(); SharedPreferencesAsyncWeb.registerWith(registrar); }同步接口SharedPreferencesStorePlatform测试测试数据覆盖五种受支持类型并刻意使用三种命名空间来验证前缀行为const flutterTestValues String, Object{ flutter.String: hello world, flutter.Bool: true, flutter.Int: 42, flutter.Double: 3.14159, flutter.StringList: String[foo, bar], }; // prefixTestValues 使用 prefix. 前缀nonPrefixTestValues 使用无前缀键名在此基础上组织的用例包括reading刚启动时getAll()返回空 Mapkeys写入 15 个键后html.window.localStorage.keys与实际写入键完全一致hasLength与containsAll双重断言clear调用clear()后flutter.*键全部消失withPrefix 组验证remove、getAllWithPrefix(prefix.)、getAllWithPrefix()、clearWithPrefix(prefix.)与clearWithPrefix()的前缀语义——例如clearWithPrefix(prefix.)只清除prefix.*flutter.*数据不受影响withParameters 组验证新式参数化 API其中值得关注的是allowList允许列表用例testWidgets(get all with allow list, (WidgetTester _) async { final MapString, Object values await preferences.getAllWithParameters( GetAllParameters( filter: PreferencesFilter(prefix: prefix., allowList: String{prefix.String}), ), ); expect(values[prefix.String], allTestValues[prefix.String]); expect(values[prefix.Bool], null); // 不在 allowList 中的键被过滤 ... });simultaneous writes并发写入一次性发起 100 个setValue(Int, Int, i)并发写入断言全部成功且最后一次写入生效最终值为writeCount。这验证了基于localStorage.setItem的 Web 实现天然具备写后覆盖的并发语义returns all valid JSON data非法 JSON 容错直接向localStorage写入非法 JSONvalue未加引号与合法 JSONvalue断言非法条目在getAllWithParameters中被丢弃、合法条目被正确解码。异步接口SharedPreferencesAsyncPlatform测试第二组围绕SharedPreferencesAsyncPlatform即SharedPreferencesAsyncWeb测试覆盖setString/setBool/setInt/setDouble/setStringList的写入回读、getStringList返回可变副本list?.add(value)后长度 1证明实现中.toList()拷贝语义、getPreferences/getKeys及其 allowList 过滤、clear及其过滤行为。其中clear with filter用例揭示了 allowList 的另一种语义PreferencesFilters(allowList: {stringKey, boolKey})意味着仅清除这两个键其余键保留——这与同步接口中getAllWithParameters的仅返回 allowList 内键是同一过滤器在不同操作上的两种应用。底层实现印证localStorage、前缀与 JSON 编解码测试所断言的每个行为都能在实现中找到对应代码lib/shared_preferences_web.dart 的核心事实如下1. 存储介质即浏览器 localStorage。setValue最终调用html.window.localStorage.setItem(key, _encodeValue(value))remove调用removeItem读取时遍历_getPrefixedKeys后逐个getItem。任何在浏览器 DevTools 的 Application → Local Storage 面板中可见的键就是这个插件的全部数据。2. 默认前缀flutter.。同步接口SharedPreferencesStorePlatform以static const String _defaultPrefix flutter.作为默认前缀getAll()与clear()都只作用于flutter.开头的键而异步接口SharedPreferencesAsyncWeb则不设前缀、读取全部键。实现中还特别注释强调// IMPORTANT: Do not use html.window.localStorage.clear() as that will // remove _all_ local data, not just the keys prefixed with // _prefix也就是说clear()采用枚举匹配前缀的键并逐个remove的方式避免误删同一站点其他业务写入的 localStorage 数据——这正是测试中keys / clear / withPrefix等用例要守护的行为契约。3. 值一律 JSON 编码。_encodeValue为json.encode(value)_decodeValue在FormatException时返回null对应非法 JSON 被丢弃的测试并对 List 类型做显式castString()原因是 JSON 往返会丢失泛型ListString→ JSON →Listdynamic必须恢复 RTTI。4. 键枚举依赖扩展方法。lib/src/keys_extension.dart 为html.Storage提供keysgetterListString get keys { return String[for (int i 0; i length; i) key(i)!]; }浏览器原生Storage只有key(index)与length该扩展将其封装为便于过滤、迭代的ListString测试代码与实现代码_getAllowedKeys均依赖它。5. 插件声明。查看 shared_preferences_web/pubspec.yaml 可知该包通过flutter.plugin.implements: shared_preferences声明为被背书endorsed的实现Web 平台入口为SharedPreferencesPlugin。因此普通应用只需依赖shared_preferences主包构建 Web 时便会自动带上此实现无需手动添加依赖——这与主包 README 及平台实现 README 的说明一致。如何运行这套浏览器集成测试关联文档说明该包使用package:integration_test在 Web 浏览器中运行测试。结合本仓库文件结构可以推断出两种标准执行方式直接运行集成测试推荐日常开发flutter test integration_test/shared_preferences_web_test.dart -d chrome需要在example目录下执行仓库为只读执行前请先cd到该目录并确保已flutter pub get。通过 test_driver 驱动适用于 CI 或需要收集结果的场景仓库提供了 example/test_driver/integration_test.dartimport package:integration_test/integration_test_driver.dart; Futurevoid main() integrationDriver();对应命令形式为flutter drive --drivertest_driver/integration_test.dart \ --targetintegration_test/shared_preferences_web_test.dart -d chrome运行后浏览器会自动打开测试页面测试结果与断言失败信息输出到命令行控制台与占位 App 界面上的Look at the console output for results!提示呼应。若需在无头 CI 环境运行可结合chromedriver与flutter drive -d web-server等 Flutter 官方集成测试流程配置详见 Flutter 官方 Integration testing 文档中的 Web 章节。总结shared_preferences_web的 example 是一个典型的平台实现测试应用它用最小占位 UI 承载基于integration_test的浏览器集成测试围绕SharedPreferencesStorePlatform与SharedPreferencesAsyncPlatform两个接口系统性地验证了 Web 实现中 localStorage 读写、flutter.默认前缀、allowList 过滤、非法 JSON 容错与并发写入等关键行为。对于希望为 Flutter 官方插件贡献代码、或想在自己项目中复刻平台实现测试套件的开发者而言这套声明式接口断言 浏览器真实存储验证 驱动端结果回传的组合是值得直接参照的范本。【免费下载链接】packagesA collection of useful packages maintained by the Flutter team项目地址: https://gitcode.com/GitHub_Trending/pac/packages创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考