plugins/languages/python/skills/testing/SKILL.md
Python 测试规范 (pytest 8.x)。涵盖 fixture/parametrize/mark、pytest-asyncio 异步测试、hypothesis 属性测试、覆盖率 (pytest-cov)、mock 策略、TDD 流程。在编写单元/集成测试、修复测试失败、提升覆盖率、配置 conftest.py 时使用。也触发于"写测试"、"pytest"、"测试覆盖率"、"mock"、"fixture"。
npx skillsauth add lazygophers/ccplugin python-testingInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
pytest 8.x + pytest-asyncio + hypothesis + pytest-cov。不用 unittest.TestCase 风格。
tests/
├── conftest.py # 全局 fixture
├── unit/
│ ├── test_models.py
│ └── test_services.py
├── integration/
│ ├── conftest.py # 集成层 fixture (db, app)
│ └── test_api.py
└── e2e/
└── test_workflows.py
pyproject.toml:
[tool.pytest.ini_options]
testpaths = ["tests"]
addopts = "-ra --strict-markers --strict-config"
asyncio_mode = "auto"
markers = [
"slow: 慢测试 (跑前需 -m slow)",
"integration: 需要外部服务",
]
[tool.coverage.run]
source = ["src"]
branch = true
[tool.coverage.report]
fail_under = 80
show_missing = true
每个测试 = Arrange / Act / Assert 三段:
def test_calculate_average_normal_case():
# Arrange
numbers = [1.0, 2.0, 3.0, 4.0, 5.0]
# Act
result = calculate_average(numbers)
# Assert
assert result == 3.0
测试函数名: test_<被测对象>_<场景>_<期望>。一个测试只验证一件事。
# tests/conftest.py
import pytest
from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine
@pytest.fixture
def sample_user() -> dict:
return {"username": "alice", "email": "[email protected]"}
@pytest.fixture(scope="session")
async def db_engine():
engine = create_async_engine("sqlite+aiosqlite:///:memory:")
async with engine.begin() as conn:
await conn.run_sync(Base.metadata.create_all)
yield engine
await engine.dispose()
@pytest.fixture
async def db_session(db_engine) -> AsyncSession:
async with AsyncSession(db_engine) as session:
yield session
await session.rollback()
scope 选择: function (默认, 隔离最强) > module > session。共享开销大的资源用 session, 易变状态用 function。
数据驱动多个用例, 避免循环:
@pytest.mark.parametrize("numbers, expected", [
([1], 1.0),
([1, 2, 3], 2.0),
([0, 0, 0], 0.0),
], ids=["single", "triple", "zeros"])
def test_average_parametrized(numbers, expected):
assert calculate_average(numbers) == expected
def test_empty_list_raises():
with pytest.raises(ValueError, match="cannot be empty"):
calculate_average([])
不要只断 pytest.raises(Exception), 写具体异常类型 + match 验证消息。
asyncio_mode = "auto" 后, 异步函数自动识别为异步测试:
async def test_fetch_user(async_client):
resp = await async_client.get("/users/1")
assert resp.status_code == 200
assert resp.json()["id"] == 1
FastAPI 集成测试用 httpx.AsyncClient + ASGITransport, 不用 TestClient (后者是同步的):
@pytest.fixture
async def async_client():
transport = httpx.ASGITransport(app=app)
async with httpx.AsyncClient(transport=transport, base_url="http://test") as c:
yield c
优先级: 真实对象 > fake 实现 > mock 部分 > mock 全部。
from unittest.mock import patch, AsyncMock
# 在边界 mock (HTTP / 时间 / 随机), 不要 mock 业务对象
def test_send_email_calls_smtp(mocker):
mock_smtp = mocker.patch("myapp.notifier.smtplib.SMTP")
send_email("[email protected]", "hi")
mock_smtp.return_value.sendmail.assert_called_once()
# 异步 mock
async def test_external_api(mocker):
mock = mocker.patch("myapp.client.httpx.AsyncClient.get", new_callable=AsyncMock)
mock.return_value.json.return_value = {"id": 1}
result = await fetch_user(1)
assert result.id == 1
不要 mock 自己写的纯逻辑函数 (改用真实调用)。
枚举测不到的边界, 让 hypothesis 自动生成:
from hypothesis import given, strategies as st
@given(st.lists(st.integers()))
def test_reverse_twice_is_identity(items):
assert list(reversed(list(reversed(items)))) == items
@given(st.text(min_size=1))
def test_username_validation_never_crashes(name):
# 不该抛任何未预期异常
try:
UserCreate(username=name, email="[email protected]", age=20)
except ValidationError:
pass
适合: 序列化往返、数学性质、不变量。不适合: 业务流程编排。
uv run pytest --cov=src --cov-branch --cov-report=term-missing
目标:
不要先写实现再补测试 (覆盖率高但测不到真实行为)。
time.sleep() 等待 (用 freezegun 或事件机制)parametrize)assert result == True (写 assert result)test_1, test_works (描述场景 + 期望)tools
--- name: trellisx-workspace description: 维护 `.trellis/task.md` 任务看板 —— trellis 缺的跨任务总览。**一个表格, 一行一个任务**, 列为 id/名称/描述/状态/阶段/进度/worktree (状态/阶段中文显示)。在 task create/start/阶段切换/archive 后**及时更新**对应行; 并**自动清理超 7 天的已完成行**防膨胀。保持看板与 task.json 实时一致。 when_to_use: 维护 / 创建 / 更新 `.trellis/task.md` 任务看板时; task 生命周期任一节点 (create/start/阶段推进/archive) 之后同步看板时; 用户问"当前有哪些任务 / 任务进度 / 任务看板"时。被 trellisx-flow 与 trellisx-apply 注入的流程引用。 user-invocable: true argument-hint: [show|update|sync|cleanup ...] [task id] arguments:
testing
强制以 Trellis task 闭环处理用户指定的请求 (自判新建/并入 → plan→exec→check→finish 全程不跳步)。**仅用户显式主动调用** (/trellisx-flow 或明确要求"强制走 task 处理这个"); **禁止自动 / 被动 / 推断式调用** —— 不要因为某个请求"看起来该建 task"就自动触发本 skill, 那是 apply 注入的 no_task 倾向的职责。
testing
把 强推task + subtask拆分 + worktree隔离 + 闭环收尾 四维度增量注入当前项目 .trellis/ (workflow.md 的 no_task/planning/in_progress 块 + spec 背书文档 + trellis 生命周期 hook worktree 自动化)。强推 task 与闭环为纯 prompt 软约束 (非平台 hook 硬拦截)。**纯增量追加, 绝不替换 trellis 原生文本** (no_task 分类+征同意/check/finish/前缀全保留)。幂等 (marker 包裹)。
development
Claude Code 会话历史整理 — 扫 ~/.claude/projects/**/*.jsonl 全部 session transcripts, 提取学习增量 (用户校正/决策/踩坑/L0 规则) → 全局记忆库 ~/.cortex/.wiki/memory/. 默认 --apply 落盘 (--dry-run opt-in 仅出 JSON plan 预览). 与 cortex-extract (L4-inbox 内部) 互补.