Что такое паттерн AAA (Arrange-Act-Assert) в тестировании?

«Что такое паттерн AAA (Arrange-Act-Assert) в тестировании?» — вопрос из категории Тестирование, который задают на 24% собеседований PHP Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

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 и других языках.