测试驱动开发工作流

Claude Code 工作流专题 · 用Claude Code实践TDD

专题:Claude Code 工作流系统学习

关键词:Claude Code, TDD, 测试驱动, 单元测试, pytest, Jest, Mock, 测试生成, 覆盖率

一、TDD概述:理解红绿重构循环

测试驱动开发(Test-Driven Development,简称TDD)是一种以测试为核心驱动的软件开发方法论。其核心理念是先写测试,再写实现代码,通过不断重复"红-绿-重构"三阶段循环,逐步构建出高质量、可维护的软件系统。与传统的先编写实现代码、再补测试的开发方式不同,TDD迫使开发者在编写任何功能代码之前,先清晰地定义需求的验收标准,从而从一开始就确保代码的可测试性和正确性。

TDD的经典循环包含三个步骤:第一步是红色阶段(Red),编写一个失败的测试用例,由于被测代码尚未实现,测试预期会失败,红色提示开发者测试框架正常工作且测试目标明确;第二步是绿色阶段(Green),编写恰好能让测试通过的最简实现代码,不追求优雅或高效,只求通过测试;第三步是重构阶段(Refactor),在测试保护的条件下,对已通过的代码进行优化重构,消除重复、提高可读性、改善设计,重构完成后所有测试依然保持绿色。

TDD的核心收益:(1)强制前置设计,迫使开发者思考接口而非实现;(2)提供安全网,任何回归问题都能被测试立即捕获;(3)催生模块化设计,可测试的代码必然是松耦合的;(4)生成活的文档,测试用例本身就是可执行的规格说明书;(5)减少调试时间,Bug通常在引入的几分钟内被发现。

在Claude Code的辅助下,TDD流程可以得到极大增强。Claude Code能够根据自然语言描述自动生成初始测试代码,分析边界条件,补全异常测试场景,甚至可以针对已有的接口签名自动推导出完整的测试套件。这使得开发者可以更加专注于测试策略的设计业务逻辑的正确性,而不必花费大量时间在测试代码的机械编写上。

二、红绿重构循环详解

2.1 红色阶段:编写失败测试

红色阶段的目标是写一个明确表达需求的测试。一个好的失败测试应该足够具体,清晰地描述一个功能点。以计算器加法功能为例,在没有任何实现代码的情况下,我们可以先用Claude Code生成如下测试:

# test_calculator.py - Claude Code生成的初始测试 import pytest from calculator import Calculator def test_add_returns_sum_of_two_numbers(): calc = Calculator() result = calc.add(3, 5) assert result == 8 def test_add_with_negative_numbers(): calc = Calculator() result = calc.add(-1, -2) assert result == -3 def test_add_with_zero(): calc = Calculator() assert calc.add(0, 5) == 5 assert calc.add(5, 0) == 5

运行这个测试文件,由于Calculator类和add方法都不存在,pytest会报告ImportError或AttributeError,这就是"红色"状态。红色不是失败,而是确认测试框架正在正确执行,并且我们的测试用例确实在检测一个尚未实现的功能。

2.2 绿色阶段:让测试通过

绿色阶段的准则是写出恰好能让测试通过的最简代码。不需要考虑性能优化、异常处理或扩展性,这些都在后续的重构阶段处理。下面是让上述测试通过的最简实现:

# calculator.py - 最简实现 class Calculator: def add(self, a, b): return a + b

仅需三行代码,所有测试即可通过。这种"尽可能简单"的做法看似初级,实则有着深刻的工程考量:它避免了过度工程化,确保每一行代码都有测试覆盖,且实现恰好对应需求,没有未经验证的冗余逻辑。

2.3 重构阶段:优化设计

测试通过后,我们可以在绿色安全的保护下对代码进行重构。重构的原则是不改变外部行为,只改善内部结构。例如,如果Calculator类需要支持更多运算,我们可能会引入策略模式:

