TDD精通 tdd-mastery

这个技能专注于测试驱动开发(TDD)的工作流程,基于红-绿-重构循环,适用于多种编程语言如JavaScript、Python、Go等。它涵盖测试结构、模式、级别、覆盖率规则和模拟指南,帮助开发者编写高质量、可维护的代码。关键词:测试驱动开发,TDD,红绿重构,单元测试,集成测试,覆盖率,模拟,反模式。

测试 0 次安装 0 次浏览 更新于 3/8/2026

名称: tdd-mastery 描述: 跨语言的测试驱动开发工作流程,基于红-绿-重构循环

TDD精通

核心循环:红-绿-重构

  1. - 编写一个失败的测试,定义期望的行为
  2. 绿 - 编写最少的代码使测试通过
  3. 重构 - 清理代码,同时保持测试通过

永远不要在没有失败测试的情况下编写生产代码。每个循环应持续2-10分钟。

测试结构

一致使用Arrange-Act-Assert模式:

Arrange: 设置测试数据和依赖
Act:     执行被测试的行为
Assert:  验证预期结果

将测试命名为 test_<单元>_<场景>_<预期结果>it("应该在<条件>时<行为>")

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="a@b.com", 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

测试级别

级别 范围 速度 依赖
单元 单个函数/类 <100毫秒 无(模拟所有)
集成 模块边界 <5秒 真实数据库,真实文件系统
端到端 完整用户流程 <30秒 完整堆栈

比例目标:70% 单元,20% 集成,10% 端到端。

覆盖率规则

  • 在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 重置共享模拟

需避免的反模式

  • 测试实现细节而不是行为
  • 当代码被删除时测试仍通过(同义反复测试)
  • 测试用例之间共享可变状态
  • 忽略不稳定的测试而不是修复它们
  • 直接测试私有方法
  • 模糊意图的巨大测试设置