关于 tdd-mastery
面向开发者的测试技能指南,介绍如何在 JavaScript、Python 和 Go 中实践测试驱动开发,并涵盖测试结构、覆盖率、模拟及反模式。
主要功能
- 01Red-Green-Refactor 循环
- 02Arrange-Act-Assert 结构
- 03Jest 和 Vitest 模式
- 04pytest 和 Go 测试模式
- 05覆盖率与测试层级指导
- 06模拟原则与反模式
适用场景
- 01在实现前规划测试
- 02为支持的语言组织测试
- 03检查覆盖率与不稳定测试的处理方式
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ rohitg00/ awesome-claude-code-toolkit --skill tdd-mastery安装后在 Claude Code 中输入/tdd-mastery确认已加载。
详细介绍
TDD 精通
核心循环:Red-Green-Refactor
- Red - 编写一个失败的测试,定义期望的行为
- Green - 编写最少的代码,让测试通过
- Refactor - 在保持测试通过的同时清理代码
没有先编写失败的测试,绝不编写生产代码。每个循环应耗时 2-10 分钟。
测试结构
始终使用 Arrange-Act-Assert 模式:
Arrange: Set up test data and dependencies
Act: Execute the behavior under test
Assert: Verify the expected outcome
测试命名为 test_<unit>_<scenario>_<expected_result> 或 it("should <behavior> when <condition>")。
Jest / Vitest 模式
describe("OrderService", () => {
it("should apply discount when order exceeds threshold", () => {
const order = createOrder({ items: [{ price: 150, qty: 1 }] });
const result = applyDiscount(order, { threshold: 100, percent: 10 });
expect(result.total).toBe(135);
});
it("should throw when applying discount to empty order", () => {
const order = createOrder({ items: [] });
expect(() => applyDiscount(order, defaultDiscount)).toThrow(EmptyOrderError);
});
});
使用 vi.fn() / jest.fn() 创建模拟对象。优先使用依赖注入,而不是模块模拟。使用 beforeEach 进行共享设置,绝不在测试间共享可变状态。
pytest 模式
@pytest.fixture
def db_session():
session = create_test_session()
yield session
session.rollback()
def test_create_user_stores_hashed_password(db_session):
user = UserService(db_session).create(email="[email protected]", password="secret")
assert user.password_hash != "secret"
assert verify_password("secret", user.password_hash)
@pytest.mark.parametrize("input,expected", [
("", False),
("short", False),
("ValidPass1!", True),
])
def test_password_validation(input, expected):
assert validate_password(input) == expected
使用 pytest.raises 处理异常。使用 conftest.py 存放共享 fixture。使用 @pytest.mark.slow 标记慢速测试。
Go 测试模式
func TestParseConfig(t *testing.T) {
tests := []struct {
name string
input string
want Config
wantErr bool
}{
{"valid yaml", "port: 8080", Config{Port: 8080}, false},
{"empty input", "", Config{}, true},
{"invalid port", "port: -1", Config{}, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := ParseConfig([]byte(tt.input))
if (err != nil) != tt.wantErr {
t.Errorf("ParseConfig() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !tt.wantErr && got != tt.want {
t.Errorf("ParseConfig() = %v, want %v", got, tt.want)
}
})
}
}
默认使用表驱动测试。在测试工具函数中使用 t.Helper()。仅当团队已经在使用 testify/assert 时才使用它。
测试级别
| 级别 | 范围 | 速度 | 依赖项 |
|---|---|---|---|
| 单元 | 单个函数/类 | <100ms | 无(全部模拟) |
| 集成 | 模块边界 | <5s | 真实数据库、真实文件系统 |
| E2E | 完整用户流程 | <30s | 完整技术栈 |
目标比例:70% 单元测试、20% 集成测试、10% E2E 测试。
覆盖率规则
- 在 CI 中强制要求 至少 80% 的行覆盖率
- 跟踪分支覆盖率,而不只是行覆盖率
- 排除生成的代码、类型定义和配置文件
- 绝不为了达到覆盖率数字而编写测试;要测试行为
# Jest/Vitest
vitest run --coverage --coverage.thresholds.lines=80 --coverage.thresholds.branches=75
# pytest
pytest --cov=src --cov-fail-under=80 --cov-branch
# Go
go test -coverprofile=cover.out -coverpkg=./... ./...
go tool cover -func=cover.out
模拟指南
- 在边界处使用模拟:HTTP 客户端、数据库、文件系统、时钟
- 绝不模拟被测单元
- 对于仓库,优先使用伪实现(内存实现),而不是模拟对象
- 断言行为,而不是模拟对象的调用次数
- 使用
t.Cleanup/afterEach重置共享模拟对象
应避免的反模式
- 测试实现细节,而不是行为
- 删除代码后仍能通过的测试(同义反复测试)
- 在测试用例间共享可变状态
- 忽略不稳定的测试,而不是修复它们
- 直接测试私有方法
- 庞大的测试设置掩盖了测试意图
评论
支持 Markdown