# calculator.py - 重构后的策略模式实现 from abc import ABC, abstractmethod from typing import Dict class Operation(ABC): def execute(self, a: float, b: float) -> float: pass class AddOperation(Operation): def execute(self, a: float, b: float) -> float: return a + b class Calculator: def __init__(self): self._operations: Dict[str, Operation] = { "add": AddOperation() } def add(self, a: float, b: float) -> float: return self._operations["add"].execute(a, b)

重构完成后,再次运行所有测试,确认全部通过。这样我们就完成了一次完整的TDD循环。Claude Code在重构阶段可以发挥巨大作用,开发者只需输入"重构Calculator类,提取Operation接口",Claude Code即可自动完成重构并保证测试仍然是绿色的。

在Claude Code中运行TDD循环的建议工作流:(1)用自然语言向Claude Code描述需求:"为计算器模块编写加法功能的测试";(2)审查Claude Code生成的测试代码,确认其正确表达了需求;(3)运行测试,确认红色状态;(4)让Claude Code生成最简实现;(5)运行测试通过所有用例;(6)提出重构要求,让Claude Code优化设计;(7)运行回归测试,确认绿色保持。

三、测试生成自动化

Claude Code在测试生成方面的能力远不止简单的样板代码。它能够根据接口签名自动推导测试用例分析边界条件自动补全异常场景,甚至可以生成覆盖完整功能路径的测试套件。下面是几个实用的Claude Code测试生成场景。

3.1 根据接口生成测试

给定一个函数签名,Claude Code可以自动分析参数类型、返回类型和业务语义,生成覆盖正常路径、边界条件和异常场景的测试。使用Claude Code的提示词如下:

# Claude Code Prompt: 根据接口生成测试 # 请为以下函数生成完整的pytest测试套件: # # def calculate_discount(price: float, member_level: str, # coupon_code: str | None = None) -> dict: # """计算最终折扣价格。 # Args: # price: 原价,必须大于0 # member_level: 会员等级,可选值为 normal/silver/gold/platinum # coupon_code: 优惠券代码,可选 # Returns: # {"final_price": float, "discount": float, "applied_coupon": bool} # """ # # 要求:覆盖正常路径、边界条件、异常输入和组合场景

Claude Code会根据函数签名的类型提示和文档字符串自动推断出需要测试的场景,生成数十个测试用例:

# Claude Code自动生成的测试 import pytest from datetime import datetime, timedelta class TestCalculateDiscount: """正常路径测试""" def test_normal_member_discount(self): result = calculate_discount(100.0, "gold") assert result["final_price"] == 80.0 assert result["discount"] == 0.2 """边界条件测试""" def test_minimum_price(self): result = calculate_discount(0.01, "silver") assert result["final_price"] > 0 def test_zero_price_raises_error(self): with pytest.raises(ValueError, match="价格必须大于0"): calculate_discount(0, "normal") def test_negative_price_raises_error(self): with pytest.raises(ValueError): calculate_discount(-50, "gold") """异常输入测试""" def test_invalid_member_level(self): with pytest.raises(ValueError, match="未知会员等级"): calculate_discount(100, "vip") def test_none_price_raises_type_error(self): with pytest.raises(TypeError): calculate_discount(None, "gold") """优惠券组合场景""" def test_with_valid_coupon(self): result = calculate_discount(200, "platinum", "SAVE50") assert result["applied_coupon"] is True assert result["final_price"] < 150 def test_expired_coupon(self): result = calculate_discount(100, "normal", "EXPIRED99") assert result["applied_coupon"] is False

3.2 边界条件自动分析

Claude Code能够自动识别和生成边界条件测试。对于数值类型的参数,它会自动生成零值、负值、最大值、最小值、浮点数精度等边界场景;对于字符串参数,它会生成空字符串、超长字符串、特殊字符、Unicode字符等场景;对于集合类型,它会生成空集合、单元素集合、大量元素集合等场景。这种系统化的边界分析大大降低了测试遗漏的风险。

常见陷阱:自动生成的测试并非完美无缺。开发者必须审查Claude Code生成的测试,确认:(1)测试名称清晰地表达了测试意图;(2)断言精确且不冗余;(3)测试之间相互独立,不共享可变状态;(4)没有因测试框架差异导致的误报(false positive)或漏报(false negative)。

