这个 Skill 解决什么具体问题
每次写单元测试都像在重复劳动——分析代码、构思用例、手写断言。更烦的是,不同项目风格不同,团队标准总得时刻记在脑子里。
你需要的是一个可复用的 AI 技能:把“写测试”这件事拆成稳定步骤,封装成一份 SKILL.md,让 agent 每次执行时输出一样质量的结果。
Superpowers 项目的核心就是这个思路:用结构化描述定义 agent 的能力边界和工作流程,不再靠零散的 prompt 碰运气。
触发条件和适用场景
- 触发条件:你提交了一个
.py文件,或者手动调用@skill test-gen。 - 适用场景:
- 新功能开发后需要测试覆盖
- 重构前需要保住现有行为
- 代码审查时快速补测试
不适用的场景:UI 测试(需要浏览器驱动)、大型遗留系统(上下文超出 token 限制)。
完整 Skill 结构(SKILL.md 示例)
Superpowers 的方法论鼓励把技能写成 SKILL.md,包含 trigger、role、steps、output schema。下面是让你直接复用的模板:
# Skill: test-gen
## Trigger
当用户要求“生成测试”或提交代码时触发。
## Role
你是一个资深测试工程师,熟悉 pytest,遵循 Given-When-Then 风格。
## Steps
1. 读取目标文件,识别所有函数和类方法。
2. 对每个公共函数:
- 确定输入参数类型和返回值类型
- 列出至少 3 个边界值
- 写一个参数化测试用例(pytest.mark.parametrize)
3. 对类方法:模拟外部依赖(mock),只测业务逻辑。
4. 输出为 `test_<module>.py`,遵循项目现有的 conftest 配置。
## Output Schema
- 文件名:`test_<original_file>`
- 文件内容:完整的 pytest 代码,包含必要的 import
为什么这样写有效? 关键是 Role + Steps + Output Schema 三位一体。Role 限定行为边界,Steps 分解原子操作,Output Schema 固定结果格式。差 Prompt 经常缺一,导致输出不稳定。
差 Prompt vs 好 Prompt
差 Prompt(直接问):
给下面代码写测试:
def add(a, b):
return a + b
输出结果可能是单边断言、缺少 import、或者连参数类型都不考虑。
好 Prompt(基于 SKILL.md 的触发指令):
@skill test-gen
目标代码:
def divide(a, b):
if b == 0:
raise ValueError("b cannot be zero")
return a / b
输出的完整测试:
import pytest
from my_module import divide
@pytest.mark.parametrize("a,b,expected_exception", [
(10, 2, None), # 正常
(0, 5, None), # 零被除数
(5, 0, ValueError), # 除数为零
(-1, 2, None), # 负数
])
def test_divide(a, b, expected_exception):
if expected_exception:
with pytest.raises(expected_exception):
divide(a, b)
else:
assert divide(a, b) == a / b
对比效果:差 prompt 几乎不会主动列边界值,好 prompt 因为 Steps 里写了“至少 3 个边界值”,agent 自然会覆盖零值、负数、异常。这就是结构化流程的力量。
实际案例演示
假设你有一个 user_service.py,包含一个注册方法:
class UserService:
def register(self, email, password):
if len(password) < 8:
raise ValueError("Password too short")
if "@" not in email:
raise ValueError("Invalid email")
# ...save to DB...
return User(email)
触发 @skill test-gen 后,agent 依据 SKILL.md 的步骤输出:
import pytest
from unittest.mock import MagicMock, patch
from user_service import UserService
class TestUserService:
@pytest.mark.parametrize("email,password,expected", [
("test@example.com", "pass1234", None), # 正常
("test@example.com", "short", ValueError), # 密码太短
("noatsign.com", "password123", ValueError), # 邮箱无效
("", "password123", ValueError), # 空邮箱
])
def test_register(self, email, password, expected):
svc = UserService()
with patch.object(svc, '_save') as mock_save:
if expected:
with pytest.raises(expected):
svc.register(email, password)
mock_save.assert_not_called()
else:
result = svc.register(email, password)
assert result.email == email
mock_save.assert_called_once()
注意里的 patch.object(svc, '_save') —— 这来自 Steps 中“模拟外部依赖”的指令。差 prompt 基本不会主动 mock 数据库。
复用和组合技巧
- 绑定到 Git Hook:在
pre-push钩子中调用@skill test-gen,对变更文件自动生成测试。 - 组合技能:先
@skill code-review审查代码,再@skill test-gen生成测试,最后@skill lint-fix格式化。把三个 SKILL.md 用 workflow 串联。 - 微调输出风格:在 SKILL.md 的 Output Schema 中增加
## Code Style段落,指定团队约定(如 pytest 使用 plain assert 还是 unittest)。 - 变体用法:将 SKILL.md 中的 Role 改为“初级开发”,Steps 减少到只生成 happy path 测试,用于快速覆盖率摸底。
# .superworks/skills/test-gen-lite/skill.yaml
trigger: "@skill test-lite"
role: 初级开发
steps:
- 对每个函数写一个最简测试(仅正常路径)
- 只使用 assert 单值,不用 parametrize
这个轻量版可以配合 CI 的 pytest --cov 快速提升代码覆盖率,减少阻塞。
一句话收尾:别每次都从头写 prompt,把重复逻辑封装进 SKILL.md,让 AI 变成你的稳定外包队员。