tdd-london-chicago
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseTest-Driven Development: London & Chicago Schools
测试驱动开发:伦敦与芝加哥流派
<default_to_action>
When implementing TDD or choosing testing style:
- IDENTIFY code type: domain logic → Chicago, external deps → London
- WRITE failing test first (Red phase)
- IMPLEMENT minimal code to pass (Green phase)
- REFACTOR while keeping tests green (Refactor phase)
- REPEAT cycle for next functionality
Quick Style Selection:
- Pure functions/calculations → Chicago (real objects, state verification)
- Controllers/services with deps → London (mocks, interaction verification)
- Value objects → Chicago (test final state)
- API integrations → London (mock external services)
- Mix both in practice (London for controllers, Chicago for domain)
Critical Success Factors:
- Tests drive design, not just verify it
- Make tests fail first to ensure they test something
- Write minimal code - no features beyond what's tested </default_to_action>
<default_to_action>
在实施TDD或选择测试风格时:
- 识别代码类型:领域逻辑 → 芝加哥流派,外部依赖 → 伦敦流派
- 先编写失败的测试(红阶段)
- 实现最小化代码使测试通过(绿阶段)
- 在保持测试通过的前提下进行重构(重构阶段)
- 为下一个功能重复该循环
快速风格选择:
- 纯函数/计算逻辑 → 芝加哥流派(使用真实对象,状态验证)
- 带有依赖的控制器/服务 → 伦敦流派(使用Mock,交互验证)
- 值对象 → 芝加哥流派(测试最终状态)
- API集成 → 伦敦流派(Mock外部服务)
- 实践中混合使用(控制器用伦敦流派,领域逻辑用芝加哥流派)
关键成功要素:
- 测试驱动设计,而非仅验证设计
- 先让测试失败,确保测试能检测到问题
- 编写最小化代码——不实现测试未覆盖的功能 </default_to_action>
Quick Reference Card
快速参考卡片
When to Use
适用场景
- Starting new feature with test-first approach
- Refactoring legacy code with test coverage
- Teaching TDD practices to team
- Choosing between mocking vs real objects
- 采用测试优先方法开发新功能时
- 为遗留代码添加测试覆盖率并重构时
- 向团队传授TDD实践时
- 在Mock与真实对象之间做选择时
TDD Cycle
TDD循环
| Phase | Action | Discipline |
|---|---|---|
| Red | Write failing test | Verify it fails, check message is clear |
| Green | Minimal code to pass | No extra features, don't refactor |
| Refactor | Improve structure | Keep tests passing, no new functionality |
| 阶段 | 操作 | 准则 |
|---|---|---|
| 红阶段 | 编写失败的测试 | 验证测试确实失败,确保错误信息清晰 |
| 绿阶段 | 编写最小化代码使测试通过 | 不添加额外功能,不进行重构 |
| 重构阶段 | 优化代码结构 | 保持测试通过,不添加新功能 |
School Comparison
流派对比
| Aspect | Chicago (Classicist) | London (Mockist) |
|---|---|---|
| Collaborators | Real objects | Mocks/stubs |
| Verification | State (assert outcomes) | Interaction (assert calls) |
| Isolation | Lower (integrated) | Higher (unit only) |
| Refactoring | Easier | Harder (mocks break) |
| Design feedback | Emerges from use | Explicit from start |
| 维度 | 芝加哥流派(经典派) | 伦敦流派(Mock派) |
|---|---|---|
| 协作对象 | 真实对象 | Mocks/Stubs |
| 验证方式 | 状态验证(断言结果) | 交互验证(断言调用) |
| 隔离程度 | 较低(集成式) | 较高(仅单元) |
| 重构难度 | 较容易 | 较难(Mock易失效) |
| 设计反馈 | 从使用中自然浮现 | 从一开始就明确体现 |
Agent Coordination
Agent协作
- : Generate tests in both schools
qe-test-generator - : Implement minimal code (Green)
qe-test-implementer - : Safe refactoring (Refactor)
qe-test-refactorer
- :生成两种流派的测试用例
qe-test-generator - :实现最小化代码(绿阶段)
qe-test-implementer - :安全重构(重构阶段)
qe-test-refactorer
Chicago School (State-Based)
芝加哥流派(基于状态)
Philosophy: Test observable behavior through public API. Keep tests close to consumer usage.
javascript
// State verification - test final outcome
describe('Order', () => {
it('calculates total with tax', () => {
const order = new Order();
order.addItem(new Product('Widget', 10.00), 2);
order.addItem(new Product('Gadget', 15.00), 1);
expect(order.totalWithTax(0.10)).toBe(38.50);
});
});When Chicago Shines:
- Domain logic with clear state
- Algorithms and calculations
- Value objects (,
Money)Email - Simple collaborations
- Learning new domain
理念: 通过公共API测试可观察行为,让测试贴近消费者的使用方式。
javascript
// 状态验证 - 测试最终结果
describe('Order', () => {
it('calculates total with tax', () => {
const order = new Order();
order.addItem(new Product('Widget', 10.00), 2);
order.addItem(new Product('Gadget', 15.00), 1);
expect(order.totalWithTax(0.10)).toBe(38.50);
});
});芝加哥流派适用场景:
- 具有清晰状态的领域逻辑
- 算法与计算逻辑
- 值对象(、
Money)Email - 简单协作场景
- 学习新领域时
London School (Mock-Based)
伦敦流派(基于Mock)
Philosophy: Test each unit in isolation. Focus on how objects collaborate.
javascript
// Interaction verification - test method calls
describe('Order', () => {
it('delegates tax calculation', () => {
const taxCalculator = {
calculateTax: jest.fn().mockReturnValue(3.50)
};
const order = new Order(taxCalculator);
order.addItem({ price: 10 }, 2);
order.totalWithTax();
expect(taxCalculator.calculateTax).toHaveBeenCalledWith(20.00);
});
});When London Shines:
- External integrations (DB, APIs)
- Command patterns with side effects
- Complex workflows
- Slow operations (network, I/O)
理念: 独立测试每个单元,聚焦对象间的协作方式。
javascript
// 交互验证 - 测试方法调用
describe('Order', () => {
it('delegates tax calculation', () => {
const taxCalculator = {
calculateTax: jest.fn().mockReturnValue(3.50)
};
const order = new Order(taxCalculator);
order.addItem({ price: 10 }, 2);
order.totalWithTax();
expect(taxCalculator.calculateTax).toHaveBeenCalledWith(20.00);
});
});伦敦流派适用场景:
- 外部集成(数据库、API)
- 带有副作用的命令模式
- 复杂工作流
- 慢速操作(网络、I/O)
Mixed Approach (Recommended)
混合方法(推荐)
javascript
// London for controller (external deps)
describe('OrderController', () => {
it('creates order and sends confirmation', async () => {
const orderService = { create: jest.fn().mockResolvedValue({ id: 123 }) };
const emailService = { send: jest.fn() };
const controller = new OrderController(orderService, emailService);
await controller.placeOrder(orderData);
expect(orderService.create).toHaveBeenCalledWith(orderData);
expect(emailService.send).toHaveBeenCalled();
});
});
// Chicago for domain logic
describe('OrderService', () => {
it('applies discount when threshold met', () => {
const service = new OrderService();
const order = service.create({ items: [...], total: 150 });
expect(order.discount).toBe(15); // 10% off > $100
});
});javascript
// 控制器使用伦敦流派(处理外部依赖)
describe('OrderController', () => {
it('creates order and sends confirmation', async () => {
const orderService = { create: jest.fn().mockResolvedValue({ id: 123 }) };
const emailService = { send: jest.fn() };
const controller = new OrderController(orderService, emailService);
await controller.placeOrder(orderData);
expect(orderService.create).toHaveBeenCalledWith(orderData);
expect(emailService.send).toHaveBeenCalled();
});
});
// 领域逻辑使用芝加哥流派
describe('OrderService', () => {
it('applies discount when threshold met', () => {
const service = new OrderService();
const order = service.create({ items: [...], total: 150 });
expect(order.discount).toBe(15); // 满100美元减10%
});
});Common Pitfalls
常见陷阱
❌ Over-Mocking (London)
❌ 过度Mock(伦敦流派)
javascript
// BAD - mocking everything
const product = { getName: jest.fn(), getPrice: jest.fn() };Better: Only mock external dependencies.
javascript
// 错误示例 - 对所有对象进行Mock
const product = { getName: jest.fn(), getPrice: jest.fn() };优化方案: 仅对外部依赖进行Mock。
❌ Mocking Internals
❌ Mock内部实现
javascript
// BAD - testing private methods
expect(order._calculateSubtotal).toHaveBeenCalled();Better: Test public behavior only.
javascript
// 错误示例 - 测试私有方法
expect(order._calculateSubtotal).toHaveBeenCalled();优化方案: 仅测试公共行为。
❌ Test Pain = Design Pain
❌ 测试痛苦 = 设计痛苦
- Need many mocks? → Too many dependencies
- Hard to set up? → Constructor does too much
- Can't test without database? → Coupling issue
- 需要大量Mock?→ 依赖过多
- 测试难以设置?→ 构造函数职责过重
- 无法脱离数据库测试?→ 耦合问题
Agent-Assisted TDD
Agent辅助TDD
typescript
// Agent generates tests in both schools
await Task("Generate Tests", {
style: 'chicago', // or 'london'
target: 'src/domain/Order.ts',
focus: 'state-verification' // or 'collaboration-patterns'
}, "qe-test-generator");
// Agent-human ping-pong TDD
// Human writes test concept
const testIdea = "Order applies 10% discount when total > $100";
// Agent generates formal failing test (Red)
await Task("Create Failing Test", testIdea, "qe-test-generator");
// Human writes minimal code (Green)
// Agent suggests refactorings
await Task("Suggest Refactorings", { preserveTests: true }, "qe-test-refactorer");typescript
// Agent生成两种流派的测试用例
await Task("Generate Tests", {
style: 'chicago', // 或 'london'
target: 'src/domain/Order.ts',
focus: 'state-verification' // 或 'collaboration-patterns'
}, "qe-test-generator");
// Agent-人类协作式TDD
// 人类提出测试思路
const testIdea = "当订单总额超过100美元时,应用10%的折扣";
// Agent生成规范的失败测试(红阶段)
await Task("Create Failing Test", testIdea, "qe-test-generator");
// 人类编写最小化代码(绿阶段)
// Agent提出重构建议
await Task("Suggest Refactorings", { preserveTests: true }, "qe-test-refactorer");Agent Coordination Hints
Agent协作提示
Memory Namespace
内存命名空间
aqe/tdd/
├── test-plan/* - TDD session plans
├── red-phase/* - Failing tests generated
├── green-phase/* - Implementation code
└── refactor-phase/* - Refactoring suggestionsaqe/tdd/
├── test-plan/* - TDD会话计划
├── red-phase/* - 生成的失败测试
├── green-phase/* - 实现代码
└── refactor-phase/* - 重构建议Fleet Coordination
集群协作
typescript
const tddFleet = await FleetManager.coordinate({
workflow: 'red-green-refactor',
agents: {
testGenerator: 'qe-test-generator',
testExecutor: 'qe-test-executor',
qualityAnalyzer: 'qe-quality-analyzer'
},
mode: 'sequential'
});typescript
const tddFleet = await FleetManager.coordinate({
workflow: 'red-green-refactor',
agents: {
testGenerator: 'qe-test-generator',
testExecutor: 'qe-test-executor',
qualityAnalyzer: 'qe-quality-analyzer'
},
mode: 'sequential'
});Related Skills
相关技能
- agentic-quality-engineering - TDD with agent coordination
- refactoring-patterns - Refactor phase techniques
- api-testing-patterns - London school for API testing
- agentic-quality-engineering - 结合Agent协作的TDD
- refactoring-patterns - 重构阶段技巧
- api-testing-patterns - 用于API测试的伦敦流派
Remember
要点回顾
Chicago: Test state, use real objects, refactor freely
London: Test interactions, mock dependencies, design interfaces first
Both: Write the test first, make it pass, refactor
Neither is "right." Choose based on context. Mix as needed. Goal: well-designed, tested code.
With Agents: Agents excel at generating tests, validating green phase, and suggesting refactorings. Use agents to maintain TDD discipline while humans focus on design decisions.
芝加哥流派: 测试状态,使用真实对象,自由重构
伦敦流派: 测试交互,Mock依赖,先设计接口
共同点: 先写测试,让测试通过,再重构
没有绝对“正确”的流派。根据场景选择,按需混合使用。目标:打造设计良好、经过测试的代码。
借助Agent: Agent擅长生成测试、验证绿阶段代码、提出重构建议。使用Agent维持TDD规范,人类聚焦设计决策。