Рефакторинг тестов

Что такое рефакторинг тестов?

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

Зачем нужен рефакторинг тестов?

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

  • Читаемость и понятность: чем проще тест, тем легче его понять и изменить.
  • Избыточность: дублирование кода тестов может приводить к трудностям при изменении логики тестирования.
  • Масштабируемость: с ростом проекта тесты могут стать громоздкими и сложными. Рефакторинг помогает упростить добавление новых тестов.
  • Надежность: слабая структура тестов может привести к ложным срабатываниям или пропущенным ошибкам.

Основные техники рефакторинга тестов

  1. Удаление дублирования кода

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

    Пример:

    test('должен правильно сложить два числа', () => {
      const a = 1;
      const b = 2;
      const result = a + b;
      expect(result).toBe(3);
    });
    
    test('должен правильно вычесть два числа', () => {
      const a = 3;
      const b = 1;
      const result = a - b;
      expect(result).toBe(2);
    });

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

    Рефакторинг:

    const numbers = { a: 1, b: 2 };
    
    test('должен правильно сложить два числа', () => {
      const result = numbers.a + numbers.b;
      expect(result).toBe(3);
    });
    
    test('должен правильно вычесть два числа', () => {
      const result = numbers.a - numbers.b;
      expect(result).toBe(1);
    });
  2. Использование beforeAll, beforeEach, afterAll и afterEach

    Jest предоставляет хук beforeEach, который позволяет выполнять настройку перед каждым тестом, а также хук afterEach для выполнения действий после каждого теста. Эти хуки могут значительно сократить дублирование в коде тестов и повысить их читаемость.

    Пример:

    let user;
    
    beforeEach(() => {
      user = { name: 'Alice', age: 30 };
    });
    
    test('должен проверять имя пользователя', () => {
      expect(user.name).toBe('Alice');
    });
    
    test('должен проверять возраст пользователя', () => {
      expect(user.age).toBe(30);
    });

    Здесь настройка объекта user выполняется перед каждым тестом с помощью beforeEach, что позволяет избежать повторения кода.

  3. Вынос логики в вспомогательные функции

    Если тесты содержат сложную логику, её стоит вынести в отдельные функции. Это повышает читаемость и позволяет повторно использовать код. Например, если нужно проверять работу с API, можно вынести запросы в отдельную функцию.

    Пример:

    test('должен проверять статус ответа от API', async () => {
      const response = await fetchDataFromAPI();
      expect(response.status).toBe(200);
    });
    
    test('должен проверять содержимое ответа от API', async () => {
      const response = await fetchDataFromAPI();
      expect(response.data).toEqual({ key: 'value' });
    });

    Рефакторинг:

    const fetchDataFromAPI = async () => {
      // логика запроса к API
    };
    
    test('должен проверять статус ответа от API', async () => {
      const response = await fetchDataFromAPI();
      expect(response.status).toBe(200);
    });
    
    test('должен проверять содержимое ответа от API', async () => {
      const response = await fetchDataFromAPI();
      expect(response.data).toEqual({ key: 'value' });
    });
  4. Использование параметризации тестов

    Для проверки разных входных данных с одним и тем же поведением теста можно использовать параметризацию. В Jest это можно сделать с помощью test.each, что позволяет запустить тест с множеством разных наборов данных.

    Пример:

    test('должен правильно складывать два числа', () => {
      const a = 1;
      const b = 2;
      const result = a + b;
      expect(result).toBe(3);
    });
    
    test('должен правильно складывать два числа', () => {
      const a = 3;
      const b = 4;
      const result = a + b;
      expect(result).toBe(7);
    });

    Рефакторинг:

    test.each([
      [1, 2, 3],
      [3, 4, 7],
      [5, 5, 10]
    ])('должен правильно складывать %i и %i, результат %i', (a, b, expected) => {
      const result = a + b;
      expect(result).toBe(expected);
    });
  5. Переименование тестов

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

    Пример:

    test('должен проверять правильность суммы', () => {
      const result = 1 + 2;
      expect(result).toBe(3);
    });

    Рефакторинг:

    test('проверка сложения двух чисел', () => {
      const result = 1 + 2;
      expect(result).toBe(3);
    });
  6. Использование моков и шпионов

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

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

    const fetchData = jest.fn().mockResolvedValue({ data: 'test' });
    
    test('должен проверять данные от API', async () => {
      const response = await fetchData();
      expect(response.data).toBe('test');
    });

Оценка результатов рефакторинга

После выполнения рефакторинга важно убедиться, что тесты не только стали более читаемыми и удобными для поддержки, но и что они всё ещё работают корректно. Для этого следует:

  • Запускать тесты на всех этапах рефакторинга.
  • Проверить, не добавились ли новые ошибки или сбои в тестах.
  • Убедиться, что логика тестов не изменилась.

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