mcpField
T
tdd-guide

tdd-guide

github.com/alirezarezvani测试与质量28.9k1.0k 次安装2 天前更新
代码仓库

关于 tdd-guide

面向开发者的测试驱动开发技能,提供测试生成、覆盖率缺口分析和红—绿—重构流程指导。涵盖 Jest、Pytest、JUnit 和 Vitest,并包含夹具生成等脚本。

主要功能

  1. 01从代码和需求生成测试
  2. 02分析并排序覆盖率缺口
  3. 03指导红—绿—重构流程
  4. 04生成测试数据与模拟对象
  5. 05提供属性测试与变异测试指导

适用场景

  1. 01为现有代码生成单元测试
  2. 02定位未覆盖的代码分支
  3. 03按验收标准逐步开发新功能

安装

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

$npx skills add https://github.com/alirezarezvani/claude-skills --skill tdd-guide

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

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

HOW_TO_USE.md
README.md
assets/
expected_output.json
sample_coverage_report.lcov
sample_input_python.json
sample_input_typescript.json
references/
ci-integration.md
framework-guide.md
tdd-best-practices.md
scripts/
coverage_analyzer.py
fixture_generator.py
format_detector.py
framework_adapter.py
metrics_calculator.py
output_formatter.py
tdd_workflow.py
test_generator.py

详细介绍

TDD 指南

测试驱动开发(TDD)Skill,用于生成测试、分析覆盖率,并引导 Jest、Pytest、JUnit 和 Vitest 中的红-绿-重构工作流。


工作流

从代码生成测试
  1. 提供源代码(TypeScript、JavaScript、Python、Java)
  2. 指定目标框架(Jest、Pytest、JUnit、Vitest)
  3. 使用需求运行 test_generator.py
  4. 检查生成的测试桩
  5. 验证: 测试可以编译,并覆盖正常路径、错误情况和边界情况
分析覆盖率缺口
  1. 从测试运行器生成覆盖率报告(npm test -- --coverage)
  2. 对 LCOV/JSON/XML 报告运行 coverage_analyzer.py
  3. 查看按优先级排列的缺口(P0/P1/P2)
  4. 为未覆盖的路径生成缺失的测试
  5. 验证: 覆盖率达到目标阈值(通常为 80% 以上)
TDD 新功能
  1. 先编写失败的测试(RED)
  2. 运行 tdd_workflow.py --phase red 进行验证
  3. 实现最少的代码以通过测试(GREEN)
  4. 运行 tdd_workflow.py --phase green 进行验证
  5. 在保持测试通过的同时进行重构(REFACTOR)
  6. 验证: 每个循环结束后所有测试都通过

示例

测试生成 — 输入 → 输出(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生成测试数据和 mockpython 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 在由书面规格驱动时最为有效。流程如下:

  1. 编写或获取规格 — 存储在 specs/<feature>.md 中
  2. 提取验收标准 — 每条标准对应一个或多个测试用例
  3. 编写失败的测试(RED) — 每条验收标准对应一个测试
  4. 实现最少的代码(GREEN) — 按顺序满足每个测试
  5. 重构 — 在所有测试保持通过的前提下进行清理
规格目录约定
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/JavaScriptStrykernpx stryker run
Pythonmutmutmutmut run --paths-to-mutate=src/
JavaPITmvn 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

评论

支持 Markdown