mcpField
T
tdd-mastery

tdd-mastery

github.com/rohitg00测试与质量2.8k185 次安装24 周前更新
代码仓库

关于 tdd-mastery

面向开发者的测试技能指南,介绍如何在 JavaScript、Python 和 Go 中实践测试驱动开发,并涵盖测试结构、覆盖率、模拟及反模式。

主要功能

  1. 01Red-Green-Refactor 循环
  2. 02Arrange-Act-Assert 结构
  3. 03Jest 和 Vitest 模式
  4. 04pytest 和 Go 测试模式
  5. 05覆盖率与测试层级指导
  6. 06模拟原则与反模式

适用场景

  1. 01在实现前规划测试
  2. 02为支持的语言组织测试
  3. 03检查覆盖率与不稳定测试的处理方式

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/rohitg00/awesome-claude-code-toolkit --skill tdd-mastery

安装后在 Claude Code 中输入/tdd-mastery确认已加载。

详细介绍

TDD 精通

核心循环:Red-Green-Refactor

  1. Red - 编写一个失败的测试,定义期望的行为
  2. Green - 编写最少的代码,让测试通过
  3. 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