Skip to content

单元测试 ​

本文引用的源码文件

本文引用的文件

更新摘要 ​

变更内容

  • 新增子任务超时预算测试模块,涵盖预算计算、时间格式化、进度回灌等核心功能
  • 强化正则断言策略,从简单的存在性检查升级为精确的接线守卫
  • 改进夹具语义,确保测试数据与生产代码字段完全对齐
  • 增强边界条件测试,特别是子任务超时预算的并发控制和资源分配

目录 ​

  1. 引言
  2. 项目结构
  3. 核心组件
  4. 架构总览
  5. 详细组件分析
  6. 依赖关系分析
  7. 性能与覆盖率
  8. 故障排查指南
  9. 结论

引言 ​

本文件面向 AI Helper Chrome 扩展(Manifest V3,版本 2.3.1)的单元测试体系,聚焦 Vitest 测试框架的配置、Chrome API Mock 策略、异步与时间控制处理、测试用例组织方式、Mock 设计模式以及持续集成中的质量门禁。文档同时给出可复用的测试编写范式,覆盖工具函数、UI 行为、状态持久化、Service Worker 保活以及子任务超时预算管理等关键场景。

项目结构 ​

测试代码采用"按功能模块划分 + 统一命名规范"的组织方式:

  • 根级配置:Vitest 默认环境为 Node,通过 setupFiles 注入全局 Mock;JS-DOM 相关用例通过文件头注释切换为 jsdom 环境。
  • 测试入口:npm scripts 提供 test:unit、test:e2e 等命令,统一由 package.json 管理。
  • 测试目录:
    • test/unit:Vitest 单元测试,匹配规则为 test/unit/**/*.test.js。
    • test/e2e:Playwright 端到端测试(不在本文重点)。
    • test/setup:共享测试基础设施,包括 Chrome API 最小桩和 jsdom 缺失 API 补丁。
graph TB
A["package.json<br/>脚本入口"] --> B["vitest.config.js<br/>测试配置"]
B --> C["test/setup/chrome-mock.js<br/>Chrome API 最小桩"]
B --> D["test/setup/jsdom-globals.js<br/>jsdom 缺失 API 补丁"]
B --> E["test/unit/**.test.js<br/>单元测试集合"]
E --> F["src/background/*<br/>后台逻辑"]
E --> G["src/side_panel/*<br/>侧边栏 UI"]
E --> H["src/shared/*<br/>共享逻辑"]

图示来源

章节来源

核心组件 ​

本节梳理测试体系的关键基础设施与常用模式。

  • Vitest 配置

    • 全局 API:启用 globals,便于在测试中直接使用 describe、it、expect 等。
    • 默认环境:node,适合纯后端逻辑或无 DOM 的工具函数。
    • 初始化文件:chrome-mock.js 与 jsdom-globals.js 作为 setupFiles 注入。
    • 包含规则:仅运行 test/unit/**/*.test.js。
  • Chrome API 最小桩

    • 目标:避免被测模块意外访问 chrome.* 时抛出 ReferenceError。
    • 策略:为 storage、runtime、tabs、scripting、bookmarks、history、cookies、downloads、notifications、offscreen 等提供 no-op 实现。
    • 适用:纯函数测试、不直接依赖浏览器行为的逻辑。
  • jsdom 缺失 API 补丁

    • 目标:让基于 DOM 的逻辑在 jsdom 下可运行。
    • 内容:补齐 Element.scrollIntoView / scrollTo、NodeList.prototype.some/map/filter 等。
    • 触发:仅在 window 与 Element 存在时生效,避免污染 node 环境。
  • 测试数据与隔离

    • 内存存储夹具:通过 makeFakeStorage 模拟 chrome.storage.local 的序列化语义,支持注入失败、记录 get/set 调用次数。
    • 模块级重置:vi.resetModules 配合 freshStore 注入新的 chrome 实例,确保每个用例独立。
    • 时间控制:vi.useFakeTimers/vi.advanceTimersByTimeAsync 精确控制防抖、心跳、rAF 等时序。

章节来源

架构总览 ​

下图展示单元测试如何围绕被测源码进行 Mock、断言与回归保护。

