Соглашения по именованию

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

1. Названия тестов

Основное правило для именования тестов в Jest — название теста должно быть простым, понятным и точно отражать ожидаемое поведение. Используется функция test(), в которой первым аргументом является строка, описывающая цель теста.

Пример:

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

Здесь строка 'должен правильно суммировать два числа' чётко сообщает, что тест проверяет правильность работы функции суммирования.

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

Пример плохого названия:

test('должен работать', () => {
  expect(sum(1, 2)).toBe(3);
});

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

2. Названия блоков с описанием поведения: describe()

Для группировки схожих тестов используется блок describe(). Это также помогает обеспечить структуру и улучшить читаемость. Название блока describe() должно описывать сущность, функциональность или компонент, который проверяется, а сами тесты в нём — поведение этой сущности.

Пример:

describe('функция суммирования', () => {
  test('должна правильно суммировать два числа', () => {
    expect(sum(1, 2)).toBe(3);
  });

  test('должна возвращать ноль при суммировании с нулём', () => {
    expect(sum(0, 2)).toBe(2);
  });
});

Если блок describe() охватывает более широкую функциональность, то можно использовать более высокоуровневое описание:

describe('математические операции', () => {
  describe('функция суммирования', () => {
    test('должна правильно суммировать два числа', () => {
      expect(sum(1, 2)).toBe(3);
    });
  });

  describe('функция умножения', () => {
    test('должна правильно умножать два числа', () => {
      expect(multiply(2, 3)).toBe(6);
    });
  });
});

При этом важно, чтобы название блока не дублировало название тестов. То есть, не следует использовать фразы вроде «тестирование функции суммирования» — это уже очевидно из контекста.

3. Использование it()

Функция it() является синонимом test(), и выбор между ними зависит исключительно от предпочтений команды. Однако предпочтительнее использовать test() для простоты, а it() — в случаях, когда это помогает структуре или читаемости теста.

Пример с it():

describe('функция суммирования', () => {
  it('должна правильно суммировать два числа', () => {
    expect(sum(1, 2)).toBe(3);
  });
});

Главное — придерживаться единообразия: если в одном проекте используются test(), то не стоит использовать it() в другом месте, чтобы не создавать путаницу.

4. Стратегия для сложных тестов

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

Пример:

describe('функция работы с пользователями', () => {
  describe('регистрация нового пользователя', () => {
    test('должна создавать нового пользователя с правильными данными', () => {
      expect(registerUser({ name: 'Иван' })).toEqual({ id: 1, name: 'Иван' });
    });

    test('должна возвращать ошибку при отсутствии данных', () => {
      expect(registerUser({})).toThrow('Ошибка данных');
    });
  });

  describe('авторизация пользователя', () => {
    test('должна авторизовать пользователя с правильными данными', () => {
      expect(loginUser({ name: 'Иван', password: '12345' })).toBe(true);
    });
  });
});

5. Стандарты для переменных и функций в тестах

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

Пример:

const validUserData = { name: 'Иван', password: '12345' };
const invalidUserData = { name: '', password: '12345' };

test('должна правильно авторизовать пользователя с корректными данными', () => {
  expect(loginUser(validUserData)).toBe(true);
});

test('должна вернуть ошибку при неправильных данных', () => {
  expect(() => loginUser(invalidUserData)).toThrow('Неверные данные');
});

6. Обработка асинхронных тестов

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

Пример:

test('должна возвращать данные о пользователе после загрузки', async () => {
  const user = await fetchUserData();
  expect(user.name).toBe('Иван');
});

Использование async/await рекомендуется для упрощения работы с асинхронными тестами, однако при необходимости можно использовать .then(), если это более подходит для конкретного случая.

7. Переиспользуемые вспомогательные функции

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

Пример:

function setupTestData() {
  return { name: 'Иван', password: '12345' };
}

test('должна правильно авторизовать пользователя', () => {
  const user = setupTestData();
  expect(loginUser(user)).toBe(true);
});

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

8. Стратегии для группировки тестов

Для группировки тестов, которые имеют общий контекст или цель, следует использовать вложенные блоки describe(). Это помогает избежать повторения, особенно если один тест зависит от данных, созданных другим тестом.

Пример:

describe('работа с пользователями', () => {
  let user;

  beforeEach(() => {
    user = createUser({ name: 'Иван' });
  });

  describe('регистрация', () => {
    test('должна создавать нового пользователя', () => {
      expect(user.name).toBe('Иван');
    });
  });

  describe('удаление', () => {
    test('должна удалять пользователя', () => {
      deleteUser(user);
      expect(getUser(user.id)).toBeNull();
    });
  });
});

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

9. Обработка ошибок и исключений

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

Пример:

test('должна выбрасывать ошибку при отсутствии обязательного параметра', () => {
  expect(() => createUser()).toThrowError('Имя обязательно');
});

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

Заключение

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