Arrange-Act-Assert паттерн

Паттерн Arrange-Act-Assert (AAA) представляет собой одну из наиболее популярных и полезных методик организации тестов в JavaScript. Он используется для того, чтобы тесты были структурированными, понятными и предсказуемыми. Этот подход особенно полезен при написании юнит-тестов с использованием фреймворка Jasmine, который является мощным инструментом для тестирования в JavaScript.

Структура теста в соответствии с AAA

Основная идея паттерна — разделить тест на три логически отдельных этапа, которые соответствуют действиям, происходящим в процессе тестирования.

  1. Arrange (Подготовка): На этом этапе выполняется вся необходимая подготовка для теста. Это включает в себя создание экземпляров объектов, установку значений переменных и настройку зависимостей. Важно, чтобы на этом этапе тест еще не выполнялся, а все необходимые ресурсы были готовы.

  2. Act (Действие): Здесь происходит выполнение того действия, которое необходимо протестировать. Это может быть вызов функции, изменение состояния объекта или выполнение какого-либо другого действия, которое требует проверки.

  3. Assert (Проверка): Этот этап включает в себя проверку результата действия. Здесь важно убедиться, что поведение системы соответствует ожидаемому. Для этого используются утверждения, которые сравнивают результаты работы системы с ожидаемыми значениями.

Пример теста, написанного в стиле AAA:

describe('Функция сложения', function() {
  it('должна правильно складывать два числа', function() {
    // Arrange
    var a = 5;
    var b = 3;

    // Act
    var result = add(a, b);

    // Assert
    expect(result).toBe(8);
  });
});

В этом примере:

  • Arrange — подготовка переменных a и b, которые будут переданы в функцию add.
  • Act — выполнение функции add с использованием подготовленных данных.
  • Assert — проверка того, что результат выполнения функции соответствует ожидаемому значению.

Зачем нужен паттерн AAA?

Паттерн AAA помогает улучшить читаемость и поддерживаемость тестов. Когда тесты организованы в таком виде, их легче понять и быстрее найти ошибку, если что-то пошло не так. Также это упрощает модификацию тестов в будущем, поскольку каждый этап теста изолирован и легко заменяем.

Повышение читаемости

Когда структура теста ясна и разделена на три логических блока, любой разработчик, читающий код, будет сразу понимать, что происходит на каждом этапе. Это особенно полезно в больших командах или при работе над проектами с долгосрочной поддержкой.

Простота в отладке

Если тест не проходит, можно с легкостью определить, на каком этапе произошла ошибка — на этапе подготовки, выполнения действия или проверки. Это помогает быстро локализовать проблему.

Поддержка и расширение

Когда тесты написаны в стиле AAA, их легче модифицировать. Например, можно добавить новые проверки в этап Assert или изменить подготовку данных в Arrange, не затрагивая саму логику выполнения действия.

Пример с несколькими проверками

В более сложных тестах можно использовать несколько утверждений на этапе Assert для проверки различных аспектов работы системы. Важно помнить, что при добавлении дополнительных проверок все они должны быть логически связаны с конкретным действием и его результатом.

describe('Функция деления', function() {
  it('должна правильно делить два числа', function() {
    // Arrange
    var a = 10;
    var b = 2;

    // Act
    var result = divide(a, b);

    // Assert
    expect(result).toBe(5);
    expect(result).not.toBeNaN();
    expect(result).toBeGreaterThan(0);
  });
});

Здесь проверяются три различных аспекта: правильность вычисления результата, отсутствие ошибки деления на ноль и положительность результата.

Отделение подготовки от действий

В некоторых случаях подготовка данных может быть достаточно сложной, и необходимо выполнить дополнительные шаги перед выполнением действия. В таких случаях удобно организовывать подготовку в отдельные функции, которые используются в каждом тесте.

describe('Класс User', function() {
  function createUser(name, age) {
    return new User(name, age);
  }

  it('должен правильно инициализировать пользователя', function() {
    // Arrange
    var user = createUser('John', 30);

    // Act
    var result = user.getName();

    // Assert
    expect(result).toBe('John');
  });

  it('должен правильно вычислять возраст', function() {
    // Arrange
    var user = createUser('John', 30);

    // Act
    var result = user.getAge();

    // Assert
    expect(result).toBe(30);
  });
});

Этот подход повышает повторное использование кода и помогает избежать дублирования при подготовке данных.

Тестирование с асинхронными операциями

Когда тесты требуют работы с асинхронными операциями, необходимо адаптировать паттерн AAA, чтобы корректно обрабатывать задержки или ожидания. В Jasmine для этого можно использовать done() или возвращать промисы.

Пример с использованием асинхронного кода:

describe('Функция загрузки данных', function() {
  it('должна загружать данные корректно', function(done) {
    // Arrange
    var api = new Api();

    // Act
    api.loadData(function(response) {
      // Assert
      expect(response).toEqual({ id: 1, name: 'John' });
      done();
    });
  });
});

Здесь:

  • Arrange — создание экземпляра объекта API.
  • Act — вызов метода loadData, который загружает данные.
  • Assert — проверка полученного ответа и завершение теста с помощью done().

Заключение

Паттерн Arrange-Act-Assert помогает структурировать тесты, делая их более понятными и поддерживаемыми. Важно придерживаться этой структуры, чтобы каждый тест был легко читаемым и предсказуемым. Разделение теста на три этапа способствует лучшему пониманию тестируемой логики и упрощает процесс отладки.