mcpField
A
aaa-pattern

aaa-pattern

github.com/yanko-belov测试与质量1341 次安装40 周前更新
代码仓库

关于 aaa-pattern

aaa-pattern 是面向开发者的测试技能,帮助编写易读、易维护的测试。它要求清晰划分准备、执行和断言阶段,并区分测试行为数量与断言数量。

主要功能

  1. 01划分准备、执行和断言阶段
  2. 02每个测试聚焦一种行为
  3. 03识别阶段混杂的迹象
  4. 04将 AAA 用于 BDD 风格测试
  5. 05允许为一种行为编写多个断言

适用场景

  1. 01组织新的单元测试
  2. 02重构断言分散的测试
  3. 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