四、单元测试工作流

单元测试是TDD实践的基石。不同的技术栈和项目类型需要不同的测试框架配置。下面分别介绍Python生态(pytest/unittest)和JavaScript生态(Jest)的单元测试工作流配置。

4.1 pytest测试配置

pytest是Python社区最流行的测试框架,其简洁的语法、强大的fixture系统和丰富的插件生态使其成为TDD实践的首选。一个典型的pytest项目配置如下:

# pytest.ini - pytest配置文件 [pytest] minversion = 7.0 testpaths = tests python_files = test_*.py python_classes = Test* python_functions = test_* markers = slow: 标记耗时较长的测试 integration: 集成测试 smoke: 冒烟测试 unit: 单元测试 filterwarnings = error ignore::DeprecationWarning
# conftest.py - 共享fixture定义 import pytest from typing import Generator from datetime import datetime @pytest.fixture def sample_user_data() -> dict: return { "id": 1, "name": "张三", "email": "zhangsan@example.com", "created_at": datetime.now().isoformat() } @pytest.fixture def db_session() -> Generator: """数据库会话fixture,自动回滚事务""" session = create_test_session() try: yield session finally: session.rollback() session.close()
# tests/test_user_service.py - 实际测试用例 import pytest from unittest.mock import Mock, patch class TestUserService: """用户服务单元测试""" @pytest.mark.unit def test_create_user_success(self, db_session, sample_user_data): service = UserService(db_session) user = service.create_user(sample_user_data) assert user.id is not None assert user.name == sample_user_data["name"] assert user.email == sample_user_data["email"] @pytest.mark.unit def test_create_user_duplicate_email(self, db_session): service = UserService(db_session) service.create_user({"name": "A", "email": "dup@test.com"}) with pytest.raises(DuplicateEmailError): service.create_user({"name": "B", "email": "dup@test.com"})

4.2 Jest测试配置(JavaScript/TypeScript)

在前端项目中,Jest是最广泛使用的测试框架之一,尤其适合React/Vue等组件化框架的TDD实践。一个标准的Jest配置包含测试环境、模块映射、覆盖率收集等关键设置:

// jest.config.ts - Jest配置文件 import type { Config } from "@jest/types"; const config: Config.InitialOptions = { preset: "ts-jest", testEnvironment: "jsdom", roots: ["/src"], testMatch: [ "**/__tests__/**/*.test.(ts|tsx)", "**/?(*.)+(spec|test).(ts|tsx)" ], moduleNameMapper: { "^@/(.*)$": "/src/$1", "\\.(css|less|scss)$": "identity-obj-proxy" }, collectCoverageFrom: [ "src/**/*.{ts,tsx}", "!src/**/*.d.ts", "!src/main.tsx" ], coverageThreshold: { global: { branches: 80, functions: 85, lines: 85, statements: 85 } } }; export default config;
// src/__tests__/UserProfile.test.tsx - React组件TDD示例 import { render, screen, fireEvent } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import { UserProfile } from "@/components/UserProfile"; describe("UserProfile 组件", () => { const mockUser = { id: 1, name: "张三", avatar: "https://example.com/avatar.png" }; test("渲染用户名称", () => { render(React.createElement(UserProfile, { user: mockUser })); expect(screen.getByText("张三")).toBeInTheDocument(); }); test("点击编辑按钮触发回调", async () => { const onEdit = jest.fn(); render(React.createElement(UserProfile, { user: mockUser, onEdit })); await userEvent.click(screen.getByRole("button", { name: /编辑/ })); expect(onEdit).toHaveBeenCalledTimes(1); expect(onEdit).toHaveBeenCalledWith(mockUser.id); }); test("显示加载状态", () => { render(React.createElement(UserProfile, { loading: true })); expect(screen.getByTestId("loading-spinner")).toBeInTheDocument(); }); });

4.3 测试覆盖率管理

