关于 tdd-guide
面向开发者的测试驱动开发技能,提供测试生成、覆盖率缺口分析和红—绿—重构流程指导。涵盖 Jest、Pytest、JUnit 和 Vitest,并包含夹具生成等脚本。
主要功能
- 01从代码和需求生成测试
- 02分析并排序覆盖率缺口
- 03指导红—绿—重构流程
- 04生成测试数据与模拟对象
- 05提供属性测试与变异测试指导
适用场景
- 01为现有代码生成单元测试
- 02定位未覆盖的代码分支
- 03按验收标准逐步开发新功能
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ alirezarezvani/ claude-skills --skill tdd-guide安装后在 Claude Code 中输入/tdd-guide确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
HOW_TO_USE.mdREADME.mdassets/expected_output.jsonsample_coverage_report.lcovsample_input_python.jsonsample_input_typescript.jsonreferences/ci-integration.mdframework-guide.mdtdd-best-practices.mdscripts/coverage_analyzer.pyfixture_generator.pyformat_detector.pyframework_adapter.pymetrics_calculator.pyoutput_formatter.pytdd_workflow.pytest_generator.py详细介绍
TDD 指南
测试驱动开发(TDD)Skill,用于生成测试、分析覆盖率,并引导 Jest、Pytest、JUnit 和 Vitest 中的红-绿-重构工作流。
工作流
从代码生成测试
- 提供源代码(TypeScript、JavaScript、Python、Java)
- 指定目标框架(Jest、Pytest、JUnit、Vitest)
- 使用需求运行
test_generator.py - 检查生成的测试桩
- 验证: 测试可以编译,并覆盖正常路径、错误情况和边界情况
分析覆盖率缺口
- 从测试运行器生成覆盖率报告(
npm test -- --coverage) - 对 LCOV/JSON/XML 报告运行
coverage_analyzer.py - 查看按优先级排列的缺口(P0/P1/P2)
- 为未覆盖的路径生成缺失的测试
- 验证: 覆盖率达到目标阈值(通常为 80% 以上)
TDD 新功能
- 先编写失败的测试(RED)
- 运行
tdd_workflow.py --phase red进行验证 - 实现最少的代码以通过测试(GREEN)
- 运行
tdd_workflow.py --phase green进行验证 - 在保持测试通过的同时进行重构(REFACTOR)
- 验证: 每个循环结束后所有测试都通过
示例
测试生成 — 输入 → 输出(Pytest)
输入的源函数(math_utils.py):
def divide(a: float, b: float) -> float:
if b == 0:
raise ValueError("Cannot divide by zero")
return a / b
命令:
python scripts/test_generator.py --input math_utils.py --framework pytest
生成的测试输出(test_math_utils.py):
import pytest
from math_utils import divide
class TestDivide:
def test_divide_positive_numbers(self):
assert divide(10, 2) == 5.0
def test_divide_negative_numerator(self):
assert divide(-10, 2) == -5.0
def test_divide_float_result(self):
assert divide(1, 3) == pytest.approx(0.333, rel=1e-3)
def test_divide_by_zero_raises_value_error(self):
with pytest.raises(ValueError, match="Cannot divide by zero"):
divide(10, 0)
def test_divide_zero_numerator(self):
assert divide(0, 5) == 0.0
覆盖率分析 — P0/P1/P2 输出示例
命令:
python scripts/coverage_analyzer.py --report lcov.info --threshold 80
示例输出:
Coverage Report — Overall: 63% (threshold: 80%)
P0 — Critical gaps (uncovered error paths):
auth/login.py:42-58 handle_expired_token() 0% covered
payments/process.py:91-110 handle_payment_failure() 0% covered
P1 — High-value gaps (core logic branches):
users/service.py:77 update_profile() — else branch 0% covered
orders/cart.py:134 apply_discount() — zero-qty guard 0% covered
P2 — Low-risk gaps (utility / helper functions):
utils/formatting.py:12 format_currency() 0% covered
Recommended: Generate tests for P0 items first to reach 80% threshold.
关键工具
| 工具 | 用途 | 用法 |
|---|---|---|
test_generator.py | 根据代码或需求生成测试用例 | python scripts/test_generator.py --input source.py --framework pytest |
coverage_analyzer.py | 解析并分析覆盖率报告 | python scripts/coverage_analyzer.py --report lcov.info --threshold 80 |
tdd_workflow.py | 引导红-绿-重构循环 | python scripts/tdd_workflow.py --phase red --test test_auth.py |
fixture_generator.py | 生成测试数据和 mock | python scripts/fixture_generator.py --entity User --count 5 |
其他脚本:framework_adapter.py(在不同框架之间转换)、metrics_calculator.py(质量指标)、format_detector.py(检测语言/框架)、output_formatter.py(命令行/桌面/CI 输出)。
输入要求
测试生成:
- 源代码(文件路径或粘贴的内容)
- 目标框架(Jest、Pytest、JUnit、Vitest)
- 覆盖范围(单元、集成、边界情况)
覆盖率分析:
- 覆盖率报告文件(LCOV、JSON 或 XML 格式)
- 可选:源代码,用于提供上下文
- 可选:目标阈值百分比
TDD 工作流:
- 功能需求或用户故事
- 当前阶段(RED、GREEN、REFACTOR)
- 测试代码和实现状态
规格优先工作流
TDD 在由书面规格驱动时最为有效。流程如下:
- 编写或获取规格 — 存储在
specs/<feature>.md中 - 提取验收标准 — 每条标准对应一个或多个测试用例
- 编写失败的测试(RED) — 每条验收标准对应一个测试
- 实现最少的代码(GREEN) — 按顺序满足每个测试
- 重构 — 在所有测试保持通过的前提下进行清理
规格目录约定
project/
├── specs/
│ ├── user-auth.md # Feature spec with acceptance criteria
│ ├── payment-processing.md
│ └── notification-system.md
├── tests/
│ ├── test_user_auth.py # Tests derived from specs/user-auth.md
│ ├── test_payments.py
│ └── test_notifications.py
└── src/
从规格中提取测试
规格中的每条验收标准至少映射到一个测试:
| 规格标准 | 测试用例 |
|---|---|
| "用户可以使用有效凭据登录" | test_login_valid_credentials_returns_token |
| "密码无效时返回 401" | test_login_invalid_password_returns_401 |
| "连续 5 次失败尝试后账户锁定" | test_login_locks_after_five_failures |
提示: 为规格中的验收标准编号。在测试文档字符串中引用编号,以便追溯(# AC-3: Account locks after 5 failed attempts)。
交叉引用: 完整的规格方法论(包括规格模板和评审清单)请参见
engineering/spec-driven-workflow。
各语言的红-绿-重构示例
TypeScript / Jest
// test/cart.test.ts
describe("Cart", () => {
describe("addItem", () => {
it("should add a new item to an empty cart", () => {
const cart = new Cart();
cart.addItem({ id: "sku-1", name: "Widget", price: 9.99, qty: 1 });
expect(cart.items).toHaveLength(1);
expect(cart.items[0].id).toBe("sku-1");
});
it("should increment quantity when adding an existing item", () => {
const cart = new Cart();
cart.addItem({ id: "sku-1", name: "Widget", price: 9.99, qty: 1 });
cart.addItem({ id: "sku-1", name: "Widget", price: 9.99, qty: 2 });
expect(cart.items).toHaveLength(1);
expect(cart.items[0].qty).toBe(3);
});
it("should throw when quantity is zero or negative", () => {
const cart = new Cart();
expect(() =>
cart.addItem({ id: "sku-1", name: "Widget", price: 9.99, qty: 0 })
).toThrow("Quantity must be positive");
});
});
});
Python / Pytest(高级模式)
# tests/conftest.py — shared fixtures
import pytest
from app.db import create_engine, Session
@pytest.fixture(scope="session")
def db_engine():
engine = create_engine("sqlite:///:memory:")
yield engine
engine.dispose()
@pytest.fixture
def db_session(db_engine):
session = Session(bind=db_engine)
yield session
session.rollback()
session.close()
# tests/test_pricing.py — parametrize for multiple cases
import pytest
from app.pricing import calculate_discount
@pytest.mark.parametrize("subtotal, expected_discount", [
(50.0, 0.0), # Below threshold — no discount
(100.0, 5.0), # 5% tier
(250.0, 25.0), # 10% tier
(500.0, 75.0), # 15% tier
])
def test_calculate_discount(subtotal, expected_discount):
assert calculate_discount(subtotal) == pytest.approx(expected_discount)
Go — 表驱动测试
// cart_test.go
package cart
import "testing"
func TestApplyDiscount(t *testing.T) {
tests := []struct {
name string
subtotal float64
want float64
}{
{"no discount below threshold", 50.0, 0.0},
{"5 percent tier", 100.0, 5.0},
{"10 percent tier", 250.0, 25.0},
{"15 percent tier", 500.0, 75.0},
{"zero subtotal", 0.0, 0.0},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := ApplyDiscount(tt.subtotal)
if got != tt.want {
t.Errorf("ApplyDiscount(%v) = %v, want %v", tt.subtotal, got, tt.want)
}
})
}
}
有界自主规则
在自主生成测试时,请遵循以下规则来判断何时停止并询问用户:
停止并询问的情况
- 需求不明确 — 规格说明或用户故事中的验收标准相互冲突或含糊不清
- 边界情况缺失 — 没有领域知识就无法确定边界值(例如:允许的最大交易金额)
- 测试数量超过 50 个 — 大型测试套件在提交前需要人工审查;请给出摘要,并询问优先覆盖哪些领域
- 外部依赖不明确 — 功能依赖的第三方 API 或服务行为没有文档说明
- 涉及安全敏感逻辑 — 身份认证、授权、加密或支付流程的测试场景需要人工确认
可以自主继续的情况
- 规格清晰且验收标准有编号 — 每条标准可以直接对应到测试
- 标准的 CRUD 操作 — 基于定义明确的模型进行创建、读取、更新、删除
- API 契约定义明确 — 有可用的 OpenAPI 规范或带类型的接口
- 纯函数 — 输入输出确定,且没有副作用
- 已有测试模式 — 代码库中已有类似的测试可供参照
基于属性的测试
基于属性的测试会生成随机输入来验证不变式,而不是依赖人工挑选的示例。当输入空间很大,且预期行为可以描述为某种属性时,就应使用它。
Python — Hypothesis
from hypothesis import given, strategies as st
from app.serializers import serialize, deserialize
@given(st.text())
def test_roundtrip_serialization(data):
"""Serialization followed by deserialization returns the original."""
assert deserialize(serialize(data)) == data
@given(st.integers(), st.integers())
def test_addition_is_commutative(a, b):
assert a + b == b + a
TypeScript — fast-check
import fc from "fast-check";
import { encode, decode } from "./codec";
test("encode/decode roundtrip", () => {
fc.assert(
fc.property(fc.string(), (input) => {
expect(decode(encode(input))).toBe(input);
})
);
});
何时选用基于属性的测试而非基于示例的测试
| 适用基于属性的测试 | 示例 |
|---|---|
| 数据转换 | 序列化/反序列化往返 |
| 数学性质 | 交换律、结合律、幂等性 |
| 编码/解码 | Base64、URL 编码、压缩 |
| 排序与过滤 | 输出有序,长度保持不变 |
| 解析器正确性 | 合法输入总能无错误地解析 |
变异测试
变异测试会修改生产代码(生成"变异体"),并检查测试是否能捕获这些改动。如果某个变异体存活下来(测试仍然通过),说明你的测试存在覆盖率本身无法揭示的缺口。
工具
| 语言 | 工具 | 命令 |
|---|---|---|
| TypeScript/JavaScript | Stryker | npx stryker run |
| Python | mutmut | mutmut run --paths-to-mutate=src/ |
| Java | PIT | mvn org.pitest:pitest-maven:mutationCoverage |
变异测试为何重要
- 100% 行覆盖率 != 好的测试 — 覆盖率只能说明代码被执行过,不能说明它被验证过
- 发现薄弱的断言 — 测试运行了代码却没有断言任何有意义的内容
- 发现缺失的边界测试 — 把
<改成<=这类变异体能暴露差一错误的缺口 - 可量化的质量指标 — 变异得分(被杀死的变异体百分比)比覆盖率百分比更能说明问题
建议: 即使整体覆盖率很高,也应对关键路径(身份认证、支付、数据处理)运行变异测试。P0 模块的变异得分目标为 85% 以上。
交叉引用
| 技能 | 关系 |
|---|---|
engineering/spec-driven-workflow | 规格 → 验收标准 → 测试提取流水线 |
engineering-team/focused-fix | 第 5 阶段(验证)使用 TDD,通过回归测试确认修复 |
engineering-team/senior-qa | 更宽泛的 QA 策略;TDD 是测试金字塔中的一层 |
engineering-team/code-reviewer | 审查生成的测试的断言质量与覆盖完整性 |
engineering-team/senior-fullstack | 项目脚手架包含与 TDD 工作流兼容的测试基础设施 |
局限性
| 范围 | 详情 |
|---|---|
| 单元测试侧重 | 集成测试与 E2E 测试需要不同的模式 |
| 静态分析 | 无法执行测试或测量运行时行为 |
| 语言支持 | 最适合 TypeScript、JavaScript、Python、Java |
| 报告格式 | 仅支持 LCOV、JSON、XML;其他格式需要转换 |
| 生成的测试 | 提供脚手架;复杂逻辑需要人工审查 |
何时使用其他工具:
- E2E 测试:Playwright、Cypress、Selenium
- 性能测试:k6、JMeter、Locust
- 安全测试:OWASP ZAP、Burp Suite