DRY в тестах

Принцип DRY (Don’t Repeat Yourself) является одним из важнейших в разработке программного обеспечения, включая написание тестов. Он заключается в том, чтобы избегать повторения кода, что способствует упрощению и улучшению качества тестов. Применение DRY в тестах помогает избежать избыточности, уменьшить количество ошибок и облегчить поддержку тестов.

Повторяющийся код в тестах

В контексте тестирования повторяющийся код может проявляться в различных формах:

  • Повторение одних и тех же действий в разных тестах.
  • Идентичные блоки кода в разных частях тестов.
  • Повторяющиеся проверки, которые одинаково проверяются в нескольких тестах.

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

Использование вспомогательных функций

Один из способов соблюдения принципа DRY в тестах — это создание вспомогательных функций, которые инкапсулируют повторяющиеся действия или проверки. В Mocha можно использовать такие вспомогательные методы, как before, after, beforeEach и afterEach, которые выполняются перед или после каждого теста или блока тестов.

Пример

describe('Тестирование пользователя', function() {
  let user;

  // Функция, выполняющая повторяющееся действие по созданию пользователя
  function createUser() {
    return { name: 'Иван', age: 25 };
  }

  beforeEach(function() {
    user = createUser();
  });

  it('проверка имени пользователя', function() {
    assert.equal(user.name, 'Иван');
  });

  it('проверка возраста пользователя', function() {
    assert.equal(user.age, 25);
  });
});

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

Параметризация тестов

Еще одним способом уменьшить повторение в тестах является параметризация. Это позволяет запускать один и тот же тест с различными входными данными, не дублируя код для каждого варианта. Mocha, в свою очередь, может быть легко интегрирован с такими библиотеками, как chai и assert, которые позволяют проводить параметризацию с помощью различных методов.

Пример

describe('Проверка арифметических операций', function() {
  const testCases = [
    { a: 2, b: 3, sum: 5 },
    { a: 5, b: 7, sum: 12 },
    { a: 1, b: 9, sum: 10 }
  ];

  testCases.forEach(function({ a, b, sum }) {
    it(`проверка суммы ${a} и ${b}`, function() {
      assert.equal(a + b, sum);
    });
  });
});

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

Создание собственных ассершенов

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

Пример

// Кастомная ассерция для проверки возраста пользователя
function assertUserAge(user, expectedAge) {
  assert.equal(user.age, expectedAge, `Ожидаемый возраст: ${expectedAge}`);
}

describe('Тестирование пользователя', function() {
  let user;

  beforeEach(function() {
    user = { name: 'Иван', age: 25 };
  });

  it('проверка возраста пользователя', function() {
    assertUserAge(user, 25);
  });

  it('проверка изменения возраста пользователя', function() {
    user.age = 30;
    assertUserAge(user, 30);
  });
});

Здесь функция assertUserAge позволяет избежать дублирования логики проверки возраста в каждом тесте, улучшая читаемость и сокращая количество кода.

Использование before и after для уменьшения дублирования

Для тестов, которые требуют одинаковых подготовительных шагов, полезно использовать хуки before и after. Хуки beforeEach и afterEach выполняются перед или после каждого теста, в то время как before и after — перед или после всей группы тестов, что помогает избежать дублирования в подготовке состояния для каждого отдельного теста.

Пример

describe('Тестирование базы данных', function() {
  let db;

  before(function() {
    // Настройка базы данных
    db = new Database();
    db.connect();
  });

  after(function() {
    // Закрытие подключения
    db.disconnect();
  });

  it('проверка добавления записи', function() {
    const user = { name: 'Иван', age: 25 };
    db.addUser(user);
    assert.equal(db.getUser(user.name).name, 'Иван');
  });

  it('проверка удаления записи', function() {
    const user = { name: 'Петр', age: 30 };
    db.addUser(user);
    db.removeUser(user.name);
    assert.equal(db.getUser(user.name), null);
  });
});

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

Тесты для компонентов и модулей

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

Пример

describe('Тестирование модулей калькулятора', function() {
  let calculator;

  beforeEach(function() {
    calculator = new Calculator();
  });

  it('проверка сложения', function() {
    const result = calculator.add(2, 3);
    assert.equal(result, 5);
  });

  it('проверка вычитания', function() {
    const result = calculator.subtract(5, 3);
    assert.equal(result, 2);
  });
});

Обработка повторяющихся ошибок

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

Пример

describe('Асинхронные операции', function() {
  let result;

  beforeEach(function(done) {
    someAsyncFunction()
      .then((data) => {
        result = data;
        done();
      })
      .catch(done);
  });

  it('проверка результата асинхронной функции', function() {
    assert.equal(result, 'expected value');
  });
});

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

Заключение

Применение принципа DRY в тестировании с Mocha позволяет уменьшить дублирование кода, облегчить поддержку тестов и сделать их более понятными. Использование вспомогательных функций, параметризации тестов, кастомных ассершенов и правильное использование хуков — все это способствует улучшению качества тестов. Это также помогает избежать ошибок при изменении логики тестов, так как достаточно обновить только одно место, где содержится повторяющийся код.