chore: sync local project changes

This commit is contained in:
Shen Wei
2026-04-27 16:26:07 +08:00
parent dfcf7de003
commit 5854781fa8
144 changed files with 12849 additions and 12330 deletions

View File

@@ -1,92 +1,95 @@
> **For AI assistants**: Read this entire file before writing any test code. All decisions here are final. Do not suggest alternatives unless asked.
# Fonrey 测试规范TEST_SPEC
> **For AI assistants**: Read this entire file before writing any test code. All decisions here are final. Do not suggest alternatives unless asked. Every new feature or User Story implementation must be accompanied by corresponding tests as defined in this document.
**版本**: 1.0 **最后更新**: 2026-04-26
**定位**: 本文档定义 Fonrey 项目的完整测试策略包含测试分层、工具选型、目录结构、多租户测试约定、HTMX 测试约定、CI 自动化配置及 AI 辅助编码时的测试要求。
**版本**: 1.1
**项目**: Fonrey 房产经纪管理系统
**技术栈**: Django 4.x + django-tenants + PostgreSQL 16 + Redis + Celery + HTMX + Playwright
**关联文档**: `TECH_STACK/TECH_STACK.md``PRD/TASK.md`、各模块技术方案(登录/权限/房源/客源/楼盘/组织人事/系统管理)
**最后更新**: 2026-04-27
---
## 1. 测试目标
## 一、文档定位与边界
Fonrey 采用 AI vibe coding 模式开发AI 负责生成功能代码,**测试是保证每日迭代质量的唯一安全网**。测试体系须满足
本文件定义项目统一测试标准
- 每个 P0 User Story 完成后,对应测试同步产出
- 每日自动运行全量测试套件,输出可读报告
- 测试失败时AI 可根据报告自主定位并修复问题
- 测试环境与生产环境技术栈完全一致(同样使用 PostgreSQL + django-tenants
1. 测试分层与覆盖率目标
2. 测试目录与夹具fixture约定
3. 多租户与 HTMX 场景测试规范
4. CI 执行基线与失败处理流程
5. AI 辅助开发的“测试随功能交付”硬约束
**覆盖率基准目标**
> 本文件不替代模块级测试设计。每个业务模块的案例细节以对应技术方案和 PRD AC 为准。
---
## 二、测试目标与覆盖基线
Fonrey 采用 AI 驱动迭代,测试是质量兜底。所有 P0 User Story 必须做到“功能 + 测试”同步交付。
### 2.1 覆盖率目标
| 层级 | 最低目标 |
|------|---------|
| `core/` 核心基础模块 | ≥ 90% |
|---|---|
| `core/` 基础模块 | ≥ 90% |
| `apps/*/services/` 业务逻辑层 | ≥ 80% |
| `apps/*/views.py` 视图层 | ≥ 70% |
| `apps/*/views*` 接口与视图层 | ≥ 70% |
| `apps/*/tasks.py` 异步任务 | ≥ 70% |
| E2E 核心用户旅程 | 5 条必须全部通过 |
| E2E 核心旅程 | 5 条全部通过 |
### 2.2 质量门禁
- 每个 P0 US 对应至少一个集成测试场景集。
- PR 合并前:单元 + 集成必须全绿。
- `main/develop`:每日自动跑全量(含 E2E 核心旅程)。
---
## 2. 测试分层架构
Fonrey 采用三层测试体系,从底层向上覆盖:
## 三、测试分层架构
```
┌─────────────────────────────────────────┐
E2E 测试(用户行为模拟) │ ← Playwright
│ 覆盖5 条核心用户旅程 │
│ E2E 测试(用户旅程) │ ← Playwright
├─────────────────────────────────────────┤
集成测试(API / View 层) │ ← pytest-django TenantClient
│ 覆盖:所有 P0 User Story 的 HTTP 接口 │
│ 集成测试(HTTP / View / Service / DB │ ← pytest-django + TenantClient
├─────────────────────────────────────────┤
单元测试(逻辑单元 │ ← pytest-django + factory_boy
│ 覆盖core/、services/、tasks.py │
│ 单元测试(逻辑) │ ← pytest + factory_boy + mock
└─────────────────────────────────────────┘
```
**三层分工原则**
### 3.1 单元测试
- **单元测试**:不启动 HTTP server不依赖浏览器速度最快测试单一函数/类的逻辑正确性
- **集成测试**:使用 Django 测试客户端,验证完整请求-响应链路View → Service → DB不启动真实 HTTP server
- **E2E 测试**:启动真实 Django dev server用浏览器驱动验证真实用户操作流程速度最慢只覆盖核心旅程
- 目标:服务层、工具层、任务函数的逻辑正确性
- 约束:不启动真实 HTTP,不依赖外部网络。
### 3.2 集成测试
- 目标验证完整请求链路View → Service → DB
- 约束:必须使用 `TenantClient`,禁止 Django 原生 `Client()`
### 3.3 E2E 测试
- 目标:验证真实用户关键路径。
- 约束:只覆盖核心旅程,避免把所有细节都堆到 E2E。
---
## 3. 工具选型
## 四、工具选型与依赖
### 3.1 工具清单
| 类型 | 工具 | 版本建议 | 用途 |
|---|---|---|---|
| 测试框架 | `pytest` | ≥ 8.x | 统一运行器 |
| Django 集成 | `pytest-django` | ≥ 4.x | DB/Client/Settings |
| 数据工厂 | `factory_boy` | ≥ 3.x | 生成测试数据 |
| 假数据 | `Faker` | ≥ 25.x | 中文业务数据 |
| Mock | `pytest-mock` | ≥ 3.x | 外部依赖打桩 |
| HTTP Mock | `responses` | ≥ 0.25.x | 三方 HTTP 隔离 |
| 覆盖率 | `pytest-cov` | ≥ 5.x | coverage 报告 |
| 并行 | `pytest-xdist` | ≥ 3.x | 加速单测/集成 |
| E2E | `playwright` + `pytest-playwright` | ≥ 1.44 / ≥ 0.5 | 浏览器自动化 |
| 类型 | 工具 | 版本 | 用途 |
|------|------|------|------|
| 测试框架 | `pytest` | ≥ 8.x | 统一测试运行器 |
| Django 集成 | `pytest-django` | ≥ 4.x | Django 数据库、Client、设置管理 |
| 测试数据工厂 | `factory_boy` | ≥ 3.x | 创建测试用 Model 实例,避免手写 fixture |
| 假数据生成 | `Faker` | ≥ 25.x | 生成中文姓名、手机号、地址等假数据 |
| Mock 工具 | `pytest-mock` | ≥ 3.x | Mock 外部依赖R2、Redis、邮件服务 |
| HTTP Mock | `responses` | ≥ 0.25.x | Mock 第三方 HTTP 请求Cloudflare API 等) |
| E2E 测试 | `playwright` (Python) | ≥ 1.44.x | 浏览器自动化 |
| E2E 集成 | `pytest-playwright` | ≥ 0.5.x | Playwright 的 pytest 插件 |
| 覆盖率 | `pytest-cov` | ≥ 5.x | 生成代码覆盖率报告 |
| 并行加速 | `pytest-xdist` | ≥ 3.x | 多进程并行运行单元/集成测试 |
### 3.2 安装依赖
所有测试依赖统一放在 `requirements/test.txt`
```
pytest>=8.0
pytest-django>=4.8
pytest-mock>=3.12
pytest-cov>=5.0
pytest-xdist>=3.5
pytest-playwright>=0.5
factory_boy>=3.3
Faker>=25.0
responses>=0.25
```
安装命令:
安装基线:
```bash
pip install -r requirements/test.txt
@@ -95,209 +98,125 @@ playwright install chromium
---
## 4. 目录结构
## 五、目录结构约定
```
fonrey/
── tests/
├── conftest.py # 全局 fixtures租户、用户、客户端
├── settings_test.py # 测试专用 Django settings
├── factories/ # factory_boy 工厂
│ ├── __init__.py
│ ├── tenant_factory.py # Tenant、域名
│ ├── account_factory.py # Staff、Account
│ ├── org_factory.py # OrgUnit
│ ├── permission_factory.py # Role、Permission
── complex_factory.py # Complex、Building
│ ├── property_factory.py # Property、FollowUpLog
── client_factory.py # Client、ClientFollowUp
├── unit/ # 单元测试
│ ├── test_encryption.py # PII 加密/解密
── test_soft_delete.py # 软删除 Manager
│ ├── test_permission_service.py
│ ├── test_property_service.py
│ ├── test_client_service.py
── test_celery_tasks.py # Celery 任务(同步模式)
├── integration/ # 集成测试(按 User Story 分文件)
│ ├── account/
│ │ └── test_us_account.py # US-ACCOUNT-001~003
── permission/
│ │ └── test_us_permission.py # US-PERMISSION-001~005
├── complex/
│ │ └── test_us_complex.py
├── property/
│ │ └── test_us_property.py # US-PROPERTY-001~008
│ ├── client/
│ │ └── test_us_client.py # US-CLIENT-001~017
│ ├── org/
│ │ └── test_us_org.py
│ └── setting/
│ └── test_us_setting.py
└── e2e/ # E2E 测试(核心用户旅程)
├── conftest.py # E2E 专用 fixtureslive_server、page
├── test_journey_login.py
├── test_journey_property.py
├── test_journey_client.py
├── test_journey_permission.py
└── test_journey_onboarding.py
```text
tests/
── conftest.py
├── settings_test.py
├── factories/
├── tenant_factory.py
│ ├── account_factory.py
│ ├── permission_factory.py
│ ├── complex_factory.py
│ ├── property_factory.py
│ ├── client_factory.py
── org_factory.py
├── unit/
── test_encryption.py
├── test_soft_delete.py
│ ├── test_*_service.py
── test_celery_tasks.py
├── integration/
│ ├── account/test_us_account.py
│ ├── permission/test_us_permission.py
── complex/test_us_complex.py
├── property/test_us_property.py
│ ├── client/test_us_client.py
│ ├── org/test_us_org.py
── setting/test_us_setting.py
└── e2e/
├── conftest.py
├── test_journey_login.py
├── test_journey_property.py
├── test_journey_client.py
├── test_journey_permission.py
└── test_journey_onboarding.py
```
---
## 5. 多租户测试约定
## 六、多租户测试约定(强制)
这是 Fonrey 测试中最重要的约定。`django-tenants` 的 Schema 隔离在测试中必须正确处理,否则测试结果不可信。
### 6.1 核心原则
### 5.1 核心原则
1. 所有 DB 测试在租户 schema 上下文执行。
2. 业务数据禁止直接在 `public` schema 断言。
3. 事务隔离默认开启,测试间不得共享可变状态。
4. 测试请求必须经 `TenantClient` 发出。
- **所有集成测试和单元测试**(涉及数据库的)必须在租户 Schema 上下文中执行
- 严禁在 `public` Schema 下直接操作业务数据
- 每个测试函数执行后,数据库状态自动回滚(`pytest-django``db` fixture 保证事务隔离)
- 禁止测试之间共享可变状态(禁止 `module``session` 级别的数据库 fixtures除非明确只读
### 6.2 标准 fixture最小集合
### 5.2 租户 Fixture 规范
- `tenant`
- `tenant_client`
- `admin_user`
- `staff_user`
- `authenticated_client`
全局 `conftest.py` 必须提供以下标准 fixtures
### 6.3 禁止事项
```python
# tests/conftest.py规范示意非最终代码
@pytest.fixture(scope="session")
def tenant():
"""
创建一个测试租户session 级别,全程复用同一个 Schema
使用 django_tenants.test.client.TenantClient 配套使用。
"""
@pytest.fixture
def tenant_client(tenant):
"""
返回绑定到测试租户的 TenantClient 实例。
等价于 Django 的 Client(),但自动切换到租户 Schema。
所有集成测试的 HTTP 请求必须通过此 client 发出。
"""
@pytest.fixture
def staff_user(tenant):
"""普通员工用户,已完成登录态(含 Cookie/Session"""
@pytest.fixture
def admin_user(tenant):
"""系统管理员用户"""
@pytest.fixture
def authenticated_client(tenant_client, staff_user):
"""已登录状态的 TenantClient"""
```
### 5.3 禁止事项
- ❌ 禁止在测试中使用 Django 原生 `Client()`,必须使用 `TenantClient`
- ❌ 禁止在测试中手动 `SET search_path`,由 fixtures 统一管理
- ❌ 禁止跨租户数据访问断言(每个测试只能操作自己的租户数据)
- 禁止手工 `SET search_path`
- 禁止跨租户数据断言
- 禁止在集成测试用 Django 原生 `Client()`
---
## 6. 单元测试规范
## 七、单元测试规范
### 6.1 适用范围
### 7.1 覆盖范围
单元测试覆盖以下代码,**不依赖 HTTP 请求,速度要求 < 100ms/个**
| 目标代码 | 测试文件位置 |
|---------|------------|
| 代码范围 | 示例文件 |
|---|---|
| `core/encryption.py` | `tests/unit/test_encryption.py` |
| `core/models/base.py`软删除、ActiveManager | `tests/unit/test_soft_delete.py` |
| `apps/*/services/` 所有 service 函数 | `tests/unit/test_*_service.py` |
| `apps/*/tasks.py` Celery 任务 | `tests/unit/test_celery_tasks.py` |
| `core/cache.py` Redis key 工具函数 | `tests/unit/test_cache.py` |
| `core/models/base.py` | `tests/unit/test_soft_delete.py` |
| `apps/*/services/` | `tests/unit/test_*_service.py` |
| `apps/*/tasks.py` | `tests/unit/test_celery_tasks.py` |
### 6.2 factory_boy 规范
### 7.2 Celery 测试模式
每个 Django Model 必须有对应的 Factory集中放在 `tests/factories/`
- Factory 类名统一为 `{ModelName}Factory`
- 使用 `faker` 生成中文假数据(姓名、手机号、地址)
- 手机号字段必须使用未加密的明文值传入 factoryfactory 内部触发 Model 的加密逻辑)
- Factory 之间通过 `SubFactory` 表达依赖关系,禁止在 Factory 内部硬编码 ID
### 6.3 Celery 任务测试规范
所有 Celery 任务测试必须在同步模式下运行,在 `settings_test.py` 中配置:
`tests/settings_test.py` 必须启用
```python
# tests/settings_test.py
CELERY_TASK_ALWAYS_EAGER = True # 任务同步执行
CELERY_TASK_EAGER_PROPAGATES = True # 同步模式下抛出真实异常
CELERY_TASK_ALWAYS_EAGER = True
CELERY_TASK_EAGER_PROPAGATES = True
```
调用方式统一使用 `.apply()` 而非 `.delay()``.apply_async()`
并统一使用
```python
# 正确
result = my_task.apply(args=[...])
# 禁止在测试中使用
my_task.delay(...)
my_task.apply_async(...)
result = some_task.apply(args=[...])
```
### 6.4 PII 加密测试要求
### 7.3 PII 相关必测点
`test_encryption.py` 必须覆盖以下场景:
- 加密后的密文与明文不同
- 相同明文每次加密产生不同密文GCM nonce 随机性
- 解密后的明文与原始明文完全一致
- 加密字段的 SHA-256 hash 索引值具有确定性(相同明文产生相同 hash
- 解密错误(篡改密文)抛出可识别异常
- 密文不等于明文
- 同明文重复加密产生不同密文(随机 nonce
- 解密结果与原文一致
- 哈希索引稳定(同明文同 hash
---
## 7. 集成测试规范
## 八、集成测试规范
### 7.1 适用范围
### 8.1 请求模式
集成测试覆盖完整的 HTTP 请求-响应链路,每个 P0 User Story 至少对应一个集成测试文件。
| 类型 | Header | 预期 |
|---|---|---|
| 普通页面请求 | 无 | 完整 HTML |
| HTMX 局刷请求 | `HTTP_HX_REQUEST=true` | HTML 片段 |
### 7.2 HTMX 请求约定
### 8.2 权限覆盖最小集
Fonrey 的 View 层分为两种响应模式,测试必须对应覆盖:
每个受保护接口必须覆盖:
| 请求类型 | Header | 预期响应 |
|---------|--------|---------|
| 普通页面请求 | 无 | 完整 HTML`<html>`, `<head>`, `<body>` |
| HTMX 局部请求 | `HTTP_HX_REQUEST: true` | 局部 HTML 片段(不含完整页面结构) |
1. 有权限:`200`
2. 无权限:`403`
3. 未登录:`302`
HTMX 请求在 `TenantClient` 中发送方式:
### 8.3 User Story 映射基线
```python
# HTMX 局部请求(规范示意)
response = authenticated_client.get(
'/properties/',
HTTP_HX_REQUEST='true'
)
# 验证返回局部 HTML不含完整页面标签
assert '<html' not in response.content.decode()
assert response.status_code == 200
```
### 7.3 权限验证覆盖要求
每个受权限保护的 View集成测试必须覆盖以下场景缺一不可
1. **有权限用户**:返回 200响应内容符合预期
2. **无权限用户**:返回 403
3. **未登录用户**:返回 302 重定向到登录页
4. **数据域隔离**(如适用):只能看到自己权限范围内的数据
### 7.4 P0 User Story 测试覆盖映射
每个 User Story 的集成测试须覆盖其 **验收标准Acceptance Criteria** 中的所有条目:
| User Story 文件 | 集成测试文件 |
|----------------|------------|
| US 范围 | 测试文件 |
|---|---|
| US-ACCOUNT-001~003 | `tests/integration/account/test_us_account.py` |
| US-PERMISSION-001~005 | `tests/integration/permission/test_us_permission.py` |
| US-COMPLEX-001~003 | `tests/integration/complex/test_us_complex.py` |
@@ -306,306 +225,121 @@ assert response.status_code == 200
| US-ORG-001~003 | `tests/integration/org/test_us_org.py` |
| US-SETTING-001 | `tests/integration/setting/test_us_setting.py` |
### 7.5 外部服务 Mock 规范
### 8.4 外部依赖 Mock 规范
集成测试中必须 Mock 所有外部 I/O禁止真实调用
| 外部依赖 | Mock 方式 |
|---------|---------|
| Cloudflare R2 文件上传 | `pytest-mock` mock `boto3.client` |
| Redis | 使用 `fakeredis` 替代真实 Redis |
| 邮件发送 | Django `django.test.utils.override_settings(EMAIL_BACKEND='django.core.mail.backends.locmem.EmailBackend')` |
| Celery 任务 | `CELERY_TASK_ALWAYS_EAGER=True`(见 §6.3 |
- R2mock `boto3.client`
- Redisfakeredis / locmem cache
- 邮件locmem backend
- 第三方 HTTP`responses` 全量拦截
---
## 8. E2E 测试规范
## 九、E2E 测试规范
### 8.1 适用范围与原则
### 9.1 核心旅程(必须)
E2E 测试成本高、速度慢,**只覆盖核心用户旅程,不追求全覆盖**。以下 5 条旅程为必须通过项,任意一条失败即视为阻塞级问题:
| 编号 | 旅程 | 对应模块 |
|---|---|---|
| J-01 | 登录 → 进入首页 | 登录 |
| J-02 | 录入房源 → 上传图片 → 查看列表 | 房源 |
| J-03 | 录入客源 → 添加跟进 | 客源 |
| J-04 | 无权限访问受限页面 | 权限 |
| J-05 | 创建员工 → 分配角色 → 新员工登录 | 组织人事 + 权限 |
| # | 旅程名称 | 对应模块 |
|---|---------|---------|
| J-01 | 登录 → 进入首页 | 登录管理 |
| J-02 | 录入房源 → 上传照片 → 查看列表 | 房源管理 |
| J-03 | 录入客源 → 添加跟进记录 | 客源管理 |
| J-04 | 无权限员工访问受限页面 → 看到 403 提示 | 权限管理 |
| J-05 | 管理员创建员工 → 分配角色 → 新员工登录验证 | 组织人事 + 权限 |
### 9.2 Playwright 约束
### 8.2 Playwright 技术约定
- 默认 Chromium
- CI 使用 headless
- 禁止 `wait_for_timeout()` 固定等待
- 优先语义等待:`wait_for_url` / `expect(locator)` / `networkidle`
- 浏览器:仅使用 Chromium与 Electron 内核一致)
- 运行模式CI 环境用 `headless=True`;本地调试可用 `headless=False`
- 等待策略:**禁止使用 `page.wait_for_timeout()`(固定等待)**,必须使用语义等待:
- `page.wait_for_url(pattern)` — 等待导航完成
- `expect(locator).to_be_visible()` — 等待元素出现
- `page.wait_for_load_state('networkidle')` — 等待 HTMX 请求完成
- 选择器优先级:`role` > `text` > `placeholder` > `data-testid` > CSS 选择器(可维护性从高到低)
- 断言使用 `expect()` 而非原生 `assert`,获得更清晰的错误输出
### 9.3 HTMX 页面注意事项
### 8.3 HTMX 页面的 E2E 注意事项
HTMX 局部更新后DOM 发生变化但页面 URL 可能不变。等待策略:
HTMX 更新后 URL 可不变,断言前必须等待请求完成:
```python
# 触发 HTMX 请求后等待网络空闲HTMX 请求完成)
page.click('button:has-text("筛选")')
page.wait_for_load_state('networkidle')
# 再断言 DOM 内容
expect(page.locator('.property-list')).to_contain_text('...')
```
### 8.4 E2E 测试数据管理
- E2E 测试使用独立的测试租户(在 `tests/e2e/conftest.py` 中创建)
- 每次 E2E 测试套件运行前,重置测试租户数据至初始种子状态
- 禁止 E2E 测试依赖其他 E2E 测试的产出数据(每条旅程测试自行准备数据)
---
## 9. 测试配置文件
## 十、测试配置基线
### 9.1 pytest.ini
### 10.1 `pytest.ini`
```ini
[pytest]
DJANGO_SETTINGS_MODULE = tests.settings_test
python_files = test_*.py
python_classes = Test*
python_functions = test_*
addopts =
--tb=short
--strict-markers
-q
addopts = --tb=short --strict-markers -q
markers =
unit: 单元测试(不访问数据库)
integration: 集成测试(访问数据库,使用 TenantClient
e2e: E2E 测试(启动真实服务,需要浏览器)
slow: 耗时超过 5 秒的测试
unit
integration
e2e
slow
```
### 9.2 tests/settings_test.py 关键配置
### 10.2 `tests/settings_test.py` 关键
```python
# 继承主 settings覆盖以下配置
DATABASES = {
# 使用独立的测试数据库CI 中由环境变量注入)
}
# Celery 同步模式
CELERY_TASK_ALWAYS_EAGER = True
CELERY_TASK_EAGER_PROPAGATES = True
# 使用内存缓存(避免依赖真实 Redis
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
}
}
# 邮件使用内存后端
EMAIL_BACKEND = 'django.core.mail.backends.locmem.EmailBackend'
# 文件存储使用本地临时目录(非 R2
DEFAULT_FILE_STORAGE = 'django.core.files.storage.FileSystemStorage'
MEDIA_ROOT = '/tmp/fonrey_test_media/'
# 关闭密码哈希加速(测试中不需要高强度 hash
PASSWORD_HASHERS = ['django.contrib.auth.hashers.MD5PasswordHasher']
# 禁用 DEBUG贴近生产环境
DEBUG = False
```
- Celery eager 模式开启
- Cache 使用测试后端locmem/fakeredis
- 邮件使用 locmem backend
- 媒体文件使用临时目录
- `DEBUG=False`(贴近生产)
---
## 10. CI 自动化运行
## 十一、CI 自动化运行
### 10.1 GitHub Actions 配置
### 11.1 触发策略
每日凌晨 2 点自动运行全量测试套件,并在每次 push 到 `main` / `develop` 分支时触发:
- 每日定时全量测试
- `main/develop` 每次 push 触发
```yaml
# .github/workflows/daily-test.yml
name: Daily Test Suite
### 11.2 流水线拆分
on:
schedule:
- cron: '0 18 * * *' # UTC 18:00 = 北京时间次日 02:00
push:
branches: [main, develop]
1. `unit-and-integration`
2. `e2e`(依赖前者成功后执行)
jobs:
unit-and-integration:
name: Unit & Integration Tests
runs-on: ubuntu-latest
services:
postgres:
image: postgres:16
env:
POSTGRES_DB: fonrey_test
POSTGRES_USER: fonrey
POSTGRES_PASSWORD: test_password
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
redis:
image: redis:7-alpine
options: >-
--health-cmd "redis-cli ping"
--health-interval 10s
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.12'
cache: pip
- run: pip install -r requirements/test.txt
- name: Run unit tests
run: pytest tests/unit -m unit --cov=core --cov=apps -q
- name: Run integration tests
run: pytest tests/integration -m integration -q
- name: Upload coverage report
uses: codecov/codecov-action@v4
### 11.3 最低产物
e2e:
name: E2E Tests (Core Journeys)
runs-on: ubuntu-latest
needs: unit-and-integration
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.12'
cache: pip
- run: pip install -r requirements/test.txt
- run: playwright install chromium --with-deps
- name: Run E2E tests
run: pytest tests/e2e -m e2e -q
- name: Upload E2E screenshots on failure
if: failure()
uses: actions/upload-artifact@v4
with:
name: e2e-screenshots
path: tests/e2e/screenshots/
```
### 10.2 本地每日运行
开发机本地运行全量测试:
```bash
# scripts/daily_test.sh
#!/bin/bash
set -e
echo "=============================="
echo " Fonrey Daily Test Runner"
echo "=============================="
echo ""
echo "[1/3] 单元测试..."
pytest tests/unit -m unit -q --tb=short
echo ""
echo "[2/3] 集成测试..."
pytest tests/integration -m integration -q --tb=short
echo ""
echo "[3/3] E2E 核心旅程测试..."
pytest tests/e2e -m e2e -q --tb=short
echo ""
echo "[覆盖率报告]"
pytest tests/unit tests/integration \
--cov=core --cov=apps \
--cov-report=term-missing \
--cov-fail-under=70 \
-q --tb=no
echo ""
echo "=============================="
echo " All tests passed."
echo "=============================="
```
- 覆盖率报告(终端 + 平台上传)
- E2E 失败截图 artifact
---
## 11. AI 辅助编码时的测试要求
## 十二、AI 协作测试要求
在 Fonrey vibe coding 流程中,每次 AI 完成一个 User Story 的功能代码后,**必须同步产出对应测试**,不允许欠测试债。
每个 User Story 实现后,必须同时补齐:
### 11.1 每个 User Story 的测试产出清单
- Factory如缺失
- Service 单元测试(正常 + 至少2个边界
- View/API 集成测试(覆盖全部 AC
- 权限三态与 HTMX/普通请求双形态
AI 完成功能代码后,须产出以下内容(缺一不可)
修复顺序
- [ ] 相关 Model 的 `factory_boy` Factory若尚未存在
- [ ] Service 层的单元测试(正常路径 + 至少 2 个边界/异常场景)
- [ ] View 层的集成测试(覆盖 PRD 验收标准中所有 AC 条目)
- [ ] 权限场景覆盖(有权限 / 无权限 / 未登录,见 §7.3
- [ ] HTMX 局部请求与完整页面请求分别测试(见 §7.2
### 11.2 触发 AI 生成测试的标准 Prompt 模板
```
基于刚才实现的 [US-XXX-NNN],请为其生成完整测试:
1. factory_boy 工厂(如尚未存在)
- 文件位置tests/factories/{app}_factory.py
- 使用 Faker 生成中文假数据
2. Service 层单元测试
- 文件位置tests/unit/test_{app}_service.py
- 覆盖正常路径 + 至少 2 个边界/异常场景
- 使用 pytest-mock mock 外部依赖
3. View 层集成测试
- 文件位置tests/integration/{app}/test_us_{module}.py
- 覆盖 PRD 中该 US 的所有 AC 条目
- 使用 TenantClient 发送请求
- HTMX 请求和普通请求分别覆盖
- 权限场景:有权限 / 无权限403 / 未登录302
所有测试须遵循 TECH_STACK/测试规范.md 中的约定。
租户 fixtures 从 tests/conftest.py 导入,不要重复定义。
```
### 11.3 测试失败时的修复流程
CI 测试失败后AI 修复流程:
1. 读取失败的测试输出(`--tb=short` 格式)
2. 定位失败原因(逻辑错误 / 数据错误 / 环境依赖问题)
3. **优先修复功能代码**(测试是需求的正式表达,不轻易修改测试)
4. 仅当测试本身有误(如 AC 理解错误)时才修改测试,并注明修改原因
5. 修复后本地重跑对应测试套件确认通过,再提交
1. 先修功能代码
2. 仅当测试确实错误才改测试
3. 本地复跑通过后再提交
---
## 12. 禁止项Do NOT
## 十三、禁止项Do NOT
- ❌ 禁止使用 Django 原生 `Client()`,必须使用 `TenantClient`
- ❌ 禁止使用固定等待 `time.sleep()` `page.wait_for_timeout()`
- ❌ 禁止测试直接调用真实外部服务R2、邮件、第三方 API
- 禁止测试之间共享可变数据(避免测试顺序依赖)
- ❌ 禁止在测试中硬编码 Tenant ID、UUID、时间戳
- ❌ 禁止 E2E 测试依赖其他 E2E 测试产出的数据
- ❌ 禁止跳过权限验证场景(无权限 / 未登录场景必须覆盖)
- ❌ 禁止在功能代码未完成时先写空测试(`pass` 占位)后忘记补全
- 禁止 Django 原生 `Client()` 进行租户集成测试
- 禁止固定等待`sleep` / `wait_for_timeout`
- 禁止真实调用外部服务
- 禁止测试之间共享可变数据
- 禁止无权限/未登录场景缺失
- 禁止空测试占位后不补全
---
## 13. 文档索引
## 十四、文档同步规则
| 文档 | 说明 |
|------|------|
| [`TECH_STACK.md`](./TECH_STACK.md) | 技术栈总纲 |
| [`登录管理技术方案.md`](./登录管理技术方案.md) | 登录模块技术细节 |
| [`权限管理系统技术方案.md`](./权限管理系统技术方案.md) | 权限模块技术细节 |
| [`../PRD/TASK.md`](../PRD/TASK.md) | P0 User Story 清单(测试覆盖基准) |
| [`../DATA_MODEL/DATA_MODEL.md`](../DATA_MODEL/DATA_MODEL.md) | 数据模型总览factory 设计参考) |
- 新增/调整 User Story同步 `PRD/TASK.md` 与集成测试映射
- 模块 API 变更:同步对应模块技术方案
- 测试目录变更:同步本文件目录结构与 CI 脚本
- 新增测试基建fixture/工具):同步 `AGENTS.md` 与本文件