> For the complete documentation index, see [llms.txt](https://tinyhumans.gitbook.io/openhuman/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://tinyhumans.gitbook.io/openhuman/zh/kai-fa-zhong/testing-strategy.md).

# 测试策略

OpenHuman 如何测试其产品——Vitest、cargo test、WDIO E2E。每类测试的去向。

OpenHuman 如何测试其产品。“我的测试该放哪里？” 的事实依据。配套于 [`TEST-COVERAGE-MATRIX.md`](https://github.com/tinyhumansai/openhuman/tree/main/docs/TEST-COVERAGE-MATRIX.md).

***

## 层级

| 层               | 存放位置                                                                                                                                                                                                                        | 测试内容                                                                          | 驱动层                                                                                                                             |
| --------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
| **Rust 单元测试**   | 同级文件 `<module>_tests.rs` 位于模块旁边的文件，位于 `crates/openhuman-core/src/<domain>/`，或者一个 `tests/` 某个域下的子目录（例如 `crates/openhuman-core/src/channels/tests/`）；内联 `#[cfg(test)] mod` 代码块以及名为 `tests.rs`/`test.rs` 失败 `pnpm rust:layout` | 纯领域逻辑、schema、RPC 处理器形状、内存状态机                                                  | `cargo test`                                                                                                                    |
| **Rust 集成测试**   | `tests/*.rs` 位于仓库根目录，每个都是显式的 `[[test]]` 目标项于 `crates/openhuman-cli/Cargo.toml` (`autotests = false`); `tests/raw_coverage/*.rs` 被以下文件模式匹配： `build.rs` 汇总到单个 `raw_coverage_all` 目标中                                          | 使用真实 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 → JSON-RPC；用户可见行为                                | 所有平台：Linux CI 驱动基于 Wry 的调试构建（在原生驱动落地之前，macOS/Windows 桌面端 E2E 已禁用，#5485）。参见 [E2E 测试](/openhuman/zh/kai-fa-zhong/e2e-testing.md). |
| **手工冒烟测试**      | [`docs/RELEASE-MANUAL-SMOKE.md`](https://github.com/tinyhumansai/openhuman/tree/main/docs/RELEASE-MANUAL-SMOKE.md)                                                                                                          | 驱动无法断言的操作系统层面界面：TCC 权限提示、Gatekeeper、代码签名、DMG 安装、OS 原生 toast 通知                | 由人在发布切分时执行，并在发布 PR 中签字确认                                                                                                        |

***

## 决策树——我的测试该放哪里？

```
这个变更是否位于 JSON-RPC 边界之后（在 `crates/openhuman-core/src/`、`crates/openhuman-rpc/` 或 `crates/openhuman-embed/` 中）？
├─ 是 - 它是否跨域或与外部服务通信？
│   ├─ 是 → Rust 集成测试（tests/*.rs）
│   └─ 否  → Rust 单元测试（源文件旁）
└─ 否 - 变更位于 `app/` 中
    ├─ 它是否是一个纯函数、hook、切片或独立组件？
    │   └─ 是 → Vitest 单元测试（*.test.tsx 同目录）
    └─ 它是否用户可见，并且跨越 UI ⇄ Tauri ⇄ 嵌入式 core ⇄ JSON-RPC？
        ├─ 是 → WDIO E2E（app/test/e2e/specs/*.spec.ts）
        └─ 它是否属于操作系统层面（TCC、Gatekeeper、安装、OS toast）？
            └─ 是 → 手工冒烟检查清单
```

如果一个变更涉及其中多个层级，请在 **每个** 它涉及到的每一层都要写。不要用一个替代另一个。

***

## 失败路径要求

覆盖矩阵中的每个功能叶子都必须有 **至少一个失败/边缘** 断言，且不能只有 happy path。示例：

* 文件写入工具：happy = 写入了字节；failure = 路径限制拒绝。
* OAuth 流程：happy = 颁发 token；edge = 过期的 refresh token 恢复。
* 内存存储：happy = 已存储 + 已取回；edge = 忘记后再取回返回空。

只断言 happy path 的 spec 是不完整的。

***

## Mock 策略

* **在单元 / 集成 / E2E 中不使用真实网络。** 使用共享 mock 后端（`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 等）在 mock 后端层被 stub 掉；测试通过 `getRequestLog()`.
* 唯一可接受的例外是有文档记录的发布切分手工冒烟步骤。

***

## 确定性规则

* 不要使用实际时钟等待，请使用 `waitForApp`, `waitForAppReady`, `waitForWebView` 辅助函数，或显式的元素就绪谓词。
* 不要共享文件系统状态，每个 E2E spec 都运行在隔离的 `OPENHUMAN_WORKSPACE` （由 `app/scripts/e2e-run-spec.sh`).
* 不要编写依赖顺序的 spec，每个 spec 单独运行时都必须通过。
* 不要依赖绝对坐标或动画时序。
* 优先通过 `browser.execute(...)` 而不是 `browser.keys()` （见 `command-palette.spec.ts` 了解该模式）。

***

## 现有 harness 提供了什么

* **Mock 后端引导**: `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 直接调用 core RPC**: `callOpenhumanRpc` 于 `helpers/core-rpc.ts`，当某个 UI 步骤会很脆弱时，可直接驱动嵌入式 core。
* **平台守卫**: `isTauriDriver`, `isMac2`, `supportsExecuteScript` 于 `helpers/platform.ts` （前两个是遗留兼容层——现在一切都运行在 Appium Chromium 驱动上）。
* **失败时捕获产物**: `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` 块映射到一个 feature-list ID 范围，如果映射不明显，请在注释中链接矩阵对应行。

***

## 合并前门禁

在打开 PR 之前运行。CI 运行同一组检查，但本地运行更快：

```bash
# Rust core
cargo fmt --check
cargo check --manifest-path Cargo.toml   # 覆盖 openhuman-core、openhuman-embed、openhuman-rpc、openhuman-tui
cargo clippy --manifest-path Cargo.toml -- -D warnings
cargo test --manifest-path Cargo.toml

# Tauri shell
cargo check --manifest-path crates/openhuman-app/Cargo.toml

# Frontend
pnpm typecheck
pnpm lint
pnpm format:check
pnpm test:unit

# Rust integration with mock backend
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/tree/main/docs/RELEASE-MANUAL-SMOKE.md)，该文件是每次发布必须验证内容的事实依据。它涵盖的示例包括：

* macOS TCC 权限提示（辅助功能、输入监控、麦克风）
* 首次启动时的 Gatekeeper 签名验证
* 代码签名完整性（`codesign --verify --deep --strict`)
* DMG 安装 / 拖到 Applications 流程
* 自动更新下载 + 重新启动
* Linux 上的操作系统原生通知 toast（除 Xvfb 外，驱动看不到显示服务器）

如果某个功能既没有自动化覆盖，也不在手工冒烟列表中，则将其视为未测试，并开一个覆盖缺口。

***

## 将覆盖矩阵视为契约

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

1. 一个或多个测试路径， **或**
2. 一个有理由的 `🚫` 并带有手工冒烟条目。

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

***

## 如有疑问

* 尽可能把测试推到更低的层级栈中（Rust 单元 > Rust 集成 > Vitest > WDIO）。更低的层级更快、更确定、运行成本更低。
* WDIO 用于真正跨越 UI ⇄ Tauri ⇄ 嵌入式 core ⇄ JSON-RPC 的行为。不要因为 UI 存在，就把本可由单元测试覆盖的关注点交给 WDIO。
* happy path 失败是回归。缺失失败路径测试是缺口。二者都是 bug。
