# 测试策略

OpenHuman 如何测试其产品。“我的测试该去哪里？”的权威来源。配套文档： [`TEST-COVERAGE-MATRIX.md`](https://github.com/tinyhumansai/openhuman/blob/main/docs/TEST-COVERAGE-MATRIX.md).

***

## 层级

| 层               | 所在位置                                                                                                               | 测试内容                                                                      | 驱动程序                                                                                                             |
| --------------- | ------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- |
| **Rust 单元测试**   | `#[cfg(test)] mod tests` 位于同一个 `*.rs` 文件中，或位于同级的 `tests.rs`，或 `tests/` 域下的子目录中（例如 `src/openhuman/channels/tests/`) | 纯域逻辑、schema、RPC 处理器形状、内存状态机                                               | `cargo test`                                                                                                     |
| **Rust 集成测试**   | `tests/*.rs` 位于仓库根目录                                                                                               | 使用真实 Tokio 运行时、模拟外部服务、JSON-RPC 端到端的完整域连接（`tests/json_rpc_e2e.rs`），域 × 域交互 | `pnpm test:rust` （会调用 `bash scripts/test-rust-with-mock.sh`)                                                     |
| **Vitest 单元测试** | 与源码同目录放置为 `*.test.ts(x)` ，位于 `app/src/**`下，或位于 `app/src/**/__tests__/`                                             | React 组件、hooks、store 分片、纯工具函数、服务层适配器                                      | `pnpm test:unit`                                                                                                 |
| **WDIO 端到端测试**  | `app/test/e2e/specs/*.spec.ts`                                                                                     | 完整桌面流程：UI → Tauri → core sidecar → JSON-RPC；用户可见行为                        | Linux CI： `tauri-driver` （端口 4444）。macOS 本地：Appium Mac2（端口 4723）。见 [端到端测试](/openhuman/zh/kai-fa/e2e-testing.md). |
| **手动冒烟测试**      | [`docs/RELEASE-MANUAL-SMOKE.md`](https://github.com/tinyhumansai/openhuman/blob/main/docs/RELEASE-MANUAL-SMOKE.md) | 驱动程序无法断言的操作系统级表面：TCC 权限弹窗、Gatekeeper、代码签名、DMG 安装、系统原生通知                   | 在发布切版时由人工确认，并在发布 PR 中签署                                                                                          |

***

## 决策树 - 我的测试该去哪里？

```
变更是否位于 JSON-RPC 边界之后（在 `src/` 中）？
├─ 是 - 它是否跨域或与外部服务通信？
│   ├─ 是 → Rust 集成测试（tests/*.rs）
│   └─ 否  → Rust 单元测试（放在源码旁）
└─ 否 - 变更位于 `app/` 中
    ├─ 它是否是独立的纯函数、hook、slice 或组件？
    │   └─ 是 → Vitest 单元测试（*.test.tsx 同目录放置）
    └─ 它是否用户可见，并且跨越 UI ⇄ Tauri ⇄ sidecar ⇄ JSON-RPC？
        ├─ 是 → WDIO 端到端测试（app/test/e2e/specs/*.spec.ts）
        └─ 它是否是操作系统级（TCC、Gatekeeper、安装、系统通知）？
            └─ 是 → 手动冒烟检查清单
```

如果一项变更触及其中多个层级，请在 **每个** 受影响的层中都写测试。不要用一个测试替代另一个。

***

## 失败路径要求

覆盖矩阵中的每个功能叶子都必须有 **至少一个失败 / 边界** 断言，不能只有“快乐路径”。例如：

* 文件写入工具：快乐 = 已写入字节；失败 = 路径限制拒绝。
* OAuth 流程：快乐 = 已签发令牌；边界 = 刷新令牌过期后的恢复。
* 内存存储：快乐 = 已存储 + 已取回；边界 = 先忘记再取回时返回空。

如果某个 spec 只断言快乐路径，那它是不完整的。

***

## 模拟策略

* **单元 / 集成 / 端到端测试中不得使用真实网络。** 使用共享的模拟后端（`scripts/mock-api-core.mjs`, `scripts/mock-api-server.mjs`, `app/test/e2e/mock-server.ts`).
* 测试专用管理端点： `GET /__admin/health`, `POST /__admin/reset`, `POST /__admin/behavior`, `GET /__admin/requests`.
* **外部服务** （Telegram、Slack、Gmail、Notion、Ollama、OpenAI 等）在模拟后端层被 stub 掉；测试通过 `getRequestLog()`.
* 断言请求形状。唯一可接受的例外是已文档化的发布切版手动冒烟步骤。

***

## 确定性规则

* 不要使用墙钟等待，使用 `waitForApp`, `waitForAppReady`, `waitForWebView` 辅助函数，或显式的元素就绪谓词。
* 不要共享文件系统状态，每个 E2E spec 都在一个隔离的 `OPENHUMAN_WORKSPACE` 中运行（由 `app/scripts/e2e-run-spec.sh`).
* 创建 / 清理），不要依赖执行顺序，每个 spec 都必须能单独运行通过。
* 不要依赖绝对坐标或动画时序。
* 不要通过真实键盘使用 `browser.keys()` 在 tauri-driver 上输入；应通过 `browser.execute(...)` 来合成（见 `command-palette.spec.ts` 中的模式）。

