Ответ
AAA (Arrange-Act-Assert) — это структурный паттерн для организации unit-тестов, который разделяет тест на три четкие фазы, улучшая читаемость и поддерживаемость.
Структура AAA:
1. Arrange (Подготовка)
Настройка тестового окружения: создание объектов, мокирование зависимостей, подготовка входных данных.
2. Act (Действие)
Выполнение тестируемого метода или операции.
3. Assert (Проверка)
Верификация результатов: проверка возвращаемых значений, состояния объектов, вызовов зависимостей.
Пример на PHP с PHPUnit:
class UserServiceTest extends TestCase
{
public function testUserIsActivatedAfterConfirmation(): void
{
// ===== ARRANGE =====
// Подготовка всех необходимых объектов и данных
$userRepository = $this->createMock(UserRepository::class);
$emailService = $this->createMock(EmailService::class);
$user = new User(
id: 1,
email: 'test@example.com',
isActive: false,
confirmationToken: 'abc123'
);
$userService = new UserService($userRepository, $emailService);
// ===== ACT =====
// Выполнение тестируемого действия
$result = $userService->confirmEmail($user, 'abc123');
// ===== ASSERT =====
// Проверка всех ожидаемых результатов
$this->assertTrue($result);
$this->assertTrue($user->isActive());
$this->assertNull($user->getConfirmationToken());
}
}
Более сложный пример с моками:
public function testOrderProcessingChargesPayment(): void
{
// ARRANGE
$paymentGateway = $this->createMock(PaymentGateway::class);
$inventory = $this->createMock(Inventory::class);
// Настройка ожиданий для моков
$paymentGateway->expects($this->once())
->method('charge')
->with(100.00, 'order_123')
->willReturn(new PaymentResult(true, 'txn_456'));
$inventory->expects($this->exactly(2))
->method('reserveItem');
$order = new Order(id: 'order_123', total: 100.00);
$order->addItem(new OrderItem(productId: 1, quantity: 2));
$order->addItem(new OrderItem(productId: 2, quantity: 1));
$orderProcessor = new OrderProcessor($paymentGateway, $inventory);
// ACT
$result = $orderProcessor->process($order);
// ASSERT
$this->assertTrue($result->isSuccess());
$this->assertEquals('txn_456', $result->getTransactionId());
$this->assertEquals(OrderStatus::PROCESSED, $order->getStatus());
}
Преимущества паттерна AAA:
1. Улучшенная читаемость:
// Плохо: смешанная логика
public function testSomething(): void
{
$service = new Service();
$result = $service->doSomething('input'); // Act среди Arrange
$this->assertEquals('expected', $result);
$dependency = new Dependency(); // Arrange после Act!
// ...
}
// Хорошо: четкое разделение
public function testSomething(): void
{
// Arrange - только подготовка
$dependency = new Dependency();
$service = new Service($dependency);
// Act - одно действие
$result = $service->doSomething('input');
// Assert - только проверки
$this->assertEquals('expected', $result);
}
2. Легкость отладки: При падении теста сразу понятно, на каком этапе проблема:
- Ошибка в Arrange → проблема с подготовкой данных
- Ошибка в Act → проблема в тестируемом коде
- Ошибка в Assert → неверные ожидания
3. Соблюдение Single Responsibility Principle: Каждый тест проверяет одну конкретную функциональность.
4. Упрощение рефакторинга: Четкая структура делает тесты более устойчивыми к изменениям.
Рекомендации по использованию:
Один Act на тест:
// Плохо: несколько действий
public function testMultipleActions(): void
{
// Arrange
$calculator = new Calculator();
// Act 1
$sum = $calculator->add(2, 3);
// Act 2 (нарушение паттерна)
$product = $calculator->multiply($sum, 4);
// Assert
$this->assertEquals(20, $product);
}
// Хорошо: разделить на два теста
public function testAddition(): void { /* тестирует add() */ }
public function testMultiplication(): void { /* тестирует multiply() */ }
Минимальный Arrange:
// Плохо: избыточная подготовка
public function testSomething(): void
{
// Arrange
$config = new Config(['option1' => 'value1', 'option2' => 'value2']);
$logger = new Logger();
$cache = new Cache();
$service = new Service($config, $logger, $cache);
// ... но тест использует только $config
}
// Хорошо: только необходимые зависимости
public function testSomething(): void
{
// Arrange
$config = new Config(['option1' => 'value1']);
$service = new Service($config);
// ...
}
Альтернативные названия паттерна:
- Given-When-Then (в BDD-стиле)
- Setup-Exercise-Verify (в xUnit)
Паттерн AAA является де-факто стандартом для написания чистых, поддерживаемых unit-тестов в PHP и других языках.