创建日期: 2026-02-06 | 适用范围: Employee App、Guest App、Kiosk App
| 维度 | Mock 测试 | 真实后端测试 |
|---|---|---|
| 后端依赖 | ❌ 不需要后端运行 | ✅ 需要 localhost:3000 运行 |
| 数据库依赖 | ❌ 不需要数据库 | ✅ 需要 PostgreSQL + 种子数据 |
| 执行速度 | ⚡ 快(秒级) | 🐢 慢(需要网络 I/O + DB 查询) |
| CI 友好度 | ⭐⭐⭐ 无外部依赖,随时可跑 | ⭐⭐ 需要 Docker/后端服务 |
| 验证范围 | UI 渲染 + 交互逻辑 + 状态管理 | 端到端数据流 + API 契约 + 认证 |
| 边界场景 | ⭐⭐⭐ 精确模拟任意场景(500、超时、空数据) | ⭐ 只能测试实际可达的场景 |
| API 契约验证 | ❌ mock 数据可能与真实 API 不一致 | ✅ 真实验证前后端数据格式匹配 |
| 数据一致性 | ❌ mock 数据是静态的,不反映 DB 变更 | ✅ 使用真实数据库,反映最新 schema |
| 稳定性 | ⭐⭐⭐ 确定性高,几乎不 flaky | ⭐⭐ 可能因网络/数据/时序问题 flaky |
| 维护成本 | 需要手动维护 mock 数据(API 变更时同步更新) | 需要维护种子数据 + 后端环境 |
核心是在 Dio 的 Interceptor 层拦截所有 HTTP 请求,根据路径和方法返回预定义的 JSON 响应,完全不触及真实网络。
// MockInterceptor — 路由注册模式
class MockInterceptor extends Interceptor {
final Map<String, dynamic Function(RequestOptions)> _routes = {};
void register(String method, String path, dynamic Function(RequestOptions) handler) {
_routes['$method:$path'] = handler;
}
@override
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
final key = '${options.method}:${options.path}';
final route = _routes[key];
if (route != null) {
handler.resolve(Response(
requestOptions: options,
statusCode: 200,
data: route(options),
));
} else {
handler.resolve(Response(
requestOptions: options,
statusCode: 404,
data: {'error': 'Mock route not found: $key'},
));
}
}
}
| 场景 | 示例 | 为什么 Mock 更适合 |
|---|---|---|
| 空数据 | 门店列表为空、预约列表为空 | 真实后端很难保证空数据状态 |
| 服务器错误 | 500 Internal Server Error | 不可能让真实后端故意返回 500 |
| 网络超时 | 请求超过 10 秒无响应 | 真实网络超时不可控 |
| 数据异常 | API 返回意外格式的 JSON | 真实后端不会返回错误格式 |
| Loading 状态 | 验证加载动画正确显示 | 可精确控制响应延迟 |
| 权限不足 | 403 Forbidden 的 UI 处理 | 可精确模拟权限拒绝 |
| Token 过期 | 401 后的重新登录流程 | 可精确控制认证状态 |
Flutter App 直接连接运行中的后端服务,走完整的 HTTP → 路由 → 中间件 → Service → DB 链路。
# 1. 确保后端运行
npm run start:backend
# 2. 确保种子数据已加载
npm run db:seed:dev
# 3. 运行测试
cd employee_mobile_app/qqnails_employee_app
flutter test integration_test/
| 场景 | 示例 | 为什么真实后端更适合 |
|---|---|---|
| 登录认证 | 输入账号密码 → 获取 JWT → 访问受保护资源 | 验证真实认证链路 |
| 数据 CRUD | 创建预约 → 查看列表 → 确认存在 | 验证数据真正被写入数据库 |
| API 契约 | 后端返回的 JSON 结构 App 能正确解析 | 捕获字段重命名/类型变更 |
| 权限验证 | 员工角色不能访问管理员接口 | 验证后端 RBAC 中间件 |
| 业务流程 | 完整预约流程:选服务 → 选技师 → 选时间 → 确认 | 验证整个流程无断点 |
| 数据关联 | 签到后预约状态从 confirmed → checked_in | 验证多表联动 |
理想的测试金字塔:
| Bug 类型 | Mock 能发现? | 真实测试能发现? |
|---|---|---|
| 按钮点击后 UI 没更新 | ✅ | ✅ |
| 空列表时页面崩溃 | ✅ | ❌(真实数据不为空) |
后端返回字段从 store_name 改为 storeName |
❌(mock 不会自动更新) | ✅ |
| 网络断开时 App 崩溃 | ✅ | ❌(测试环境网络正常) |
| JWT 过期后 refresh token 不工作 | ❌(mock 跳过认证) | ✅ |
| 预约时间冲突检查失效 | ❌(mock 不校验业务规则) | ✅ |
| Loading 动画卡住不消失 | ✅(可控制响应时机) | ❌(可能太快看不到) |
统一的三个 App 测试目录结构:
{app}/integration_test/
├── mock/ # Mock 测试(不连接后端)
│ ├── mock_interceptor.dart # Dio 拦截器,按路由返回 mock 数据
│ ├── fake_secure_storage.dart # 内存 SecureStorage
│ ├── t01_xxx_mock_test.dart # 各 mock 测试文件
│ └── ...
├── helpers/ # 真实后端测试辅助
│ ├── {app}_test_config.dart # extends BaseTestConfig
│ ├── {app}_test_helpers.dart # App 特有的 helper 方法
│ └── {app}_test_actions.dart # 高级业务操作
├── t01_xxx_test.dart # 真实后端测试文件
├── t02_xxx_test.dart
└── ...
# 只跑 mock 测试(不需要后端)
flutter test integration_test/mock/
# 只跑真实后端测试(需要后端运行)
flutter test integration_test/t01_*.dart integration_test/t02_*.dart ...
# 跑全部
flutter test integration_test/
| App | Mock 测试 | 真实后端测试 | 计划 |
|---|---|---|---|
| Employee App | ❌ 无 | ✅ 20 个测试 | 补 mock 测试(~5个,覆盖边界场景) |
| Guest App | ❌ 无 | ❌ 无(仅 widget 测试) | 两者都要新建 |
| Kiosk App | ✅ 12 个测试 | ❌ 无 | 补真实后端测试(~6个,覆盖核心流程) |
为避免三个 App 各写一套相同的工具代码,提取公共部分到 packages/flutter_test_shared/:
| 共享组件 | 说明 | 用于 |
|---|---|---|
PumpStrategy |
封装 pump(Duration) + 轮询,禁止 pumpAndSettle() |
Mock + 真实 |
safeTap() |
先等 widget 出现,再点击,再 pump | Mock + 真实 |
scrollToFind() |
滚动查找 widget(带方向和最大次数) | Mock + 真实 |
waitForWidget() |
带超时的 widget 出现等待 | Mock + 真实 |
BaseMockInterceptor |
Dio 拦截器基类,子类注册 App 特有路由 | 仅 Mock |
FakeSecureStorage |
内存 HashMap 实现的 SecureStorage | 仅 Mock |
BaseTestConfig |
超时常量、baseUrl、通用配置 | Mock + 真实 |
pumpAndSettle():所有三个 App 都有无限动画(CircularProgressIndicator、shimmer loading 等),pumpAndSettle() 会无限等待导致测试超时。统一使用 pump(Duration) + 轮询模式。
你在测试什么?
├── UI 渲染是否正确? → Mock 测试
├── 边界/异常场景?(空数据、网络错误、超时) → Mock 测试
├── 前后端数据格式是否匹配? → 真实后端测试
├── 认证/权限是否正常? → 真实后端测试
├── 完整业务流程能否走通? → 真实后端测试
└── 两者都需要 → 两套都写
文档版本: v1.0 | 最后更新: 2026-02-06