Тестирование в программировании — неотъемлемая часть процесса разработки, и важно, чтобы написанные тесты были точными и эффективно проверяли лишь одну функциональность. Принцип “Один тест — одна проверка” помогает избежать создания сложных, неясных и избыточных тестов. В его основе лежит идеология того, что каждый тест должен фокусироваться только на одной задаче, проверяя конкретное поведение компонента, функции или модуля. Этот подход способствует улучшению качества тестов и повышению их читаемости, что особенно важно в крупных проектах.
Под одним тестом понимается такой тест, который выполняет одну проверку: он должен удостовериться, что не более одной вещи работает как ожидается. В случае с более сложными сценариями тестирование может быть выполнено несколькими тестами, каждый из которых фокусируется на отдельной части поведения программы.
Этот принцип имеет несколько целей:
Допустим, есть функция, которая форматирует строку с датой и выводит её в нужном формате. Мы могли бы написать тест, который проверяет как корректность формирования даты, так и её корректный вывод.
test('correctly formats and prints the date', () => {
const input = new Date('2023-01-01');
const result = formatAndPrintDate(input);
expect(result).toBe('01-01-2023');
expect(console.log).toHaveBeenCalledWith('01-01-2023');
});
В этом примере тест проверяет сразу два аспекта: корректность форматирования даты и правильность вывода в консоль. Такой подход нарушает принцип “Один тест — одна проверка”, поскольку возникает две возможные причины ошибки. Если тест не пройдет, будет трудно понять, что именно вызвало сбой.
Лучше будет разделить эти две проверки на отдельные тесты:
test('correctly formats the date', () => {
const input = new Date('2023-01-01');
const result = formatDate(input);
expect(result).toBe('01-01-2023');
});
test('correctly prints the date to console', () => {
const input = new Date('2023-01-01');
formatAndPrintDate(input);
expect(console.log).toHaveBeenCalledWith('01-01-2023');
});
Теперь каждый тест решает только одну задачу: первый тест проверяет правильность форматирования, а второй — корректность вывода. Такой подход позволяет легко изолировать ошибку, если тест не пройдет.
Если тесты проверяют только одну вещь, в случае ошибки легко понять, что именно пошло не так. Например, в тестах выше, если тест на форматирование даты не пройдет, причина будет очевидной — ошибка в логике форматирования. Если же не пройдет тест на вывод в консоль, можно быть уверенным, что проблема именно в этом компоненте.
Когда добавляются новые функции или меняются старые, тесты, проверяющие лишь одну задачу, проще адаптировать под новые условия. Например, если в будущем появится новый формат даты, можно будет написать новый тест, который проверит его правильность, не затрагивая уже существующие тесты.
Тесты, которые проверяют несколько аспектов, могут давать ложные срабатывания. Когда ошибка возникает в одном месте, она может повлиять на другие части теста, которые, в свою очередь, могут не быть связаны с основной проблемой. Это затрудняет диагностику и увеличивает время на устранение ошибки.
При модульном тестировании, когда каждый модуль или функция программы проверяется отдельно, принцип “Один тест — одна проверка” легко применим. Каждый модуль обычно отвечает за свою небольшую задачу, и поэтому тесты могут быть написаны с фокусом на одну конкретную задачу.
Пример:
// Модуль, который проверяет правильность расчета налога
const calculateTax = (amount, taxRate) => {
return amount * taxRate;
};
test('correctly calculates tax', () => {
const result = calculateTax(100, 0.2);
expect(result).toBe(20);
});
Здесь проверяется только одна вещь: правильность расчета налога. Поскольку это простая функция, она имеет одно поведение, и тест соответствует принципу.
При тестировании компонентов в фреймворках, таких как React, Vue или Angular, принцип “Один тест — одна проверка” также полезен. В большинстве случаев компонент может иметь несколько состояний или вызывать различные методы, но лучше, если каждый тест проверяет только одно состояние или поведение компонента.
Пример для React-компонента:
import { render, screen } from '@testing-library/react';
import MyComponent from './MyComponent';
test('renders the title correctly', () => {
render(<MyComponent />);
const titleElement = screen.getByText(/my title/i);
expect(titleElement).toBeInTheDocument();
});
test('button click changes the state', () => {
render(<MyComponent />);
const button = screen.getByRole('button');
button.click();
const textElement = screen.getByText(/new state/i);
expect(textElement).toBeInTheDocument();
});
В этом примере каждый тест проверяет только одно поведение: первый — правильность рендеринга заголовка, второй — изменение состояния при клике на кнопку. Такой подход упрощает поддержку и отладку тестов.
Принцип “Один тест — одна проверка” является важной частью разработки качественного тестового покрытия. Он способствует улучшению читаемости тестов, упрощает поиск ошибок и помогает в поддержке тестов на протяжении всего жизненного цикла проекта. Соблюдение этого принципа важно для создания легко поддерживаемых и понятных тестов, что, в свою очередь, помогает поддерживать высокий уровень качества программного обеспечения.