name: test-env-setup description: 测试环境自动准备 Skill。在执行任何测试之前,自动检查并准备完整的测试环境:服务健康检查、自动启动后端、数据库就绪、测试账号创建、依赖验证。确保后续测试在稳定环境中执行。触发词:「准备测试环境」「环境预检」「初始化测试环境」「测试前准备」。也被 role-测试工程师 Step 0 和 fixer Step A0/B0 自动调用。
测试环境自动准备(test-env-setup)
使命:让「说一句话就能开始测试」成为现实。 在测试智能体开始执行任何测试用例之前,自动完成所有环境准备工作。 若环境不可修复,输出明确的人工干预指引,不静默失败。
知识导航表
| 层级 | 文档 | 用途 |
|---|---|---|
| D1 项目配置 | 项目群/[项目]/.cursor/test-config.md(若存在) | 项目专属配置:端口/账号/技术栈 |
| D2 技术架构 | 项目群/[项目]/2_技术架构/技术框架.md | 了解服务架构和启动方式 |
| D3 部署文档 | 项目群/[项目]/DEPLOY_ARCH.md | 生产/测试账号信息 |
激活后立即执行
Step 0 【读取项目配置】
先尝试读取 .cursor/test-config.md(项目专属配置)
IF 不存在:使用智能推断(见后文「自动推断逻辑」)
Step 1 【后端服务健康检查 + 自动启动】
1.1 检查服务是否在目标端口响应:
```bash
curl -s --max-time 3 http://localhost:[PORT]/health 2>/dev/null
```
IF 响应正常(含 "ok" 或 HTTP 200):
→ 输出「✅ 后端服务正常(端口 [PORT])」
→ 跳到 Step 2
IF 无响应:
1.2 检查是否有进程占用端口:
```bash
lsof -i :[PORT] 2>/dev/null | head -5
```
IF 有进程但未响应 → kill 并重启:
```bash
pkill -f "uvicorn.*[PORT]" 2>/dev/null || true
pkill -f "node.*[PORT]" 2>/dev/null || true
sleep 2
```
1.3 自动启动服务(根据技术栈):
FastAPI/Python 项目:
```bash
cd [项目根目录]
# 激活虚拟环境(优先 .venv,其次 venv,其次系统 Python)
PYTHON=$([ -f .venv/bin/python ] && echo .venv/bin/python || \
[ -f venv/bin/python ] && echo venv/bin/python || \
echo python3)
# 后台启动,日志写入 /tmp/test-backend.log
nohup $PYTHON -m uvicorn backend.app.main:app \
--host 0.0.0.0 --port [PORT] --workers 1 \
> /tmp/test-backend.log 2>&1 &
echo "启动PID: $!"
```
Node.js/Next.js 项目:
```bash
cd [项目根目录]
nohup npm run dev > /tmp/test-frontend.log 2>&1 &
```
1.4 等待服务就绪(最多 30 秒):
```bash
for i in $(seq 1 10); do
sleep 3
curl -s http://localhost:[PORT]/health && echo "✅ 服务就绪" && break
echo "等待中... ($i/10)"
done
```
IF 30 秒后仍无响应:
→ 读取启动日志诊断:cat /tmp/test-backend.log | tail -20
→ 输出错误信息和诊断建议
→ 停止,输出:「❌ 后端服务无法自动启动,需要人工检查」
→ 附上日志内容供人工排查
Step 2 【数据库连接验证】
```bash
# 通过服务健康端点间接验证 DB
curl -s http://localhost:[PORT]/config-check 2>/dev/null | python3 -c "
import sys, json
d = json.load(sys.stdin)
print('DB:', '✅' if d.get('db_configured') else '❌')
" 2>/dev/null || echo "config-check 端点不可用(跳过DB验证)"
```
IF DB 未连接(db_configured=false):
→ 尝试启动数据库(如有 docker-compose):
```bash
docker-compose -f docker-compose.dev.yml up -d postgres 2>/dev/null || true
sleep 5
```
→ 重新检查
IF 仍然无法连接 → 输出「❌ 数据库连接失败,需要人工检查」
Step 3 【测试账号验证 + 自动创建(完整 Persona 体系)】
需要创建以下 4 类测试人格账号(Personas):
```python
# 测试 Persona 定义(从 test-config.md 读取,以下为默认值)
PERSONAS = [
# admin:管理员,生成邀请码
{"phone": "10000000001", "password": "admin2026",
"role": "admin", "handle": "test_admin", "display_name": "测试管理员"},
# rich:丰富认知库,Proxy 测试目标 + Owner 模式测试
{"phone": "19900000001", "password": "sim2026",
"role": "creator", "handle": "user_rich", "display_name": "认知丰富用户",
"seed_workspace": True, # 需要预装知识库
"visibility": "public_free"}, # 公开供 Proxy 测试
# empty:空 workspace,冷启动/新用户测试
{"phone": "19900000088", "password": "newuser2026",
"role": "creator", "handle": "user_empty", "display_name": "新用户"},
# visitor:访客,专门用于 Proxy 对话 / 消息系统测试
{"phone": "19900000099", "password": "visitor2026",
"role": "user", "handle": "user_visitor", "display_name": "访客测试者"},
]
```
验证并创建缺失账号:
```python
import requests, json
BASE_URL = "http://localhost:[PORT]"
def check_account(phone, password):
r = requests.post(f"{BASE_URL}/auth/login", json={"phone": phone, "password": password})
return r.status_code == 200, r.json() if r.status_code == 200 else None
# 先确保 admin 账号存在(用 SQL 直接创建,绕过邀请码)
admin_exists, _ = check_account("10000000001", "admin2026")
IF not admin_exists:
# 用终端执行 Python 脚本直接写 DB
python -c "
import sys; sys.path.insert(0, 'backend')
from core.db import get_db
from core.auth import create_user, hash_password, create_profile
with get_db() as db:
uid = create_user(db, '10000000001', hash_password('admin2026'))
create_profile(db, uid, role='admin', display_name='测试管理员')
print(f'Admin 创建成功: {uid}')
"
# 用 admin 账号生成邀请码并创建其他账号
_, admin_data = check_account("10000000001", "admin2026")
admin_token = admin_data["access_token"]
r = requests.post(f"{BASE_URL}/admin/invite-codes",
headers={"Authorization": f"Bearer {admin_token}"},
params={"count": 10, "expire_days": 365})
codes = r.json()["codes"]
code_idx = 0
for persona in PERSONAS[1:]: # 跳过 admin
exists, _ = check_account(persona["phone"], persona["password"])
if not exists:
r = requests.post(f"{BASE_URL}/auth/register", json={
"phone": persona["phone"],
"password": persona["password"],
"invite_code": codes[code_idx],
"display_name": persona["display_name"],
"handle": persona["handle"],
})
print(f"注册 {persona['handle']}: {r.status_code}")
code_idx += 1
Step 3.5 【预装知识库(seed_workspace 标记的账号)】
对标记了 seed_workspace=True 的账号(默认:user_rich),
将预制知识库内容复制到其 workspace:
```bash
# 检查 fixture 目录是否存在
FIXTURE_DIR="tests/fixtures/rich_workspace"
if [ ! -d "$FIXTURE_DIR" ]; then
echo "⚠️ Fixture 目录不存在,正在从模板创建..."
mkdir -p "$FIXTURE_DIR/sources"
mkdir -p "$FIXTURE_DIR/docs/L1/产品理论维度"
mkdir -p "$FIXTURE_DIR/docs/L2"
# 创建已知内容的 fixture 文档(用于精确断言)
cat > "$FIXTURE_DIR/sources/认知杠杆论.md" << 'EOF'
# 认知杠杆论
AI时代的核心竞争力是认知结构而非执行效率。
知识管理的本质是建立连接,而非存储。
个人认知结构的稀缺性,决定了它在AI时代的价值。
EOF
cat > "$FIXTURE_DIR/sources/研究方法论.md" << 'EOF'
# 自下而上的系统性研究方法
从具体案例出发,逐步归纳规律,最后升维到原则。
避免一开始就构造大框架的陷阱。
EOF
echo "✅ Fixture 内容已创建"
fi
```
将 fixture 触发 build-cognition(让 AI 真正处理并建立认知结构):
```python
import requests, time
# 登录 rich 账号
_, rich_data = check_account("19900000001", "sim2026")
rich_token = rich_data["access_token"]
rich_ws = rich_data["workspace_id"]
rich_headers = {"Authorization": f"Bearer {rich_token}"}
# 检查是否已有认知内容(避免重复 seed)
r = requests.get(f"{BASE_URL}/doc-registry", headers=rich_headers)
existing_docs = r.json().get("docs", [])
if len(existing_docs) < 3: # 少于3个文档,说明需要 seed
# 上传 fixture 文件
import os
from pathlib import Path
fixture_files = list(Path("tests/fixtures/rich_workspace/sources").glob("*.md"))
files = [("files", (f.name, open(f, "rb"), "text/markdown")) for f in fixture_files]
r = requests.post(f"{BASE_URL}/twin/upload", headers=rich_headers, files=files)
print(f"上传 fixture 文件: {r.status_code}, 文件数: {r.json().get('file_count')}")
# 触发认知构建(通过 SSE 等待完成)
print("⏳ 触发认知构建,等待完成(最多3分钟)...")
with requests.post(f"{BASE_URL}/twin/build-cognition",
headers=rich_headers, stream=True, timeout=180) as r:
for line in r.iter_lines():
if b'"done"' in line or b'build_done' in line:
print("✅ 认知构建完成")
break
else:
print(f"✅ rich 账号已有 {len(existing_docs)} 个文档,跳过 seed")
# 设置 rich 账号的分身为公开(供 Proxy 测试)
requests.post(f"{BASE_URL}/brain-square/visibility",
headers=rich_headers,
json={"visibility": "public_free"})
print("✅ rich 账号设为公开(供 Proxy 测试)")
```
Step 4 【Python 依赖验证】
```bash
cd [项目根目录]
# 验证关键依赖是否安装
$PYTHON -c "import requests, pytest, sseclient" 2>/dev/null && \
echo "✅ Python 依赖齐全" || \
(echo "⚠️ 安装缺失依赖..." && pip install requests pytest sseclient-py 2>&1 | tail -5)
```
Step 5 【最终就绪确认 + 输出报告】
```bash
# 最终健康检查
HEALTH=$(curl -s http://localhost:[PORT]/health 2>/dev/null)
echo "服务状态: $HEALTH"
# 验证两个测试账号均可登录
TOKEN_MAIN=$(curl -s -X POST http://localhost:[PORT]/auth/login \
-H "Content-Type: application/json" \
-d '{"phone":"[TEST_PHONE_MAIN]","password":"[TEST_PASSWORD_MAIN]"}' \
| python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('access_token','FAIL')[:20])")
echo "主账号 Token: $TOKEN_MAIN"
```
输出就绪报告:
「✅ 测试环境就绪
服务状态:✅ localhost:[PORT]/health → ok
数据库:✅ 已连接
主测试账号([TEST_PHONE_MAIN]):✅ 可登录
新用户账号([TEST_PHONE_NEW]):✅ 可登录
Python 依赖:✅ 齐全
准备就绪,可以开始测试。」
自动推断逻辑(无 test-config.md 时)
推断后端端口:
grep -r "port.*810[0-9]\|:810[0-9]" [项目目录]/backend/ 2>/dev/null | head -3
→ 默认端口尝试顺序:8100 → 8000 → 3000
推断技术栈:
IF exists requirements.txt AND exists backend/ → FastAPI/Python
IF exists package.json AND exists pages/ → Next.js
IF exists package.json AND exists src/ → React/Node
推断数据库:
grep DATABASE_URL [项目目录]/.env 2>/dev/null
项目配置文件规范(.cursor/test-config.md)
# 测试环境配置
## 服务
BACKEND_PORT: 8100
HEALTH_ENDPOINT: /health
STARTUP_CMD: python -m uvicorn backend.app.main:app --port 8100 --workers 1
## 测试账号
TEST_PHONE_MAIN: 19900000001
TEST_PASSWORD_MAIN: sim2026
TEST_PHONE_NEW: 19900000088
TEST_PASSWORD_NEW: newuser2026
## 技术栈
LANGUAGE: python
TEST_CMD: python -m pytest tests/ -x -v --timeout=120
VENV_PATH: .venv
## P2 处理
P2_HANDLING: skip # skip / fix_all / ask
日志驱动断言工具(测试必用)
产品天然对 AI 透明:
/logs端点暴露所有工具调用、节点状态、SSE 事件。 用这些做断言,比「检查 AI 回答了什么文字」可靠 10 倍。
import requests
BASE_URL = "http://localhost:[PORT]"
def get_session_logs(headers, session_id=None):
"""获取当前 session 的完整操作日志"""
r = requests.get(f"{BASE_URL}/logs", headers=headers)
logs = r.json() if r.status_code == 200 else []
if session_id:
logs = [l for l in logs if l.get("session_id") == session_id]
return logs
def clear_logs(headers):
"""清空日志(每条 Layer 1 测试前调用,防止干扰)"""
requests.delete(f"{BASE_URL}/logs", headers=headers)
def assert_tool_called(logs, tool_name, args_contains=None):
"""验证某个工具被调用(不管 AI 输出了什么)"""
for log in logs:
if log.get("tool") == tool_name or tool_name in str(log.get("action", "")):
if args_contains is None:
return True
if args_contains in str(log.get("args", "")):
return True
return False
def assert_workflow_completed(events):
"""验证工作流完整执行完毕"""
event_types = [e.get("type") for e in events if isinstance(e, dict)]
return "workflow_done" in event_types
def assert_proposal_created(workspace_path):
"""验证工作流真的创建了 Proposal(文件系统状态断言)"""
from pathlib import Path
proposals = list(Path(workspace_path) / "proposals").glob("p-*.json"))
return len(proposals) > 0
def assert_doc_modified(workspace_path, doc_pattern, after_timestamp):
"""验证某文档在测试操作后被修改"""
from pathlib import Path
import os
for doc in Path(workspace_path).rglob(doc_pattern):
if os.path.getmtime(doc) > after_timestamp:
return True
return False
def count_l2_fragments(workspace_path):
"""统计 L2 碎片数量(认知积累断言)"""
from pathlib import Path
return len(list((Path(workspace_path) / "docs/L2").glob("*.md")))
断言优先级
1. 文件系统状态(最可靠):Proposal 出现了吗?L2 有新文件吗?
2. 日志工具调用:read_skill 被调用了吗?write_file 被调用了吗?
3. SSE 事件序列:workflow_done 在所有 node_update 之后吗?
4. 接口响应结构:返回了正确的字段吗?
5. 文字内容(最后手段):仅在格式强制要求时(如第三人称检查)
失败处理(人工干预指引)
| 情况 | 自动尝试 | 无法自动处理时的指引 |
|---|---|---|
| 后端服务无法启动 | kill+restart × 3 | 「查看 /tmp/test-backend.log,检查端口冲突或环境变量」 |
| 数据库无法连接 | 尝试 docker-compose up | 「检查 .env 的 DATABASE_URL,确认 PostgreSQL 在运行」 |
| 测试账号无法创建 | 尝试管理员 API 创建 | 输出手动创建 SQL |
| 依赖缺失 | pip install | 「运行 pip install -r requirements.txt」 |
变更记录
v1.0 — 2026-03-24 — 初始创建
根因:Agent 拥有完整终端权限,但之前测试流程的「第一步」仍是人工确认环境就绪。 需要一个专门的 Skill 把环境准备工作完全自动化,让「说一句话就能开始测试」成为现实。
设计决策:
- 5步串行(健康检查→DB→账号→依赖→就绪确认)
- 每步有自动修复逻辑,修复3次失败才报告人工干预
- 支持 .cursor/test-config.md 项目配置,无配置时智能推断
- 输出就绪报告,让调用方知道环境状态
验证状态:🔵 待验证