2026-03-19 01:12:17 +08:00
|
|
|
|
# `app-expo` 测试框架规范
|
|
|
|
|
|
|
|
|
|
|
|
## 目标
|
|
|
|
|
|
|
|
|
|
|
|
测试只解决一件事:阻止高价值回归。
|
|
|
|
|
|
|
|
|
|
|
|
禁止为了覆盖率、形式感或 TDD 仪式感添加低价值测试。
|
|
|
|
|
|
|
|
|
|
|
|
## 工具链
|
|
|
|
|
|
|
|
|
|
|
|
- 测试框架:`Jest`
|
|
|
|
|
|
- 组件测试:`@testing-library/react-native`
|
|
|
|
|
|
- Expo 预设:`jest-expo`
|
|
|
|
|
|
- E2E:暂不纳入默认门禁,后续如需引入,使用 `Maestro`
|
|
|
|
|
|
|
|
|
|
|
|
## 强制规则
|
|
|
|
|
|
|
|
|
|
|
|
- 必须优先测试高风险代码:有分支、有状态、有环境差异、有回归历史。
|
|
|
|
|
|
- 必须优先写用户可见断言,不写实现细节断言。
|
|
|
|
|
|
- 必须把测试文件放在 `tests/`,禁止放进 Expo Router 的 `app/` 目录。
|
|
|
|
|
|
- 必须让 `react-test-renderer` 与 `react` 主版本严格一致。
|
|
|
|
|
|
- 必须在新增领域逻辑时评估是否加入 `collectCoverageFrom`。
|
|
|
|
|
|
- 必须保证 CI 可重复运行,测试不得依赖本地手工环境。
|
|
|
|
|
|
|
|
|
|
|
|
## 禁止规则
|
|
|
|
|
|
|
|
|
|
|
|
- 禁止为了凑覆盖率给低价值代码补测试。
|
|
|
|
|
|
- 禁止批量给 `src/components/ui/*` 生成型 wrapper 补测试。
|
|
|
|
|
|
- 禁止测试 `className`、样式细节、第三方库内部实现。
|
|
|
|
|
|
- 禁止默认使用 snapshot。
|
|
|
|
|
|
- 禁止在没有明确回归风险时盲目 TDD。
|
|
|
|
|
|
- 禁止在 CI 中自动更新 snapshot。
|
|
|
|
|
|
|
|
|
|
|
|
## 测什么
|
|
|
|
|
|
|
|
|
|
|
|
优先级从高到低:
|
|
|
|
|
|
|
|
|
|
|
|
1. 纯逻辑和桥接层
|
|
|
|
|
|
2. 关键页面的关键可见结果
|
|
|
|
|
|
3. 少量高价值主流程
|
|
|
|
|
|
|
|
|
|
|
|
当前门禁覆盖文件:
|
|
|
|
|
|
|
|
|
|
|
|
- `src/i18n/index.ts`
|
|
|
|
|
|
- `src/constants/theme-bridge.ts`
|
|
|
|
|
|
- `src/app/index.tsx`
|
|
|
|
|
|
|
|
|
|
|
|
## 不测什么
|
|
|
|
|
|
|
|
|
|
|
|
默认不测以下内容,除非出现真实回归或明确业务风险:
|
|
|
|
|
|
|
|
|
|
|
|
- `src/components/ui/*`
|
|
|
|
|
|
- 纯透传组件
|
|
|
|
|
|
- 静态文案和静态布局
|
|
|
|
|
|
- 样式实现细节
|
|
|
|
|
|
- 第三方库已经保证的行为
|
|
|
|
|
|
|
|
|
|
|
|
## RNTL 规则
|
|
|
|
|
|
|
|
|
|
|
|
- 当前使用 `@testing-library/react-native@13`
|
|
|
|
|
|
- 直接使用内置 matcher,例如 `toBeOnTheScreen()`
|
|
|
|
|
|
- 不添加 `@testing-library/react-native/extend-expect`
|
|
|
|
|
|
- 不引入 `@testing-library/jest-native`,除非现有 matcher 明确不够用
|
|
|
|
|
|
|
|
|
|
|
|
## TDD 规则
|
|
|
|
|
|
|
|
|
|
|
|
- 新增业务逻辑、修 bug、有明确回归风险时:优先测试先行
|
|
|
|
|
|
- 配置接入、探索性 UI 调整、低风险文案变更:不强制 TDD
|
|
|
|
|
|
- 如果代码很难测:先拆耦合,再决定是否写测试
|
|
|
|
|
|
|
|
|
|
|
|
## Snapshot 规则
|
|
|
|
|
|
|
|
|
|
|
|
默认不用 snapshot。
|
|
|
|
|
|
|
|
|
|
|
|
只有同时满足以下条件才允许:
|
|
|
|
|
|
|
|
|
|
|
|
- 组件稳定
|
|
|
|
|
|
- 共享范围广
|
|
|
|
|
|
- 风险来自整体结构而不是交互
|
|
|
|
|
|
- 评审明确同意
|
|
|
|
|
|
|
|
|
|
|
|
否则一律改写为行为断言。
|
|
|
|
|
|
|
|
|
|
|
|
## 覆盖率规则
|
|
|
|
|
|
|
|
|
|
|
|
覆盖率是门禁,不是目标。
|
|
|
|
|
|
|
|
|
|
|
|
当前阈值:
|
|
|
|
|
|
|
|
|
|
|
|
- `branches >= 80`
|
|
|
|
|
|
- `functions >= 90`
|
|
|
|
|
|
- `lines >= 90`
|
|
|
|
|
|
- `statements >= 90`
|
|
|
|
|
|
|
|
|
|
|
|
这些阈值只对 `collectCoverageFrom` 中的高价值文件生效。
|
|
|
|
|
|
|
|
|
|
|
|
新增有分支、状态变化或环境差异的领域逻辑时,评审必须回答一个问题:
|
|
|
|
|
|
|
|
|
|
|
|
`这个文件要不要进入 collectCoverageFrom?`
|
|
|
|
|
|
|
|
|
|
|
|
## 本地命令
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
npm run test
|
|
|
|
|
|
npm run test:changed
|
|
|
|
|
|
npm run test:ci
|
|
|
|
|
|
npm run test:update
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
本地约束:
|
|
|
|
|
|
|
|
|
|
|
|
- 日常开发优先使用 `npm run test` 或 `npm run test:changed`
|
|
|
|
|
|
- 提交前必须至少运行一次 `npm run test:ci`
|
|
|
|
|
|
- 只有明确批准时才运行 `npm run test:update`
|
|
|
|
|
|
|
|
|
|
|
|
## CI 门禁
|
|
|
|
|
|
|
|
|
|
|
|
`App Expo Quality` 必须执行:
|
|
|
|
|
|
|
|
|
|
|
|
- `npm ci`
|
|
|
|
|
|
- `npm run format:check`
|
|
|
|
|
|
- `npm run lint`
|
|
|
|
|
|
- `npm run test:ci`
|
|
|
|
|
|
|
|
|
|
|
|
同仓 PR 额外要求:
|
|
|
|
|
|
|
|
|
|
|
|
- 发布 coverage sticky comment
|
|
|
|
|
|
|
|
|
|
|
|
## E2E 规则
|
|
|
|
|
|
|
|
|
|
|
|
E2E 不是默认门禁。
|
|
|
|
|
|
|
|
|
|
|
|
只有满足以下条件才引入:
|
|
|
|
|
|
|
|
|
|
|
|
- 核心用户旅程稳定
|
|
|
|
|
|
- 已明确 smoke flow
|
|
|
|
|
|
- 团队能承担维护成本
|
|
|
|
|
|
|
|
|
|
|
|
引入后也只测关键主路径,不做像素巡检,不做大面积 UI 回归脚本。
|
2026-05-17 22:04:54 +02:00
|
|
|
|
|
|
|
|
|
|
### 前置准备
|
|
|
|
|
|
|
|
|
|
|
|
安装 Maestro CLI:
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
curl -Ls "https://get.maestro.mobile.dev" | bash
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
构建并安装到目标 iOS Simulator / Android Emulator / 真机。不同 flow 对构建方式的要求不同:
|
|
|
|
|
|
|
|
|
|
|
|
| 场景 | 构建命令 | 读取配置 |
|
|
|
|
|
|
|------|---------|---------|
|
2026-05-20 10:27:40 +08:00
|
|
|
|
| 普通开发 | `pnpm ios` / `pnpm android` | `env/development` |
|
2026-05-17 22:04:54 +02:00
|
|
|
|
| 需要登录态的 E2E | `pnpm ios:e2e` / `pnpm android:e2e` | `.env.e2e`(staging backend + E2E 开关) |
|
|
|
|
|
|
|
2026-05-20 10:27:40 +08:00
|
|
|
|
正常 staging 构建继续使用 `env/staging`(`use-env staging`),不注入 `EXPO_PUBLIC_E2E`。
|
2026-05-17 22:04:54 +02:00
|
|
|
|
|
|
|
|
|
|
### 现有 flow
|
|
|
|
|
|
|
|
|
|
|
|
| Flow 文件 | 用途 | 需要后端 | 需要 E2E 构建 |
|
|
|
|
|
|
|-----------|------|---------|--------------|
|
|
|
|
|
|
| `login-smoke.yaml` | 登录页协议拦截 smoke,验证 App 启动到未登录态 | 否 | 否 |
|
|
|
|
|
|
| `login-authenticated.yaml` | 快捷登录进入已登录态 | 是 | 是 |
|
|
|
|
|
|
| `post-login-tabs-smoke.yaml` | 登录后 Tab 导航 smoke(对话 → 回忆录 → 我的 → 对话) | 是 | 是 |
|
|
|
|
|
|
| `post-login-long-chat.yaml` | 登录后发送大量对话消息,为回忆录生成提供种子数据 | 是 | 是 |
|
|
|
|
|
|
|
|
|
|
|
|
### 运行命令
|
|
|
|
|
|
|
|
|
|
|
|
无后端依赖(普通构建即可):
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
pnpm e2e:ios
|
|
|
|
|
|
pnpm e2e:android
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
需要 E2E 构建 + 后端 mock 登录:
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
pnpm e2e:auth:ios
|
|
|
|
|
|
pnpm e2e:auth:android
|
|
|
|
|
|
pnpm e2e:post-login:ios
|
|
|
|
|
|
pnpm e2e:long-chat:ios
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
调试时可通过 `pnpm start:e2e` 启动 dev server 并自动加载 `.env.e2e`。
|
|
|
|
|
|
|
|
|
|
|
|
### 登录后 E2E 环境要求
|
|
|
|
|
|
|
|
|
|
|
|
- 后端:`APP_ENV` 不能是 `production`,并开启 `MOCK_SMS_LOGIN_ENABLED=1`
|
|
|
|
|
|
- App:通过 `pnpm ios:e2e` / `pnpm android:e2e` 构建安装,读取 `.env.e2e`
|
|
|
|
|
|
- 所有需要登录态的 flow 通过点击 `login.e2e.quickLogin.button` 进入已登录态
|