graph TB
subgraph "测试层"
T1["Vitest 配置<br/>vitest.config.js"]
T2["Chrome 最小桩<br/>chrome-mock.js"]
T3["jsdom 补丁<br/>jsdom-globals.js"]
T4["用例集合<br/>test/unit/*.test.js"]
end
subgraph "被测源码"
S1["background 逻辑<br/>src/background/*"]
S2["side_panel 逻辑<br/>src/side_panel/*"]
S3["shared 逻辑<br/>src/shared/*"]
end
T1 --> T4
T2 --> T4
T3 --> T4
T4 --> S1
T4 --> S2
T4 --> S3

图示来源

详细组件分析 ​

活动存储写合并与竞态守卫测试 ​

该用例验证 activity-store 的审计日志与工具统计写入路径:

  • 形状不变量:条目字段与旧版一致,最新在前。
  • 上限约束:最多保留 100 条。
  • 冷启动合并:不清空存量,新条目插到最前。
  • 防抖合并:窗口内多次变更只落盘一次,且多键同批写入。
  • 关键审计即时落盘:敏感操作不等防抖。
  • 失败重试:set 失败保留脏标,下次合并带出。
  • 并发竞态对照:旧读-改-写会丢更新,新实现一条不丢。
  • 接线守卫:tool-executor 改用 activity-store,热路径零逐次 IPC。
flowchart TD
Start(["开始:appendAuditLog/recordToolStats"]) --> CheckType{"是否关键审计?"}
CheckType --> |是| ImmediateFlush["立即落盘"]
CheckType --> |否| Debounce["进入防抖窗口"]
ImmediateFlush --> Persist["chrome.storage.local.set"]
Debounce --> Merge["合并待写数据"]
Merge --> FlushTimer["定时器到期后 flush"]
FlushTimer --> Persist
Persist --> ErrorCheck{"set 是否失败?"}
ErrorCheck --> |是| MarkDirty["标记脏数据并保留"]
ErrorCheck --> |否| Done(["结束"])
MarkDirty --> NextFlush["下一次 flush 携带脏数据"]
NextFlush --> Done

图示来源

章节来源

流式滚动到底部补滚测试 ​

该用例验证 chat-streaming 的智能滚动:

  • 首帧 scrollHeight 偏小,下一帧才稳定增大。
  • autoScrollToBottom 必须补滚一帧,否则最新内容被遮挡。
  • scrollStreamingToBottom 对已连接元素立即补滚,对未连接元素不滚动。
  • 使用手动 rAF 队列完全接管渲染时序,避免 jsdom 差异。
sequenceDiagram
participant Test as "测试用例"
participant DOM as "DOM 容器"
participant Scroll as "scrollStreamingToBottom"
participant RAF as "requestAnimationFrame"
Test->>DOM : 构建容器并设置初始 scrollHeight
Test->>Scroll : 调用滚动到底部
Scroll->>RAF : 入队补滚回调
Note over DOM,RAF : 下一帧内容高度稳定
RAF-->>Scroll : 执行补滚回调
Scroll->>DOM : 读取最终 scrollHeight 并滚动到底
Test->>Test : 断言 scrollTop 等于最终底部

图示来源

章节来源

Service Worker 心跳保活测试 ​

该用例验证 MV3 Service Worker 任务级保活心跳:

  • 心跳间隔小于 30 秒闲置阈值,并留出余量。
  • start 立即跳一拍,之后每间隔跳一拍;stop 停止。
  • 引用计数:嵌套 start/stop 成对出现,只有最外层 stop 真正停跳。
  • 多余 stop 不出负数、不崩溃;API 抛异常静默容错。
  • react-loop 导入共享心跳模块,并以包装器形式保证 finally 停止。
stateDiagram-v2
[*] --> 空闲
空闲 --> 跳动中 : "startSwHeartbeat()"
跳动中 --> 跳动中 : "startSwHeartbeat() 嵌套"
跳动中 --> 空闲 : "stopSwHeartbeat() 最外层"
跳动中 --> 空闲 : "定时器到期 → 调用轻量 API"
空闲 --> 空闲 : "多余 stop 不崩溃"

