文档修改
This commit is contained in:
@@ -54,15 +54,15 @@
|
||||
```
|
||||
fonrey/
|
||||
├── apps/
|
||||
│ ├── tenants/ # django-tenants 配置(shared_apps)
|
||||
│ ├── accounts/ # 登录认证(详见 登录管理技术方案.md)
|
||||
│ ├── permissions/ # 权限管理(详见 权限管理系统技术方案.md)
|
||||
│ ├── tenant/ # django-tenants 配置(shared_apps)
|
||||
│ ├── account/ # 登录认证(详见 登录管理技术方案.md)
|
||||
│ ├── permission/ # 权限管理(详见 权限管理系统技术方案.md)
|
||||
│ ├── org/ # 组织人事(org_units, staff)
|
||||
│ ├── region/ # 区域管理(districts, business_areas, metro)
|
||||
│ ├── complex/ # 楼盘管理(complexes, buildings, schools)
|
||||
│ ├── property/ # 房源核心(含 models/services/tasks 三层)
|
||||
│ ├── client/ # 客源管理
|
||||
│ ├── settings/ # 系统设置(lookup, tags)
|
||||
│ ├── setting/ # 系统设置(lookup, tags)
|
||||
│ └── release/ # 客户端发布管理(shared_apps)
|
||||
├── shared/ # 公共 Schema App
|
||||
└── core/
|
||||
@@ -144,10 +144,51 @@ apps/property/
|
||||
|
||||
---
|
||||
|
||||
## 9. 文档维护原则
|
||||
## 9. 测试策略
|
||||
|
||||
> **完整测试规范**见:[`测试规范.md`](./测试规范.md)。本节仅列关键结论。
|
||||
|
||||
Fonrey 采用 AI vibe coding 模式开发,测试是保证每日迭代质量的唯一安全网。**每个 P0 User Story 完成后,对应测试必须同步产出,不允许欠测试债。**
|
||||
|
||||
### 测试分层
|
||||
|
||||
| 层级 | 工具 | 覆盖目标 | 运行频率 |
|
||||
|------|------|---------|---------|
|
||||
| **单元测试** | `pytest-django` + `factory_boy` | `core/`、`services/`、`tasks.py` | 每次 push |
|
||||
| **集成测试** | `pytest-django` TenantClient | 所有 P0 User Story 的 HTTP 接口 | 每次 push |
|
||||
| **E2E 测试** | `playwright` (Python) | 5 条核心用户旅程 | 每日定时 |
|
||||
|
||||
### 关键约定
|
||||
|
||||
- 所有集成测试必须使用 `django-tenants` 的 `TenantClient`,禁止使用 Django 原生 `Client()`
|
||||
- HTMX 局部请求测试须携带 `HTTP_HX_REQUEST: true` header,并验证返回局部 HTML 而非完整页面
|
||||
- Celery 任务测试使用 `CELERY_TASK_ALWAYS_EAGER = True` 同步执行
|
||||
- 外部服务(R2、Redis、邮件)在测试中全部 Mock,禁止真实调用
|
||||
- 每个受权限保护的 View,必须覆盖:有权限(200)、无权限(403)、未登录(302)三个场景
|
||||
|
||||
### 覆盖率基准
|
||||
|
||||
| 模块 | 最低目标 |
|
||||
|------|---------|
|
||||
| `core/` 核心基础模块 | ≥ 90% |
|
||||
| `apps/*/services/` 业务逻辑层 | ≥ 80% |
|
||||
| `apps/*/views.py` 视图层 | ≥ 70% |
|
||||
| E2E 核心用户旅程(5 条) | 100% 通过 |
|
||||
|
||||
### CI 自动化
|
||||
|
||||
- 每次 push 到 `main` / `develop` 自动运行单元测试 + 集成测试
|
||||
- 每日北京时间凌晨 2 点自动运行全量套件(含 E2E)
|
||||
- 配置文件:`.github/workflows/daily-test.yml`
|
||||
|
||||
---
|
||||
|
||||
## 10. 文档维护原则
|
||||
|
||||
- 本文档仅记录**跨模块共识**与**模块索引**,不展开模块细节
|
||||
- 模块技术方案在子文档中维护,并通过 §8 表格回链
|
||||
- 任何技术栈变更(替换组件、升级主版本、新增外部服务)须同步更新本文档 §2、§5、§6
|
||||
- 新增模块时,先在 §4 目录结构补位,再在 §8 索引登记子文档
|
||||
- 测试规范变更须同步更新 §9 关键结论,完整细节在 [`测试规范.md`](./测试规范.md) 中维护
|
||||
|
||||
|
||||
|
||||
611
Project/fonrey/TECH_STACK/测试规范.md
Normal file
611
Project/fonrey/TECH_STACK/测试规范.md
Normal file
@@ -0,0 +1,611 @@
|
||||
# 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. 测试目标
|
||||
|
||||
Fonrey 采用 AI vibe coding 模式开发,AI 负责生成功能代码,**测试是保证每日迭代质量的唯一安全网**。测试体系须满足:
|
||||
|
||||
- 每个 P0 User Story 完成后,对应测试同步产出
|
||||
- 每日自动运行全量测试套件,输出可读报告
|
||||
- 测试失败时,AI 可根据报告自主定位并修复问题
|
||||
- 测试环境与生产环境技术栈完全一致(同样使用 PostgreSQL + django-tenants)
|
||||
|
||||
**覆盖率基准目标**:
|
||||
|
||||
| 层级 | 最低目标 |
|
||||
|------|---------|
|
||||
| `core/` 核心基础模块 | ≥ 90% |
|
||||
| `apps/*/services/` 业务逻辑层 | ≥ 80% |
|
||||
| `apps/*/views.py` 视图层 | ≥ 70% |
|
||||
| `apps/*/tasks.py` 异步任务 | ≥ 70% |
|
||||
| E2E 核心用户旅程 | 5 条必须全部通过 |
|
||||
|
||||
---
|
||||
|
||||
## 2. 测试分层架构
|
||||
|
||||
Fonrey 采用三层测试体系,从底层向上覆盖:
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────┐
|
||||
│ E2E 测试(用户行为模拟) │ ← Playwright
|
||||
│ 覆盖:5 条核心用户旅程 │
|
||||
├─────────────────────────────────────────┤
|
||||
│ 集成测试(API / View 层) │ ← pytest-django TenantClient
|
||||
│ 覆盖:所有 P0 User Story 的 HTTP 接口 │
|
||||
├─────────────────────────────────────────┤
|
||||
│ 单元测试(逻辑单元) │ ← pytest-django + factory_boy
|
||||
│ 覆盖:core/、services/、tasks.py │
|
||||
└─────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**三层分工原则**:
|
||||
|
||||
- **单元测试**:不启动 HTTP server,不依赖浏览器,速度最快;测试单一函数/类的逻辑正确性
|
||||
- **集成测试**:使用 Django 测试客户端,验证完整请求-响应链路(View → Service → DB);不启动真实 HTTP server
|
||||
- **E2E 测试**:启动真实 Django dev server,用浏览器驱动验证真实用户操作流程;速度最慢,只覆盖核心旅程
|
||||
|
||||
---
|
||||
|
||||
## 3. 工具选型
|
||||
|
||||
### 3.1 工具清单
|
||||
|
||||
| 类型 | 工具 | 版本 | 用途 |
|
||||
|------|------|------|------|
|
||||
| 测试框架 | `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
|
||||
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 专用 fixtures(live_server、page)
|
||||
├── 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 隔离在测试中必须正确处理,否则测试结果不可信。
|
||||
|
||||
### 5.1 核心原则
|
||||
|
||||
- **所有集成测试和单元测试**(涉及数据库的)必须在租户 Schema 上下文中执行
|
||||
- 严禁在 `public` Schema 下直接操作业务数据
|
||||
- 每个测试函数执行后,数据库状态自动回滚(`pytest-django` 的 `db` fixture 保证事务隔离)
|
||||
- 禁止测试之间共享可变状态(禁止 `module` 或 `session` 级别的数据库 fixtures,除非明确只读)
|
||||
|
||||
### 5.2 租户 Fixture 规范
|
||||
|
||||
全局 `conftest.py` 必须提供以下标准 fixtures:
|
||||
|
||||
```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 统一管理
|
||||
- ❌ 禁止跨租户数据访问断言(每个测试只能操作自己的租户数据)
|
||||
|
||||
---
|
||||
|
||||
## 6. 单元测试规范
|
||||
|
||||
### 6.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` |
|
||||
|
||||
### 6.2 factory_boy 规范
|
||||
|
||||
每个 Django Model 必须有对应的 Factory,集中放在 `tests/factories/` 下:
|
||||
|
||||
- Factory 类名统一为 `{ModelName}Factory`
|
||||
- 使用 `faker` 生成中文假数据(姓名、手机号、地址)
|
||||
- 手机号字段必须使用未加密的明文值传入 factory(factory 内部触发 Model 的加密逻辑)
|
||||
- Factory 之间通过 `SubFactory` 表达依赖关系,禁止在 Factory 内部硬编码 ID
|
||||
|
||||
### 6.3 Celery 任务测试规范
|
||||
|
||||
所有 Celery 任务测试必须在同步模式下运行,在 `settings_test.py` 中配置:
|
||||
|
||||
```python
|
||||
# tests/settings_test.py
|
||||
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(...)
|
||||
```
|
||||
|
||||
### 6.4 PII 加密测试要求
|
||||
|
||||
`test_encryption.py` 必须覆盖以下场景:
|
||||
|
||||
- 加密后的密文与明文不同
|
||||
- 相同明文每次加密产生不同密文(GCM nonce 随机性)
|
||||
- 解密后的明文与原始明文完全一致
|
||||
- 加密字段的 SHA-256 hash 索引值具有确定性(相同明文产生相同 hash)
|
||||
- 解密错误(篡改密文)抛出可识别异常
|
||||
|
||||
---
|
||||
|
||||
## 7. 集成测试规范
|
||||
|
||||
### 7.1 适用范围
|
||||
|
||||
集成测试覆盖完整的 HTTP 请求-响应链路,每个 P0 User Story 至少对应一个集成测试文件。
|
||||
|
||||
### 7.2 HTMX 请求约定
|
||||
|
||||
Fonrey 的 View 层分为两种响应模式,测试必须对应覆盖:
|
||||
|
||||
| 请求类型 | Header | 预期响应 |
|
||||
|---------|--------|---------|
|
||||
| 普通页面请求 | 无 | 完整 HTML(含 `<html>`, `<head>`, `<body>`) |
|
||||
| HTMX 局部请求 | `HTTP_HX_REQUEST: true` | 局部 HTML 片段(不含完整页面结构) |
|
||||
|
||||
HTMX 请求在 `TenantClient` 中发送方式:
|
||||
|
||||
```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-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` |
|
||||
| US-PROPERTY-001~008 | `tests/integration/property/test_us_property.py` |
|
||||
| US-CLIENT-001~017 | `tests/integration/client/test_us_client.py` |
|
||||
| US-ORG-001~003 | `tests/integration/org/test_us_org.py` |
|
||||
| US-SETTING-001 | `tests/integration/setting/test_us_setting.py` |
|
||||
|
||||
### 7.5 外部服务 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) |
|
||||
|
||||
---
|
||||
|
||||
## 8. E2E 测试规范
|
||||
|
||||
### 8.1 适用范围与原则
|
||||
|
||||
E2E 测试成本高、速度慢,**只覆盖核心用户旅程,不追求全覆盖**。以下 5 条旅程为必须通过项,任意一条失败即视为阻塞级问题:
|
||||
|
||||
| # | 旅程名称 | 对应模块 |
|
||||
|---|---------|---------|
|
||||
| J-01 | 登录 → 进入首页 | 登录管理 |
|
||||
| J-02 | 录入房源 → 上传照片 → 查看列表 | 房源管理 |
|
||||
| J-03 | 录入客源 → 添加跟进记录 | 客源管理 |
|
||||
| J-04 | 无权限员工访问受限页面 → 看到 403 提示 | 权限管理 |
|
||||
| J-05 | 管理员创建员工 → 分配角色 → 新员工登录验证 | 组织人事 + 权限 |
|
||||
|
||||
### 8.2 Playwright 技术约定
|
||||
|
||||
- 浏览器:仅使用 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`,获得更清晰的错误输出
|
||||
|
||||
### 8.3 HTMX 页面的 E2E 注意事项
|
||||
|
||||
HTMX 局部更新后,DOM 发生变化但页面 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
|
||||
|
||||
```ini
|
||||
[pytest]
|
||||
DJANGO_SETTINGS_MODULE = tests.settings_test
|
||||
python_files = test_*.py
|
||||
python_classes = Test*
|
||||
python_functions = test_*
|
||||
addopts =
|
||||
--tb=short
|
||||
--strict-markers
|
||||
-q
|
||||
markers =
|
||||
unit: 单元测试(不访问数据库)
|
||||
integration: 集成测试(访问数据库,使用 TenantClient)
|
||||
e2e: E2E 测试(启动真实服务,需要浏览器)
|
||||
slow: 耗时超过 5 秒的测试
|
||||
```
|
||||
|
||||
### 9.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
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 10. CI 自动化运行
|
||||
|
||||
### 10.1 GitHub Actions 配置
|
||||
|
||||
每日凌晨 2 点自动运行全量测试套件,并在每次 push 到 `main` / `develop` 分支时触发:
|
||||
|
||||
```yaml
|
||||
# .github/workflows/daily-test.yml
|
||||
name: Daily Test Suite
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 18 * * *' # UTC 18:00 = 北京时间次日 02:00
|
||||
push:
|
||||
branches: [main, develop]
|
||||
|
||||
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
|
||||
|
||||
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 "=============================="
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 11. AI 辅助编码时的测试要求
|
||||
|
||||
在 Fonrey vibe coding 流程中,每次 AI 完成一个 User Story 的功能代码后,**必须同步产出对应测试**,不允许欠测试债。
|
||||
|
||||
### 11.1 每个 User Story 的测试产出清单
|
||||
|
||||
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. 修复后本地重跑对应测试套件确认通过,再提交
|
||||
|
||||
---
|
||||
|
||||
## 12. 禁止项(Do NOT)
|
||||
|
||||
- ❌ 禁止使用 Django 原生 `Client()`,必须使用 `TenantClient`
|
||||
- ❌ 禁止使用固定等待 `time.sleep()` 或 `page.wait_for_timeout()`
|
||||
- ❌ 禁止测试直接调用真实外部服务(R2、邮件、第三方 API)
|
||||
- ❌ 禁止测试之间共享可变数据(避免测试顺序依赖)
|
||||
- ❌ 禁止在测试中硬编码 Tenant ID、UUID、时间戳
|
||||
- ❌ 禁止 E2E 测试依赖其他 E2E 测试产出的数据
|
||||
- ❌ 禁止跳过权限验证场景(无权限 / 未登录场景必须覆盖)
|
||||
- ❌ 禁止在功能代码未完成时先写空测试(`pass` 占位)后忘记补全
|
||||
|
||||
---
|
||||
|
||||
## 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 设计参考) |
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user