Тестирование является неотъемлемой частью разработки программного обеспечения, и качество тестов напрямую влияет на стабильность и надежность конечного продукта. Важным элементом успешного тестирования является правильное написание тестов, которые не только проверяют функциональность, но и обеспечивают высокий уровень надежности. Рассмотрим основные принципы написания качественных тестов с использованием Jasmine.
Тесты — это не просто код, который выполняет проверку функциональности. Они должны быть написаны так, чтобы другие разработчики (или даже будущий вы) могли легко понять, что именно проверяется и почему.
Принцип: Код теста должен быть простым, ясным и самодокументируемым. Хороший тест говорит сам за себя.
Пример:
describe('Функция сложения', function() {
it('должна возвращать правильный результат при сложении двух чисел', function() {
const результат = сложение(2, 3);
expect(результат).toBe(5);
});
});
В этом примере ясно, что тест проверяет функцию сложения, и что ожидаемый результат — это 5 при сложении 2 и 3.
Каждый тест должен быть независимым. Это означает, что тесты не должны зависеть от порядка их выполнения или от состояния других тестов.
Принцип: Каждый тест должен проверять только одну вещь, и он должен быть полностью независимым от других.
Пример:
describe('Функция деления', function() {
it('должна корректно делить положительные числа', function() {
const результат = деление(6, 2);
expect(результат).toBe(3);
});
it('должна выбрасывать ошибку при делении на ноль', function() {
expect(function() { деление(6, 0); }).toThrowError('Деление на ноль');
});
});
Каждый тест изолирован от других, проверяя только одну конкретную ситуацию. Таким образом, тесты не будут конфликтовать друг с другом.
Медленные тесты могут стать проблемой при их большом количестве. Если тесты выполняются слишком долго, это может замедлить процесс разработки и уменьшить эффективность CI/CD процессов.
Принцип: Тесты должны выполняться быстро. Задача — минимизировать время, необходимое для их запуска.
Пример:
describe('Функция умножения', function() {
it('должна возвращать правильный результат при умножении двух чисел', function() {
const результат = умножение(1000, 1000);
expect(результат).toBe(1000000);
});
});
Задача здесь — избежать сложных вычислений или обращения к внешним ресурсам (например, к базам данных или веб-сервисам), которые могут значительно замедлить выполнение тестов.
Тесты должны быть детерминированными, то есть их результаты не должны зависеть от внешних факторов, таких как время, случайные числа или состояние системы.
Принцип: Тесты должны быть стабильными и повторяемыми. Каждый запуск тестов должен давать одинаковый результат.
Пример:
describe('Функция получения текущей даты', function() {
it('должна возвращать сегодняшнюю дату', function() {
const сегодня = new Date();
const результат = getCurrentDate();
expect(результат).toBe(sегодня.toDateString());
});
});
В данном примере функция getCurrentDate должна
возвращать дату в определенном формате. Однако если не контролировать
точное время, тест может быть нестабильным.
Тесты должны проверять как положительные, так и отрицательные сценарии, а также пограничные случаи.
Принцип: Все возможные случаи, включая ошибки и исключения, должны быть покрыты тестами.
Пример:
describe('Функция извлечения элемента массива', function() {
it('должна возвращать элемент по индексу', function() {
const массив = [1, 2, 3];
const результат = извлечьЭлемент(массив, 1);
expect(результат).toBe(2);
});
it('должна выбрасывать ошибку, если индекс вне диапазона', function() {
const массив = [1, 2, 3];
expect(function() { извлечьЭлемент(массив, 5); }).toThrowError('Индекс вне диапазона');
});
});
В этом примере рассматриваются два возможных сценария: извлечение элемента по индексу и обработка ошибки, если индекс выходит за пределы массива.
Тест должен быть как можно более простым и проверять только одну конкретную функциональность. Это помогает выявить проблему, если тест не прошел, и упростить процесс отладки.
Принцип: Каждый тест проверяет только одну логику.
Пример:
describe('Функция проверки пароля', function() {
it('должна принимать правильный пароль', function() {
const результат = проверкаПароля('ПравильныйПароль123');
expect(результат).toBe(true);
});
it('должна отклонять неправильный пароль', function() {
const результат = проверкаПароля('НеправильныйПароль');
expect(результат).toBe(false);
});
});
Здесь каждый тест проверяет только одну сторону проблемы: верный и неверный пароли. Это помогает легче локализовать ошибку, если тест не проходит.
Важно, чтобы тесты были максимально точными. Даже если проверяется простая логика, следует описывать каждый аспект теста, чтобы не возникало путаницы.
Принцип: Каждый аспект функциональности должен быть тщательно описан в тестах.
Пример:
describe('Функция форматирования строки', function() {
it('должна преобразовывать строку в верхний регистр', function() {
const результат = toUpperCase('тест');
expect(результат).toBe('ТЕСТ');
});
it('должна преобразовывать строку в нижний регистр', function() {
const результат = toLowerCase('ТЕСТ');
expect(результат).toBe('тест');
});
});
Каждое поведение функции в тестах описано с максимальной точностью, что помогает избежать ошибок и недопонимания.
При росте проекта количество тестов будет увеличиваться. Поэтому необходимо поддерживать тесты в таком виде, чтобы добавление новых проверок не вызывало проблем.
Принцип: Структура тестов должна быть такой, чтобы новые тесты можно было добавлять без сложности.
Пример:
describe('Математические операции', function() {
describe('Сложение', function() {
it('должно возвращать правильный результат', function() {
expect(сложение(1, 2)).toBe(3);
});
});
describe('Вычитание', function() {
it('должно возвращать правильный результат', function() {
expect(вычитание(5, 2)).toBe(3);
});
});
});
Такой подход позволяет добавлять новые операции и тесты без значительных изменений в структуре.
Наличие тестов, которые не добавляют ценности или не проверяют функциональность, является плохой практикой. Каждый тест должен вносить вклад в проверку работоспособности системы.
Принцип: Тесты должны быть релевантными и реально проверять функциональность.
Пример:
describe('Функция проверки возраста', function() {
it('должна возвращать true для пользователей старше 18 лет', function() {
expect(проверкаВозраст(20)).toBe(true);
});
it('должна возвращать false для пользователей младше 18 лет', function() {
expect(проверкаВозраст(17)).toBe(false);
});
});
Этот тест проверяет функциональность, которая имеет реальное значение для работы приложения.
Применение строгих стандартов написания кода и стилей в тестах помогает сохранять тесты в чистом и понятном виде.
Принцип: Следует придерживаться общих стандартов и стилей кода для повышения читаемости.
Пример:
describe('Функция сортировки', function() {
it('должна сортировать массив чисел по возрастанию', function() {
const результат = сортировка([3, 1, 2