关于 aaa-pattern
aaa-pattern 是面向开发者的测试技能,帮助编写易读、易维护的测试。它要求清晰划分准备、执行和断言阶段,并区分测试行为数量与断言数量。
主要功能
- 01划分准备、执行和断言阶段
- 02每个测试聚焦一种行为
- 03识别阶段混杂的迹象
- 04将 AAA 用于 BDD 风格测试
- 05允许为一种行为编写多个断言
适用场景
- 01组织新的单元测试
- 02重构断言分散的测试
- 03排查难以理解的测试
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ yanko-belov/ code-craft --skill aaa-pattern安装后在 Claude Code 中输入/aaa-pattern确认已加载。
详细介绍
AAA 模式(Arrange-Act-Assert)
概述
每个测试都分为三个阶段:准备(Arrange)、执行(Act)、断言(Assert)。请将它们分开。
清晰的结构能让测试更易读、维护和调试。如果各阶段混在一起,测试就会变得令人困惑。
适用场景
- 编写任何测试时
- 测试逻辑难以理解时
- 不清楚测试内容时
- 多个操作或断言混在一起时
铁律
EVERY test must have clearly separated Arrange, Act, and Assert phases.
没有例外:
- 不能因为“这只是个简单测试”而例外
- 不能因为“这样更简洁”而例外
- 不能因为“各阶段一目了然”而例外
检测:阶段混杂的异味
如果准备、执行和断言混在一起,请停止:
// ❌ VIOLATION: Phases mixed together
it('adds items to cart', () => {
const cart = new Cart();
cart.add({ id: '1', price: 10 });
expect(cart.items.length).toBe(1); // Assert in the middle
cart.add({ id: '2', price: 20 }); // More acting
expect(cart.total).toBe(30); // Another assert
expect(cart.items.length).toBe(2); // And another
});
问题:
- 具体在测试什么?
- 哪个操作导致了哪个结果?
- 很难准确地为测试命名
正确模式:明确分离
// ✅ CORRECT: Clear AAA structure
it('calculates total price of all items in cart', () => {
// Arrange
const cart = new Cart();
cart.add({ id: '1', name: 'Apple', price: 10 });
cart.add({ id: '2', name: 'Banana', price: 20 });
// Act
const total = cart.getTotal();
// Assert
expect(total).toBe(30);
});
it('tracks number of items in cart', () => {
// Arrange
const cart = new Cart();
// Act
cart.add({ id: '1', name: 'Apple', price: 10 });
cart.add({ id: '2', name: 'Banana', price: 20 });
// Assert
expect(cart.itemCount).toBe(2);
});
三个阶段
准备(Arrange)
设置测试场景:
- 创建对象
- 配置模拟对象
- 准备输入数据
- 设置初始状态
执行(Act)
执行要测试的行为:
- 通常只有一个操作
- 调用方法
- 触发事件
- 发起请求
断言(Assert)
验证结果:
- 检查返回值
- 验证状态变化
- 确认模拟对象的交互
- 验证副作用
进阶:Given-When-Then
对于 BDD 风格的测试,概念相同:
describe('Cart', () => {
describe('when adding items', () => {
it('should update the total', () => {
// Given (Arrange)
const cart = new Cart();
const item = { id: '1', price: 25 };
// When (Act)
cart.add(item);
// Then (Assert)
expect(cart.total).toBe(25);
});
});
});
每个测试只写一个断言?
**这是指导原则,不是硬性规定。**如果多个断言验证的是同一个行为,就没有问题:
// ✅ OK: Multiple asserts for one logical behavior
it('creates user with correct properties', () => {
// Arrange
const input = { email: '[email protected]', name: 'Alice' };
// Act
const user = createUser(input);
// Assert - all verify the creation behavior
expect(user.id).toBeDefined();
expect(user.email).toBe('[email protected]');
expect(user.name).toBe('Alice');
expect(user.createdAt).toBeInstanceOf(Date);
});
// ❌ BAD: Multiple behaviors in one test
it('user operations', () => {
const user = createUser({ name: 'Alice' });
expect(user.name).toBe('Alice');
updateUser(user.id, { name: 'Bob' });
expect(user.name).toBe('Bob'); // Different behavior!
deleteUser(user.id);
expect(getUser(user.id)).toBeNull(); // Yet another behavior!
});
抵御压力的准则
1. “这样更简洁”
压力:“把各阶段合并起来能缩短测试”
**回应:**简短但令人困惑,不如稍长但清晰。
**行动:**分开各阶段。必要时添加注释。
2. “这只是个简单测试”
压力:“对于简单测试来说,AAA 模式太繁琐了”
**回应:**保持一致很重要。所有测试都应遵循相同的模式。
**行动:**即使是简单测试也使用 AAA。这不会增加任何成本。
3. “我需要执行多个操作”
压力:“这个行为需要多个步骤”
**回应:**多个准备步骤属于准备阶段。执行阶段只包含要测试的行为。
**行动:**如果需要多个执行阶段,你可能需要编写多个测试。
危险信号——停止并重新考虑
- 操作之间穿插着
expect()调用 - 没有明确的单一“执行”阶段
- 断言散落各处
- 难以描述测试验证的内容
- 测试名称与结构不符
这些情况都意味着:请按照 AAA 模式重新组织,并明确区分各阶段。
快速参考
| 阶段 | 包含内容 | 示例 |
|---|---|---|
| 准备(Arrange) | 设置、模拟对象、数据 | const cart = new Cart() |
| 执行(Act) | 单一行为 | const total = cart.checkout() |
| 断言(Assert) | 验证 | expect(total).toBe(100) |
常见的自我辩解(都不成立)
| 借口 | 事实 |
|---|---|
| “这样更简洁” | 清晰胜过简短。 |
| “各阶段一目了然” | 仍应明确地分开它们。 |
| “测试很简单,没必要” | 保持一致很重要。 |
| “需要执行多个操作” | 拆分成多个测试。 |
| “有注释就够了” | 良好的结构胜过注释。 |
总结
准备。执行。断言。按此顺序,并明确分开。
每个测试都先设置场景(准备),再执行一项操作(执行),最后进行验证(断言)。各阶段清晰时,测试就更易读、调试和维护。
评论
支持 Markdown