***

## 现有测试框架能提供什么

* **模拟后端启动**: `startMockServer` / `stopMockServer` 在 `app/test/e2e/mock-server.ts`.
* **认证快捷方式**: `triggerAuthDeepLink` / `triggerAuthDeepLinkBypass` 在 `helpers/deep-link-helpers.ts` 会跳过真实 OAuth。
* **元素辅助函数**: `clickNativeButton`, `waitForWebView`, `clickToggle` 在 `helpers/element-helpers.ts`，请使用这些而不是原始的 `XCUIElementType*` 选择器。
* **共享流程**: `completeOnboardingIfVisible`, `navigateViaHash`, `navigateToSkills`, `walkOnboarding` 在 `helpers/shared-flows.ts`.
* **从 spec 调用核心 RPC**: `callOpenhumanRpc` 在 `helpers/core-rpc.ts`，在 UI 步骤过于脆弱时直接驱动 sidecar。
* **平台守卫**: `isTauriDriver`, `isMac2`, `supportsExecuteScript` 在 `helpers/platform.ts`.
* **失败时采集制品**: `captureFailureArtifacts` 由 `wdio.conf.ts`运行，截图 + DOM 转储会保存在 `app/test/e2e/artifacts/`.

***

## 命名与结构约定

* WDIO specs： `<feature-area>-flow.spec.ts` 用于端到端产品流程； `<feature>.spec.ts` 用于更窄的表面。
* Vitest 同目录放置：优先使用 `Component.tsx` + `Component.test.tsx` 作为同级文件；仅当需要对多个相关测试分组时使用 `__tests__/` 。
* Rust 集成测试：与表面匹配的 snake\_case 文件名， `<feature>_e2e.rs` 用于 JSON-RPC 驱动的流程， `<feature>_integration.rs` 用于跨域场景。
* 每个 `describe` / `mod tests` 代码块都对应一个功能列表 ID 范围，如果映射不明显，请在注释中链接矩阵行。

***

## 合并前门禁

在打开 PR 前运行。CI 会运行同一套命令，但本地运行更快：

```bash
# Rust 核心
cargo fmt --check
cargo check --manifest-path Cargo.toml
cargo clippy --manifest-path Cargo.toml -- -D warnings
cargo test --manifest-path Cargo.toml

# Tauri 外壳
cargo check --manifest-path app/src-tauri/Cargo.toml

# 前端
pnpm typecheck
pnpm lint
pnpm format:check
pnpm test:unit

# 使用模拟后端的 Rust 集成测试
pnpm test:rust

# E2E（较慢 - 当行为对用户可见地改变时运行）
pnpm test:e2e:build
bash app/scripts/e2e-run-spec.sh test/e2e/specs/<your-spec>.spec.ts <id>
```

***

## 无法由驱动程序自动化 - 需要手动冒烟测试

某些表面无法由 WDIO / Appium 驱动，因为它们跨越操作系统级信任边界或硬件路径。完整清单 + 签署块位于 [`docs/RELEASE-MANUAL-SMOKE.md`](https://github.com/tinyhumansai/openhuman/blob/main/docs/RELEASE-MANUAL-SMOKE.md)，该文件是每个发布版本必须验证内容的权威来源。它涵盖的示例包括：

* macOS TCC 权限弹窗（辅助功能、输入监控、屏幕录制、麦克风）
* 首次启动时的 Gatekeeper 签名验证
* 代码签名完整性（`codesign --verify --deep --strict`)
* DMG 安装 / 拖拽到“应用程序”流程
* 自动更新下载 + 重新启动
* Linux 上系统原生通知气泡（驱动程序在 Xvfb 之外看不到显示服务器）

如果某个功能没有自动化覆盖，而且也不在手动冒烟列表中，就把它视为未测试，开一个覆盖缺口。

***

## 以覆盖矩阵作为契约

覆盖矩阵中的每个功能叶子都映射到： [覆盖矩阵](https://github.com/tinyhumansai/openhuman/blob/main/docs/TEST-COVERAGE-MATRIX.md) ：

1. 一个或多个测试路径， **或**
2. 一个已说明理由的 `🚫` 以及一个手动冒烟条目。

当你新增 / 删除 / 重命名某个功能时， **在同一个 PR 中更新矩阵行**。等 #965 合并后，CI 会守护这一契约。

***

## 拿不准时

* 尽可能把测试放在更低的层级（Rust 单元测试 > Rust 集成测试 > Vitest > WDIO）。更低层级更快、更确定，运行成本也更低。
* WDIO 适用于真正跨越 UI ⇄ Tauri ⇄ sidecar ⇄ JSON-RPC 的行为。不要因为有 UI，就把本可用单元测试覆盖的关注点交给 WDIO。
* 快乐路径失败是回归。缺少失败路径测试是缺口。两者都是 bug。


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://tinyhumans.gitbook.io/openhuman/zh/kai-fa/testing-strategy.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
