Рефакторинг тестов — это процесс улучшения структуры тестов, не меняя их функциональности. Цель рефакторинга заключается в том, чтобы сделать тесты более читаемыми, поддерживаемыми и эффективными. Он помогает уменьшить дублирование кода, улучшить масштабируемость и повысить стабильность тестов. Важно помнить, что рефакторинг тестов не должен влиять на логику проверок и не должен изменять их результат.
Тесты — это важная часть процесса разработки, и их качество напрямую влияет на стабильность и надежность приложения. Однако, как и в случае с основным кодом, тесты могут со временем становиться сложными, трудными для понимания и поддержки. Рефакторинг помогает решить следующие проблемы:
Удаление дублирования кода
Повторение одних и тех же действий в разных тестах — это один из основных признаков того, что код нуждается в рефакторинге. Если несколько тестов используют одинаковую логику для настройки начальных данных или проверок, имеет смысл вынести этот код в отдельные функции или специальные утилиты.
Пример:
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);
});Использование 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, что позволяет избежать
повторения кода.
Вынос логики в вспомогательные функции
Если тесты содержат сложную логику, её стоит вынести в отдельные функции. Это повышает читаемость и позволяет повторно использовать код. Например, если нужно проверять работу с 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' });
});Использование параметризации тестов
Для проверки разных входных данных с одним и тем же поведением теста
можно использовать параметризацию. В 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);
});Переименование тестов
Названия тестов должны быть ясными и точными, чтобы в будущем легко было понять, что проверяется. Переименование тестов и описательных фрагментов помогает улучшить читаемость.
Пример:
test('должен проверять правильность суммы', () => {
const result = 1 + 2;
expect(result).toBe(3);
});
Рефакторинг:
test('проверка сложения двух чисел', () => {
const result = 1 + 2;
expect(result).toBe(3);
});Использование моков и шпионов
Моки и шпионы позволяют тестировать взаимодействие с внешними зависимостями. Это помогает не только улучшить тесты, но и изолировать их от изменений в сторонних библиотеках или API.
Пример с использованием мока:
const fetchData = jest.fn().mockResolvedValue({ data: 'test' });
test('должен проверять данные от API', async () => {
const response = await fetchData();
expect(response.data).toBe('test');
});После выполнения рефакторинга важно убедиться, что тесты не только стали более читаемыми и удобными для поддержки, но и что они всё ещё работают корректно. Для этого следует:
Правильный рефакторинг тестов позволяет улучшить не только их структуру, но и процесс тестирования в целом, делая его более эффективным и понятным.