测试覆盖率是衡量TDD实践质量的重要指标,但不是唯一指标。高覆盖率不等于高质量的测试。覆盖率数据应当用作发现未测试代码的指南,而非绩效考核目标。一个健康的项目通常要求:行覆盖率不低于80%,分支覆盖率不低于75%,函数覆盖率不低于85%。更重要的是确保关键业务逻辑路径被完全覆盖。

# 运行覆盖率测试并生成报告 # pytest + coverage pytest --cov=src --cov-report=term-missing --cov-report=html:coverage_report # Jest jest --coverage --coverageDirectory=coverage_report # Claude Code可以分析覆盖率报告并生成补充测试 # 提示词:"分析 coverage_report/index.html 中未覆盖的行, # 为 src/services/payment.py 中缺失覆盖的代码路径生成测试"

4.4 测试分组与执行策略

随着项目增长,测试数量会快速膨胀,合理的测试分组和执行策略变得至关重要。常见的分组方式包括:单元测试(快速、无外部依赖、可频繁执行)、集成测试(依赖数据库或API、执行较慢)、端到端测试(模拟真实用户操作、最慢但最全面)。在CI/CD流水线中,单元测试应在每次提交时运行,集成测试可在PR阶段运行,端到端测试则在合并到主分支前执行。

# 分层测试执行命令 # 单元测试(毫秒级,每次提交执行) pytest -m unit -x --tb=short # 集成测试(秒级,PR阶段执行) pytest -m integration --timeout=30 # 冒烟测试(核心功能快速验证) pytest -m smoke -v # 完整测试套件(合并前执行) pytest --cov=src --cov-fail-under=80

五、测试替身:Mock、Stub、Fake与Spy

在TDD实践中,测试替身(Test Doubles)是隔离被测代码与外部依赖的关键技术。当被测对象依赖于数据库、网络API、文件系统或时钟等外部资源时,测试替身可以模拟这些依赖的行为,使测试变得快速、可靠且可重复。Claude Code能够根据接口类型自动生成各类测试替身,大幅降低测试编写的复杂度。

5.1 各类测试替身的用途对比

类型用途典型场景验证方式
Dummy填充参数列表函数必须但测试不关心的参数不使用,仅占位
Stub提供预设返回值硬编码的API响应、数据库查询结果不验证调用
Spy记录调用信息验证方法被调用、调用次数、调用参数验证调用行为
Mock预设行为+验证交互外部服务调用、消息队列发送验证交互符合预期
Fake轻量级替代实现内存数据库、本地文件系统替代验证行为结果

5.2 pytest Mock实战

使用pytest中的unittest.mock模块可以灵活创建各种测试替身。下面是一个支付服务的测试示例,展示了如何通过Mock隔离外部支付网关:

# test_payment_service.py - Mock测试替身实战 from unittest.mock import Mock, patch, PropertyMock import pytest from datetime import datetime class TestPaymentService: """支付服务测试 - 使用Mock隔离外部网关""" def test_charge_success(self): """Stub: 模拟支付网关返回成功""" mock_gateway = Mock() mock_gateway.charge.return_value = { "status": "success", "transaction_id": "txn_12345", "amount": 99.99 } service = PaymentService(mock_gateway) result = service.charge(99.99, "card_abc") assert result["status"] == "success" assert result["transaction_id"] == "txn_12345" """Spy: 验证网关被正确调用""" mock_gateway.charge.assert_called_once_with( amount=99.99, source="card_abc", currency="cny" ) def test_charge_retry_on_failure(self): """Mock: 模拟网关首次失败,重试后成功""" mock_gateway = Mock() mock_gateway.charge.side_effect = [ ConnectionError("Network timeout"), # 第一次调用失败 {"status": "success", "transaction_id": "txn_67890"} # 重试成功 ] service = PaymentService(mock_gateway, max_retries=3) result = service.charge(99.99, "card_abc") assert result["status"] == "success" assert mock_gateway.charge.call_count == 2 @patch("payment_service.datetime") def test_charge_records_timestamp(self, mock_datetime): """Mock: 使用patch替换全局依赖""" fixed_time = datetime(2025, 6, 1, 12, 0, 0) mock_datetime.now.return_value = fixed_time mock_gateway = Mock() mock_gateway.charge.return_value = {"status": "success"} service = PaymentService(mock_gateway) service.charge(50, "card_xyz") # 验证时间戳被记录 assert service.last_charge_time == fixed_time