图示来源

章节来源

Manifest 权限与帧注入守卫测试 ​

该用例校验 manifest.json 的阶段三帧支持与权限:

  • permissions 包含 webNavigation。
  • content_scripts 开启 all_frames、match_about_blank、match_origin_as_fallback,并匹配 <all_urls>。

章节来源

配置导入合并与安全性测试 ​

该用例覆盖 config-io 的导入流程:

  • merge 模式:新 ID 追加、同 ID 较新覆盖、较旧忽略,并重投影扁平键。
  • replace 模式:整体替换厂商配置。
  • 剥离下线字段:移除 toolReflection 等旧配置。
  • 安全约定:networkAccessEnabled 与 allowedNetworkHosts 不参与导入,防止配置文件越权打开内网访问。

章节来源

主题系统测试 ​

该用例覆盖 theme.js 的行为:

  • 默认 light,写入 data-theme。
  • setTheme(dark) 落盘 appTheme 并翻转属性。
  • system 偏好按 prefers-color-scheme 解析并订阅变化。
  • storage.onChanged 跨页同步翻转。
  • 顶层立即应用 localStorage 镜像(防闪烁)。
  • 非法偏好值回退 light,setTheme 拒绝非法值。
  • apply 仅在生效值变化时通知订阅者(吞同值回声)。
  • storage.get 报 lastError 时保留镜像主题,不回落 light。

章节来源

子任务超时预算与进度管理测试 ​

新增 该测试模块验证子任务超时预算管理的核心功能:

  • 预算计算逻辑:取消硬封顶,改为配置驱动,同时不透支父任务预算
  • 并发控制:N 个并发子任务不得各享满额父剩余,需按比例分摊
  • 边界条件:父剩余为零/为负时预算归零,pendingCount 极大时启用 60s 下限
  • 时间格式化:整分钟不带小数,非整分钟保留一位小数,不足一分钟按秒显示
  • 进度回灌:已完成节点摘要回灌给重跑的子任务,避免重复工作
  • 接线守卫:精确的正则匹配确保关键函数调用点不被删除
flowchart TD
A["computeSubtaskLoopTimeoutMs"] --> B["计算配置上限 cap"]
B --> C{"是否有父预算?"}
C --> |否| D["返回配置值本身"]
C --> |是| E["计算预留 reserve = 5% 父剩余"]
E --> F["分摊 perChild = (hardCap - reserve) / pendingCount"]
F --> G["应用下限 MIN_BUDGET"]
G --> H["取 min(cap, budget)"]
H --> I["返回最终预算"]

图示来源

章节来源

子任务进度接线与字段契约测试 ​

新增 该测试模块验证子任务进度管理的接线正确性和字段契约:

  • 字段契约:生产代码将产出正文写在 observation 字段而非 result 字段
  • 进度写入:createSubtaskPartialProgressWriter 负责滚动写入进度表
  • 现场合并:跨尝试的进度有界合并,避免重复堆积
  • 安全包裹:buildSubtaskResumeContext 对不可信内容进行 HTML 实体转义
  • 接线守卫:精确计数确保关键函数调用点存在且参数正确
sequenceDiagram
participant Writer as "进度写入器"
participant Progress as "进度 Map"
participant Context as "继承上下文"
participant Prompt as "系统提示词"
Note over Writer : 第一趟执行
Writer->>Progress : 写入工具执行结果
Note over Progress : 保存 partialSummary
Note over Writer : 第二趟重试
Writer->>Progress : 追加新的执行结果
Progress->>Context : 生成继承上下文
Context->>Prompt : 注入「不要重做」段落

图示来源

章节来源

子任务部分完成语义测试 ​

新增 该测试模块验证子任务部分完成的分类逻辑和失败上报:

  • 分类逻辑:failed 且节点成功率 ≥80% 时显示「部分完成」而非「失败」
  • 失败上报:失败 Result 附加已完成节点的产出摘要,避免父模型盲续
  • 字段对齐:夹具严格对齐生产代码的 tool_exec 节点结构
  • 样式守卫:CSS 定义 partial 橙色样式区别于红色 failed

章节来源

依赖关系分析 ​

