第一次听说Spec-Kit时,我正在为一个简单的天气查询API焦头烂额。代码越写越乱,功能越加越多,最后连我自己都搞不清楚哪些边界条件测试过,哪些没有。同事推荐说:"试试Spec-Kit吧,它能逼着你按规矩来。"作为一个对测试驱动开发(TDD)既向往又困惑的开发者,我决定亲身尝试这个号称能"强制先生成测试用例"的工具,记录下这段从抗拒到接受的转变历程。
安装过程比想象中顺利。按照官方文档,我用了以下命令初始化项目:
bash复制pip install uv
uvx --from git+https://github.com/github/spec-kit.git specify init weather-api
启动Claude Code后,我迫不及待地输入了第一个命令:
code复制/specify 开发一个天气查询API,能够根据城市名称返回当前温度、天气状况和湿度
Spec-Kit立即生成了一个详细的spec.md文件,列出了用户故事、成功标准和边界条件。但当我直接尝试写业务代码时,工具毫不留情地阻止了我:
错误:未检测到测试文件。请先完成测试用例生成步骤。
这种强制约束让我这个习惯先写代码后补测试的开发者感到不适。下表对比了我习惯的工作流与Spec-Kit强制要求的工作流:
| 传统工作流 | Spec-Kit工作流 |
|---|---|
| 直接编写业务逻辑 | 必须先定义规格文档 |
| 功能完成后补测试 | 测试用例是实现的准入条件 |
| 边界条件靠后期排查 | 边界条件在spec阶段明确定义 |
| 重构时测试可能滞后 | 任何代码变更都需要对应测试更新 |
被迫先生成测试后,我遇到了第一个挑战:AI生成的测试用例看起来合理吗?初始生成的测试文件包含以下内容:
python复制def test_get_weather_by_city():
"""测试通过城市名称获取天气数据"""
result = get_weather("北京")
assert "temperature" in result
assert "conditions" in result
assert "humidity" in result
这些测试看似全面,但存在几个问题:
通过不断与AI交互,我学会了更精确地表达需求:
code复制/specify 当查询不存在的城市时,应返回404状态码和错误信息。湿度值应为0-100之间的整数。
改进后的测试用例明显更完善:
python复制def test_invalid_city_returns_404():
"""测试查询不存在城市时的响应"""
with pytest.raises(WeatherNotFoundError) as excinfo:
get_weather("不存在的城市")
assert excinfo.value.status_code == 404
assert "not found" in str(excinfo.value).lower()
def test_humidity_range():
"""测试湿度值在有效范围内"""
result = get_weather("上海")
assert isinstance(result["humidity"], int)
assert 0 <= result["humidity"] <= 100
这个过程中,我总结出与AI协作的几个关键技巧:
当测试用例终于通过审查,AI生成的初始实现代码却无法编译。典型的错误包括:
传统工作流中,我可能会直接修改代码使其通过编译。但Spec-Kit强制要求保持测试不变,通过AI迭代修复实现。例如,当遇到缺失的异常类时,我这样与AI交互:
code复制错误:NameError: name 'WeatherNotFoundError' is not defined
请按照测试要求实现WeatherNotFoundError异常类,包含status_code属性和适当的错误信息
AI随后生成了符合要求的异常类实现:
python复制class WeatherNotFoundError(Exception):
"""当查询的城市不存在时抛出此异常"""
def __init__(self, city):
self.status_code = 404
self.message = f"Weather data for {city} not found"
super().__init__(self.message)
这种"测试先行"的约束虽然初期令人烦躁,但确实带来了意想不到的好处:
使用Spec-Kit两周后,我发现自己编码习惯发生了微妙而深刻的变化:
问题发现提前:以前常在集成阶段才发现接口不匹配,现在在spec阶段就能暴露设计缺陷。最近一个项目中,我原本设计的缓存接口是:
python复制def get_cached_weather(city: str) -> dict:
但在写测试时意识到,返回原始字典会导致客户端与实现细节耦合,于是改为:
python复制def get_cached_weather(city: str) -> WeatherData:
代码结构改善:因为要先写测试,自然倾向于编写:
调试时间减少:虽然初始开发时间可能增加,但后期调试时间大幅下降。下表对比了天气API项目不同阶段的时间分配:
| 阶段 | 传统方式 | Spec-Kit方式 |
|---|---|---|
| 初始开发 | 8小时 | 12小时 |
| 调试修复 | 15小时 | 3小时 |
| 后续迭代 | 10小时 | 5小时 |
团队协作改进:规格文档(spec.md)成为团队共享的"唯一真相源",新人加入时能快速理解:
经过一个月的实践,我总结出这些让Spec-Kit发挥最大价值的方法:
合理设置测试粒度:不是所有代码都需要同等程度的测试覆盖。我的优先级排序是:
活用AI提示技巧:当AI生成的测试不够理想时,尝试:
管理测试数据:建立可维护的测试数据集,例如:
python复制# tests/test_data.py
VALID_CITIES = ["北京", "上海", "广州"]
INVALID_CITIES = ["不存在的城市", "", "New York"]
平衡严格性与灵活性:虽然Spec-Kit强制TDD,但遇到以下情况可以适当变通:
在天气API项目中,最让我惊喜的是当需求变更为"支持按经纬度查询"时,得益于良好的测试覆盖,重构过程异常顺利。我只需要:
最终项目结构保持清晰,核心逻辑的测试覆盖率维持在95%以上。这让我真正体会到"写测试不是负担,而是投资"的含义。