
OpenHuman 测试策略实战从 Vitest 前端单测、Rust 集成测试到 tauri-driver E2E 的完整测试体系【免费下载链接】openhumanOpenHuman is an open source personal AI for Mac, Windows and Linux — local-first memory, agent orchestration, and deep research.项目地址: https://gitcode.com/GitHub_Trending/op/openhumanOpenHuman 是一个本地优先的开源个人 AI 应用Mac / Windows / Linux其前端为 React Vite Tauri后端核心为 Rust 工作区。仓库中的 test-agent 子代理定义 集中描述了该项目的测试策略骨架前端单元测试Vitest Testing Library、Rust 单元测试、集成测试与 E2E 配置、移动端测试以及 CI 集成。本文以该文档为主线逐节继承其中的配置、命令与代码示例并结合仓库中真实落地的配置文件如 app/test/vitest.config.ts、scripts/test-rust-with-mock.sh补充默认值、端口与隔离策略等细节帮助读者掌握一套可直接复现的前后端全栈测试方案。test-agent 子代理职责与能力边界test-agent 以 Claude Code 子代理subagent的形式定义在 .claude/agents/test-agent.md 中其 YAML frontmatter 声明了运行身份--- name: test-agent description: Manages testing strategies for both frontend and backend code across all platforms model: sonnet color: yellow ---其声明的四项核心能力与后文各章节一一对应运行前端单元测试Vitest运行 Rust 单元测试cargo test搭建集成测试配置 E2E 测试tauri-driver / WebDriver 路线。值得注意的是该代理覆盖的是前端 后端双栈测试策略OpenHuman 的仓库实际是 pnpm workspace 与 Cargo workspace 的混合体根目录 pnpm-workspace.yaml、Cargo.toml前端位于 app/ 子包Rust 核心位于仓库根的 src/Tauri 壳位于 app/src-tauri/。理解这一布局是理解下文所有测试命令的前提。前端测试Vitest Testing Library jsdom依赖安装文档给出的最小安装命令如下用于引入测试运行器、React 测试工具链与 jsdom 环境# Install testing dependencies npm install -D vitest testing-library/react testing-library/jest-dom jsdom对照仓库实际依赖app/package.json 的 devDependencies当前版本基线为vitest ^4.0.18、testing-library/react ^16.3.2、testing-library/jest-dom ^6.9.1、jsdom ^28.0.0并额外包含testing-library/user-event真实用户交互模拟、vitest/coverage-v8V8 覆盖率、playwright/test、webdriverio与整套wdio/*包供 E2E 使用。仓库使用 pnpm 而非 npm实际安装时应使用 pnpm 对应命令。Vitest 配置文档中的最小配置模板// vitest.config.ts import react from vitejs/plugin-react; import { defineConfig } from vitest/config; export default defineConfig({ plugins: [react()], test: { environment: jsdom, setupFiles: ./src/test/setup.ts, globals: true }, });而仓库中的实际配置落在 app/test/vitest.config.ts由pnpm test通过--config test/vitest.config.ts显式指定。相对最小模板实际配置增加了几个关键工程决策Node polyfills 注入通过vite-plugin-node-polyfills为 jsdom 环境注入buffer、process、util、os、crypto、stream等全局对象解决前端代码依赖 Node 内置模块的问题路径别名指向src/并将 workspace 内的tauri-plugin-ptt-api解析到 packages/tauri-plugin-ptt/guest-js/index.ts保证跨包导入在测试中可用单 worker 串行执行maxWorkers: 1, minWorkers: 1配合全量 V8 覆盖率插桩时牺牲速度换取确定性mock 重置策略clearMocks: true但mockReset: false、restoreMocks: false——注释明确说明mockReset会清除 setup.ts 中共享 mock 的实现如getBackendUrl因此只清调用历史、保留实现超时放宽hookTimeout与testTimeout均为 30000ms覆盖率范围provider: v8include 为src/**/*.{ts,tsx}并显式排除类型声明文件、src/test/**与仅用于开发调试的src/pages/dev/**reporter 输出text、text-summary、html、lcov四种格式。全局 setup把模拟一个 Tauri 应用运行时做成基础设施文档中的最小 setup 只 mock 了invoke// src/test/setup.ts文档最小模板 import testing-library/jest-dom; import { vi } from vitest; // Mock Tauri APIs vi.mock(tauri-apps/api/core, () ({ invoke: vi.fn() }));仓库中真正的 app/src/test/setup.ts约 390 行则把这套思路做成了完整的基础设施其要点包括内置 mock 后端setup 启动阶段直接导入 scripts/mock-api-core.mjs 并调用startMockServer(port, { retryIfInUse: true })默认端口 5005可由VITEST_MOCK_API_PORT/MOCK_API_PORT覆盖随后把VITEST_MOCK_API_URL、VITE_BACKEND_URL写入环境变量使所有单元测试天然对着一个本地 HTTP mock 后端运行afterAll中stopMockServer()收尾。jsdom 缺失 API 的成批 polyfillwindow.matchMediaRive / 媒体查询 hooks、ResizeObservercmdk / Radix、scrollIntoViewcmdk、HTMLElement.prototype.scrollToassistant-ui 线程视口、Range.getBoundingClientRect/getClientRectsLexical 选区测量、Pointer Capture 三件套Radix 的 Select / Slider / Toggle / DropdownMenu 在 pointerdown 时无条件调用、IntersectionObserverRadix 懒挂载、以及 jsdom 28 缺失的PointerEvent构造器否则testing-library/user-event只能回退到 MouseEventRadix 的 pointer 处理器永远不触发。Tauri 运行时代替向window.__TAURI_INTERNALS__注入一个 no-op 的invoke使isTauri()的 IPC-ready 检查默认通过同时vi.mock掉tauri-apps/api/coreinvokeisTauri: () false、tauri-apps/api/event、tauri-apps/plugin-deep-link、tauri-apps/plugin-opener、tauri-apps/plugin-os固定返回macos以及项目自己的utils/tauriCommands模块storeSession、getAuthState、openhumanService*等服务命令全部返回确定性的成功值。重量级第三方库降级redux-persist被 mock 为直接返回基础 reducer 的 no-op persistor规避 CJS/ESM 问题redux-logger、sentry/react同样整体替换为空实现。测试隔离钩子afterEach中清空 mock 后端的请求日志clearRequestLog、执行 Testing Library 的cleanup()、并重新播种__TAURI_INTERNALS__因为部分测试会delete它来走 CEF 缺失分支beforeEach中重置 mock 行为resetMockBehavior与 MCP 限流器的模块级计数器。输出治理未设置DEBUG_TESTS1时console.log/info/debug/warn/error全部静音保持测试输出干净。从源码结构看这种setup 即环境的写法解释了为什么 vitest 配置敢开全量 V8 覆盖率每个测试文件启动即获得一个隔离的 mock 后端与完整的 jsdom 补丁测试用例本身几乎不需要关心环境搭建。编写测试文档给出的组件测试示例核心是渲染 断言 mock IPC 调用三步import { render, screen, fireEvent } from testing-library/react; import { describe, it, expect, vi } from vitest; import { invoke } from tauri-apps/api/core; import App from ./App; describe(App, () { it(renders greeting button, () { render(App /); expect(screen.getByText(Greet)).toBeInTheDocument(); }); it(calls greet command on click, async () { vi.mocked(invoke).mockResolvedValue(Hello, World!); render(App /); fireEvent.click(screen.getByText(Greet)); expect(invoke).toHaveBeenCalledWith(greet, { name: expect.any(String) }); }); });这里vi.mocked(invoke)的类型收窄技巧值得保留因为 setup.ts 已全局 mocktauri-apps/api/coreinvoke天然是vi.fn()vi.mocked()让 TypeScript 能识别它的mockResolvedValue/toHaveBeenCalledWith等 mock API。仓库中的真实用例如 app/AppRoutes.redirects.test.tsx、app/src/components 下大量*.test.tsx延续了同样的模式用screen.getBy*定位、用expect(invoke).toHaveBeenCalledWith(命令名, 参数)验证前端是否正确发起 Tauri IPC 调用而不是真实调用 Rust 侧。运行命令文档给出的运行方式npm test # Run all tests npm test -- --watch # Watch mode npm test -- --coverage # Coverage在 app/package.json 中这些能力被拆分为显式脚本注意仓库使用 pnpm{ scripts: { test: vitest run --config test/vitest.config.ts, test:unit: vitest run --config test/vitest.config.ts, test:unit:watch: vitest --config test/vitest.config.ts, test:watch: vitest --config test/vitest.config.ts, test:coverage: vitest run --config test/vitest.config.ts --coverage } }即test等价于一次性跑完test:watch才是文档中--watch的对应物。Rust 测试单元测试写法文档中在 Tauri 壳src-tauri/src/lib.rs内联#[cfg(test)] mod tests的示例展示了两种基础形态#[cfg(test)] mod tests { use super::*; #[test] fn test_greet() { let result greet(World); assert!(result.contains(World)); } #[tokio::test] async fn test_async_command() { let result fetch_data(https://example.com).await; assert!(result.is_ok()); } }同步逻辑用#[test]异步逻辑用#[tokio::test]这一原则在 OpenHuman 的 Rust 核心中同样成立。从源码结构看仓库的 Rust 核心把测试组织为与源码同目录的独立*_tests.rs文件而非仅内联 mod例如 src/core/auth.rs 有 20 个#[test]、src/core/cli_tests.rs 23 个、src/core/all_tests.rs 86 个且其中包含大量#[tokio::test]如 src/core/all_tests.rs#L722-L736。这种实现文件 伴生测试文件的布局让cargo test --lib可以统一编译并运行所有核心模块的单元测试。运行命令与仓库增强文档的基本运行方式cd src-tauri cargo test cargo test -- --nocapture # 带 stdout 输出 cargo test test_greet # 运行指定测试这些命令对app/src-tauri这个 Tauri 壳仍然成立。但对 OpenHuman 的产品级 Rust 测试面仓库提供的是pnpm test:rust见 app/package.json它委托给 scripts/test-rust-with-mock.sh。该脚本的工程要点值得逐个展开共享 mock 后端脚本先启动 scripts/mock-api-server.mjs默认MOCK_API_PORT18505日志写到/tmp/openhuman-mock-api.log以__admin/health端点做 30 秒健康轮询随后导出BACKEND_URL/VITE_BACKEND_URL指向该 mock——Rust 集成测试因此全程 hermetic无外部网络依赖栈空间保护RUST_MIN_STACK默认提升到 16MBscripts/test-rust-with-mock.sh#L50注释说明 agent harness 的异步 future 在 debug 构建下非常大Apple Silicon 上默认测试线程栈会栈溢出产品特性集从 scripts/ci/product-features.txt 读取特性列表以cargo test --workspace --features ${PRODUCT_FEATURES},bin-tools运行避免四个required-features集成目标json_rpc_e2e、raw_coverage_all、observability_smoke、x402_twit_sh_live被静默跳过原生测试模块构建按固定子模块构建 TinyMemory / TinyJuice / TinyConnectors 的测试.so并通过TINYMEMORY_TEST_MODULE等环境变量注入钱包模块则从校验和固定的 release 归档下载SHA256 校验后解压保证测试可复现进程级隔离raw_coverage类模块与json_rpc_e2e用例会逐个在独立的 cargo 进程中以--test-threads1运行如 tests/raw_coverage/ 下 70 余个模块、tests/json_rpc_e2e.rs因为这类用例会修改进程级全局状态串行独立进程是保证确定性的手段用法无参运行完整套件--test name透传给cargo test跑指定目标还支持--test raw_coverage_all/--test json_rpc_e2e两个特殊入口分别走隔离执行路径。集成测试本体集中在仓库根的 tests/ 目录agent_harness_e2e.rs、memory_tree_summarizer_e2e.rs、mcp_registry_e2e.rs等它们通过 mock 后端驱动完整的 JSON-RPC / 记忆 / 编排链路属于后端集成测试层。集成 / E2E 测试文档给出的 tauri-driver 路线文档建议通过cargo install tauri-driver安装 WebDriver然后用 WebDriver 协议驱动应用const { Builder, By } require(selenium-webdriver); describe(App E2E, () { let driver; beforeAll(async () { driver await new Builder().usingServer(http://localhost:4444).forBrowser(tauri).build(); }); afterAll(async () { await driver.quit(); }); it(shows greeting, async () { const button await driver.findElement(By.css(button)); await button.click(); const message await driver.findElement(By.css(.message)); expect(await message.getText()).toContain(Hello); }); });仓库的落地实现沿用了同一条技术路线tauri-driver 监听127.0.0.1:4444的 WebDriver 协议但驱动框架从 selenium-webdriver 换成了 WebDriverIO Mochaapp/package.json 中的webdriverio ^9.24.0与wdio/*系列配置位于 app/test/wdio.conf.ts。从源码结构看其关键设计有单一 WebDriver 会话maxInstances: 1且不做跨 spec 拆会话——所有 spec 顺序运行在同一个应用进程中省去重启成本测试因此被设计为顺序依赖、由每个 spec 自行负责需要的状态重置mock 状态按 spec 文件重置每个 spec 文件首次执行时向 mock 后端的__admin/reset发一次 POST端口来自BACKEND_URL或E2E_MOCK_PORT默认 18473防止某个 spec 失败后污染下一个文件失败产物保留trace: retain-on-failure策略下自动截图/录屏/保存 trace并在失败时调用captureFailureArtifactsapp/test/e2e/helpers/artifacts运行入口app/scripts/e2e-run-spec.sh 是薄壳接收 spec 路径后exec到 app/scripts/e2e-run-session.sh由后者负责启动 tauri-driver、等待其/status端点就绪再调用 wdiospec 文件位于 app/test/e2e/specs/。网页端 E2EPlaywright除原生桌面端外app/还提供独立的 web 端 E2E 通道app/playwright.config.ts 将testDir指向test/playwright/specsbaseURL默认http://127.0.0.1:4173Vite preview 端口单 worker、CI 下 2 次重试与 90s 超时。对应脚本为pnpm test:e2e:web构建 web 目标后跑 app/scripts/e2e-web-session.sh以及按流程拆分的test:e2e:login、test:e2e:auth、test:e2e:megaapp/scripts/e2e-login.sh、app/scripts/e2e-run-spec.sh 等。移动端测试文档给出的两条命令分别覆盖 Android instrumented test 与 iOS XCTest# Android运行 instrumented tests cd src-tauri/gen/android ./gradlew connectedAndroidTest # iOS运行 XCTest xcodebuild test \ -project src-tauri/gen/apple/tauri-app.xcodeproj \ -scheme tauri-app \ -destination platformiOS Simulator,nameiPhone 15结合仓库现状Tauri 生成的gen/android、gen/apple工程由初始化脚本产出scripts/android-init.sh、scripts/ios-init.shapp/package.json 中也有tauri:ios:init、tauri:android:init、tauri:ios:build、release:android:play等配套脚本并依赖IPHONEOS_DEPLOYMENT_TARGET默认 16.0。devDependencies 中同时存在wdio/appium-service说明移动端 UI 自动化走的是 Appium WebDriverIO 通道配套解析脚本见 app/scripts/e2e-resolve-node-appium.sh。文档中的 gradlew / xcodebuild 命令在本地生成工程后可直接使用iOS 模拟器名称需按本机已安装的模拟器调整。测试脚本编排与 CI 集成文档建议的 package.json 脚本集{ scripts: { test: vitest, test:watch: vitest --watch, test:coverage: vitest --coverage, test:rust: cd src-tauri cargo test, test:all: npm test npm run test:rust } }仓库实际编排app/package.json在此之上把 E2E 也纳入了test:all{ scripts: { test: vitest run --config test/vitest.config.ts, test:rust: bash ../scripts/test-rust-with-mock.sh, test:e2e: pnpm test:e2e:web pnpm test:e2e:mega, test:all: pnpm test:coverage pnpm test:rust pnpm test:e2e } }即前端覆盖率 Rust mock 后端全量测试 双通道 E2E构成完整门禁。文档给出的 CI 示例GitHub Actionsname: Test on: [push, pull_request] jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkoutv4 - uses: actions/setup-nodev4 - uses: dtolnay/rust-toolchainstable - run: npm ci - run: npm test - run: cd src-tauri cargo test对照仓库实际约定有两处适配依赖安装用pnpm install --frozen-lockfile锁定文件为 pnpm-lock.yamlRust 测试一步替换为调用bash scripts/test-rust-with-mock.sh它自带 mock 后端、特性集与原生模块构建。此外仓库提供了 scripts/test-ci-local.sh 用于本地复现 CI 行为以及 scripts/ci/ 目录下的覆盖率存在性检查assert-coverage-presence.sh、rust-coverage-changed.sh等辅助门禁可视为该 CI 思路的落地形态。小结三层测试金字塔在 OpenHuman 中的落点层次文档中的定位仓库实际落点前端单元测试Vitest Testing Library jsdommockinvokeapp/test/vitest.config.ts app/src/test/setup.ts内置 mock 后端端口 5005pnpm test/test:coverageRust 单元/集成测试cargo test#[test]/#[tokio::test]伴生*_tests.rs如 src/core/all_tests.rs tests/ 集成面统一由 scripts/test-rust-with-mock.sh 驱动mock 端口 18505E2Etauri-driver WebDriverselenium-webdriver 示例WebDriverIO tauri-driver127.0.0.1:4444单会话顺序 specapp/test/wdio.conf.tsweb 端 Playwrightapp/playwright.config.ts移动端gradlew / xcodebuild 命令由 scripts/android-init.sh / scripts/ios-init.sh 生成工程后执行Appium 通道可选test-agent 的价值在于把前端 mock 策略、Rust 测试运行器、E2E 驱动方式、CI 编排收敛成一个可被其他代理直接调用的策略清单而仓库中 app/src/test/setup.ts 与 scripts/test-rust-with-mock.sh 则展示了这份清单在生产级工程中的真实深度——每一个看似简单的mock 一下 Tauri API背后都是对 jsdom 能力边界、Rust 进程级状态与覆盖率确定性的系统性处理。【免费下载链接】openhumanOpenHuman is an open source personal AI for Mac, Windows and Linux — local-first memory, agent orchestration, and deep research.项目地址: https://gitcode.com/GitHub_Trending/op/openhuman创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考