测试与源码之间的依赖关系如下:

  • vitest.config.js 定义测试环境与初始化文件,决定哪些测试会被执行。
  • chrome-mock.js 与 jsdom-globals.js 为所有测试提供运行时基础能力。
  • 各 unit.test.js 通过 import 引入被测模块,并通过 vi.mock、vi.stubGlobal、readFileSync 等方式进行依赖隔离与静态守卫。
graph LR
V["vitest.config.js"] --> U["test/unit/*.test.js"]
M["chrome-mock.js"] --> U
J["jsdom-globals.js"] --> U
U --> B["src/background/*"]
U --> S["src/side_panel/*"]
U --> H["src/shared/*"]

图示来源

章节来源

性能与覆盖率 ​

  • 性能测试方法
    • 使用 vi.useFakeTimers 与 vi.advanceTimersByTimeAsync 精确控制防抖、节流与心跳,避免真实时间不确定性。
    • 手动接管 requestAnimationFrame,构造可控的渲染帧,验证布局延后稳定后的滚动行为。
    • 并发竞态对照:用 Promise.all 并行触发旧实现与新实现,断言旧实现丢更新、新实现不丢。
  • 覆盖率报告生成
    • 当前仓库未显式配置 Vitest 覆盖率输出;如需生成覆盖率,可在 vitest.config.js 中添加 coverage 配置并在 npm scripts 中增加对应命令。
  • 持续集成与质量门禁
    • release.yml:负责构建扩展包、上传 GitHub Releases 与同步 Gitee,不包含单元测试步骤。
    • test-install.yml:针对安装脚本的全生命周期验证,也不包含单元测试。
    • 建议在 CI 中新增 job 执行 npm run test:unit,并将失败视为质量门禁阻断条件。

章节来源

故障排查指南 ​

  • 常见问题
    • 测试环境缺少 chrome.*:检查 setupFiles 是否正确加载 chrome-mock.js,或在用例中按需 vi.stubGlobal('chrome', ...)。
    • jsdom 环境下 DOM API 缺失:确认 jsdom-globals.js 已加载,必要时在用例中补充 polyfill。
    • 异步时序不稳定:优先使用 vi.useFakeTimers 与手动 rAF 队列,避免依赖真实浏览器时序。
    • 模块单例污染:使用 vi.resetModules 与 freshStore 注入新的 chrome 实例,确保用例隔离。
    • 心跳与保活:确保 start/stop 成对出现,引用计数正确;API 异常需静默容错。
    • 子任务超时预算:检查 computeSubtaskLoopTimeoutMs 的参数传递,确保父剩余、配置值和 pendingCount 正确传入。
    • 进度回灌失效:确认 fixture 字段与生产代码对齐(observation vs result),检查接线守卫是否通过。
  • 调试建议
    • 打印 fake storage 的 getCalls/setCalls,核对读写次数与合并批次。
    • 对关键审计类操作,断言即时落盘;对普通操作,断言防抖窗口内不落盘。
    • 对 UI 滚动行为,先断言首帧滚动结果,再断言下一帧补滚后的最终位置。
    • 对子任务超时预算,验证预算计算公式:min(配置, max(MIN_BUDGET, (父剩余 - 预留) / 并发数), 父剩余)。
    • 对进度回灌,检查 partialSummary 是否包含预期的工具执行结果,确认不应重做的语义正确。

章节来源

结论 ​

本项目单元测试体系以 Vitest 为核心,结合 Chrome API 最小桩与 jsdom 补丁,覆盖了后台逻辑、侧边栏 UI 与共享模块的关键行为。通过时间控制、并发竞态对照与静态接线守卫,测试不仅验证功能正确性,还承担回归保护职责。

最新更新 引入了子任务超时预算管理和进度回灌的完整测试套件,包括:

  • 精确的正则断言策略,从简单的存在性检查升级为接线守卫
  • 与生产代码字段完全对齐的夹具语义
  • 全面的边界条件测试,特别是并发控制和资源分配
  • 闭环测试确保进度回灌链路不断链

建议在后续迭代中补充覆盖率配置与 CI 质量门禁,进一步提升测试可见性与发布可靠性。