5.3 Fake模式:内存数据库替代

当测试需要验证复杂的数据库查询逻辑时,Mock可能不够用,此时Fake模式是更好的选择。Fake是一个轻量级的内存实现,行为与真实依赖一致但不需要外部基础设施。Claude Code可以根据数据库模型自动生成Fake实现:

# fake_repository.py - Fake内存数据库实现 from typing import Dict, List, Optional from datetime import datetime class FakeUserRepository: """内存数据库Fake实现,模拟真实数据库行为""" def __init__(self): self._users: Dict[int, dict] = {} self._next_id = 1 def add(self, user_data: dict) -> dict: user = {"id": self._next_id, **user_data} self._users[self._next_id] = user self._next_id += 1 return {**user} # 返回副本防止外部修改 def get_by_id(self, user_id: int) -> Optional[dict]: user = self._users.get(user_id) return {**user} if user else None def find_by_email(self, email: str) -> Optional[dict]: for user in self._users.values(): if user["email"] == email: return {**user} return None def get_active_users(self, since: datetime) -> List[dict]: return [ {**u} for u in self._users.values() if u.get("last_login", datetime.min) >= since ]

选择替身类型的准则:(1)如果只关心返回值,用Stub;(2)如果需要验证交互行为,用MockSpy;(3)如果涉及复杂的逻辑路径或多步骤操作,用Fake;(4)尽量在集成测试中使用Fake而非Mock,因为Fake更接近真实的运行时行为;(5)Claude Code可以根据接口类型签名自动判断最适合的替身类型。

六、参数化测试

参数化测试是TDD中提高测试覆盖率和减少重复代码的关键技术。通过参数化,同一个测试逻辑可以使用多组输入数据运行,每组数据都独立执行,互不影响。这特别适合测试多输入组合边界值等价类划分场景。

6.1 pytest参数化

pytest通过内置的@pytest.mark.parametrize装饰器提供强大的参数化支持。Claude Code可以根据函数的参数类型和业务规则自动生成参数组合:

# test_discount_parametrized.py - 参数化测试 import pytest from decimal import Decimal # 等价类划分 + 边界值分析的参数组合 @pytest.mark.parametrize("price, level, expected_discount", [ # 等价类:正常价格范围 (100.00, "normal", 0.0), (100.00, "silver", 0.05), (100.00, "gold", 0.10), (100.00, "platinum", 0.20), # 边界值:价格边界 (0.01, "gold", 0.10), # 最小值 (9999.99, "platinum", 0.20), # 最大值 # 等价类:特殊价格点 (500.00, "silver", 0.10), # 满500额外折扣 (1000.00, "gold", 0.20), # 满1000额外折扣 ]) def test_calculate_discount(price, level, expected_discount): result = calculate_discount(price, level) assert result["discount"] == expected_discount # 多参数组合自动生成(使用笛卡尔积) @pytest.mark.parametrize("has_coupon,is_holiday,expected_multiplier", [ (True, True, 0.6), # 优惠券+节假日:折上折 (True, False, 0.8), # 仅优惠券 (False, True, 0.9), # 仅节假日 (False, False, 1.0), # 无优惠 ]) def test_discount_multiplier(has_coupon, is_holiday, expected_multiplier): config = DiscountConfig(has_coupon=has_coupon, is_holiday=is_holiday) assert config.get_multiplier() == expected_multiplier

6.2 Jtest参数化

在JavaScript生态中,Jest通过test.eachdescribe.each提供参数化支持:

// test/parametrized.test.ts - Jest参数化测试 describe("价格计算参数化测试", () => { // test.each: 内联参数化 test.each([ { price: 100, level: "normal", expected: 100.00 }, { price: 100, level: "silver", expected: 95.00 }, { price: 100, level: "gold", expected: 90.00 }, { price: 100, level: "platinum", expected: 80.00 }, { price: 0.01, level: "gold", expected: 0.009 }, { price: 500, level: "silver", expected: 450.00 } ])("价格 $price 会员 $level 应支付 $expected", ({ price, level, expected }) => { const result = calculatePrice(price, level); expect(result.finalPrice).toBeCloseTo(expected, 2); } ); // describe.each: 分组参数化 describe.each([ ["整数", [0, 1, 100, 9999]], ["小数", [0.01, 99.99, 500.50]], ["大数值", [10000, 99999.99]] ])("%s 价格类型", (_, prices) => { test.each(prices)("价格 %p 应返回有效折扣率", (price) => { const discount = getDiscountRate(price); expect(discount).toBeGreaterThanOrEqual(0); expect(discount).toBeLessThanOrEqual(1); }); }); });

6.3 边界值与等价类划分策略

Claude Code可以自动应用边界值分析(Boundary Value Analysis)等价类划分(Equivalence Partitioning)两种经典测试设计技术来生成参数组合。边界值分析关注输入域的边界(最小值、最大值、略高于最小值、略低于最大值等),因为实践经验表明边界处最容易出现缺陷。等价类划分则将输入域划分为若干等价类,每个等价类中的任意值都应该产生相同的行为,从而只需在每个等价类中选取一个代表值进行测试。这两种技术的结合可以用最少的测试用例覆盖最多的缺陷

# Claude Code的边界值分析提示词 # 提示词模板: # "函数 divide(dividend: float, divisor: float) -> float # 约束:divisor != 0, |dividend| <= 1e6 # 请应用边界值分析和等价类划分生成测试数据" # Claude Code生成的边界值测试数据 # 被除数等价类:正数、负数、零、接近上限、接近下限 # 除数等价类:正数、负数、接近零的正数、接近零的负数 @pytest.mark.parametrize("dividend, divisor, expected", [ # 正常除法 (10, 2, 5.0), (-10, 2, -5.0), (10, -2, -5.0), (0, 5, 0.0), # 除数边界 (1, 1e-10, 1e10), # 除数接近零(正侧) (1, -1e-10, -1e10), # 除数接近零(负侧) (1, 0, "ZeroDivisionError"), # 除数为零 # 被除数边界 (1e6, 1, 1e6), # 上限边界 (-1e6, 1, -1e6), # 下限边界 (1e6 + 1, 1, "ValueError"), # 超出上限 ]) def test_divide(dividend, divisor, expected): if isinstance(expected, str): with pytest.raises(eval(expected)): divide(dividend, divisor) else: assert divide(dividend, divisor) == expected

七、测试最佳实践

良好的TDD实践不仅取决于技术能力,更取决于遵循经过验证的测试最佳实践。这些实践规范来自于大量开源项目和工业级系统的经验总结,能够有效提升测试的可维护性、可靠性和价值。

7.1 测试命名规范

测试名称是测试用例的"文档",一个好的测试名称应该清晰地表达被测内容测试场景预期结果。推荐的命名模式为 test_[被测试方法]_[场景]_[预期结果]

# 推荐的测试命名模式 def test_withdraw_sufficient_balance_deducts_amount(): """Given: 余额充足的账户 | When: 提款 | Then: 余额减少""" account = BankAccount(balance=1000) account.withdraw(300) assert account.balance == 700 def test_withdraw_insufficient_balance_raises_error(): """Given: 余额不足的账户 | When: 提款 | Then: 抛出异常""" account = BankAccount(balance=100) with pytest.raises(InsufficientFundsError): account.withdraw(200)

7.2 单一断言原则

每个测试用例应该只测试一个行为逻辑,即一个测试一个断言(或一组高度相关的断言)。这确保当测试失败时,失败的原因非常明确,不需要复杂的排查过程。多个不相关的断言应当拆分为独立的测试用例:

// 反例:多断言混合测试(不推荐) test("用户创建和查询", () => { const user = createUser({ name: "Alice" }); expect(user.id).toBeDefined(); // 关注点1 expect(user.name).toBe("Alice"); // 关注点2 const found = findUser(user.id); expect(found).not.toBeNull(); // 关注点3(不同行为) expect(found.email).toBe(""); // 关注点4 }); // 正例:拆分为独立测试(推荐) describe("用户创建", () => { test("成功创建后返回带有ID的用户对象", () => { const user = createUser({ name: "Alice" }); expect(user.id).toBeDefined(); expect(user.name).toBe("Alice"); }); }); describe("用户查询", () => { test("已存在的用户可以通过ID查询到", () => { const user = createUser({ name: "Alice" }); const found = findUser(user.id); expect(found).not.toBeNull(); }); });

7.3 测试可读性

测试代码被阅读的次数远多于被编写的次数,因此可读性是测试质量的核心指标。遵循Given-When-Then模式可以有效提升测试的可读性:Given描述测试的前置条件和上下文,When描述被测操作,Then描述期望的结果。在Claude Code生成的测试中,通过注释清晰标注这三个阶段,使测试意图一目了然。

7.4 测试金字塔

测试金字塔是指导测试策略的经典模型。它建议项目的测试应该呈现金字塔形状:底层是大量的单元测试(快速、可靠、细粒度),中间是适量的集成测试(验证组件间的交互),顶层是少量的端到端测试(验证完整的用户流程)。这个比例通常建议为 70%:20%:10%。Claude Code可以根据测试的类型自动将其归类并分组,帮助团队维护合理的金字塔结构。

# 测试金字塔:典型项目测试统计 # pytest --collect-only --quiet | wc -l # 统计总测试数 # 标记测试层级 @pytest.mark.unit # 单元测试(~70%) @pytest.mark.integration # 集成测试(~20%) @pytest.mark.e2e # 端到端测试(~10%) # 在CI中按层级执行 pytest -m unit --cov=src --cov-fail-under=85 # 每次提交 pytest -m integration --cov=src --cov-fail-under=80 # PR阶段 pytest -m e2e --timeout=120 # 合并前

7.5 测试重构

测试代码也需要像生产代码一样进行重构。常见的测试坏味道包括:重复的测试设置代码(提取到fixture或工厂函数)、过于具体的断言(绑定实现细节而非行为契约)、条件逻辑(测试中的if语句通常是危险信号)、不稳定的测试(偶发失败,通常由共享可变状态或时序依赖引起)。Claude Code可以自动检测这些测试坏味道并提出重构建议。

TDD最佳实践速查表:(1)每个测试只验证一个行为;(2)测试名称应该像一句完整的句子;(3)遵循Given-When-Then结构组织测试;(4)避免测试中的条件逻辑和循环;(5)使用工厂函数或fixture减少重复;(6)断言应面向行为契约而非实现细节;(7)不稳定的测试比没有测试更糟糕;(8)将测试视为可执行文档;(9)持续重构测试代码;(10)测试覆盖率不是目标,测试价值才是。

八、Claude Code TDD工作流总结

将Claude Code与TDD结合,可以构建一个高效的开发工作流。下表总结了在不同阶段如何利用Claude Code加速TDD实践:

TDD阶段Claude Code作用提示词示例
RED(编写测试)根据自然语言需求生成测试代码,分析边界条件"根据接口签名生成pytest测试,覆盖正常路径和边界条件"
GREEN(实现代码)生成恰好通过测试的最简实现"为这些测试生成最简实现,只要求测试通过"
REFACTOR(重构)优化设计模式、提取抽象、消除重复"重构代码提取策略模式,保持所有测试通过"
覆盖率分析分析覆盖率报告,生成补充测试"分析coverage报告,为未覆盖的分支生成测试"
测试维护识别测试坏味道,提出重构建议"审查测试代码的可维护性,列出改进建议"

工作流总结:在Claude Code的协助下,TDD的实践效率可以提升数倍。开发者只需专注于需求分析测试策略设计,而将测试代码生成边界条件分析最简实现编写重构优化等重复性工作交给AI助手。人机协作的TDD工作流不仅提高了开发效率,更通过AI的全面分析能力减少了测试遗漏的风险,是现代化软件工程的重要实践方向。