Как cделать тестирование кода более эффективным: принципы F.I.R.S.T
- четверг, 25 июля 2024 г. в 00:00:08
В последнее время я все больше уделяю внимание юнит тестированию, что связано с моим наставничеством на Hexlet и выравнивание пирамиды на работе. И немного решил освежить основы при написании юнит тестов:
Тесты должны выполняться очень быстро. Время выполнения, включая настройку, сам тест и завершение, должно составлять миллисекунды, так как в проекте может быть тысячи тестов.
Каждый тест должен быть независим. Он должен следовать модели "подготовка, действие, проверка" (Arrange, Act, Assert) без зависимости от других тестов или внешнего окружения.
Тесты должны давать одинаковые результаты в любой среде и в любое время, независимо от внешних условий, таких как дата/время или случайные значения.
Результаты теста должны быть ясны без внешних проверок — тест либо проходит, либо нет, без всякой необходимости в дополнительной интерпретации.
Тесты должны охватывать все возможные сценарии использования и граничные условия, не ограничиваясь простым стремлением к 100% покрытию кода. Важно тестировать различные объемы данных, безопасность, и корректную обработку исключений.
// ❌ Плохой тест, который загружает данные из API
it('fetches and displays user data', async () => {
render(<UserProfile userId="123" />);
await waitFor(() => expect(screen.getByText(/Username/)).toBeInTheDocument());
});
Этот тест медленный, потому что делает реальный запрос к API.
Хороший пример:
// ✅ Использование моков для ускорения тестов,
// чтобы сделать тест быстрым и независимым
jest.mock('api/userApi');
it('displays user data from mock', () => {
userApi.getUser.mockResolvedValue({ id: '123', name: 'John Doe' });
render(<UserProfile userId="123" />);
expect(screen.getByText(/John Doe/)).toBeInTheDocument();
});
Плохой пример:
// ❌ Эти тесты зависимы друг от друга из-за общего состояния хука
const { result } = renderHook(() => useCounter());
it('increments counter', () => {
act(() => { result.current.increment(); });
expect(result.current.count).toBe(1);
});
it('increments counter again', () => {
act(() => { result.current.increment(); });
expect(result.current.count).toBe(2);
});
Хороший пример:
// ✅ Каждый тест изолирован
it('increments counter', () => {
const { result } = renderHook(() => useCounter());
act(() => { result.current.increment(); });
expect(result.current.count).toBe(1);
});
it('increments counter independently', () => {
const { result } = renderHook(() => useCounter());
act(() => { result.current.increment(); });
expect(result.current.count).toBe(1);
});
Плохой пример:
// ❌ Тест зависит от текущей даты
it('shows current date', () => {
render(<CurrentDateDisplay />);
const today = new Date().toISOString().slice(0, 10);
expect(screen.getByText(today)).toBeInTheDocument();
});
Этот тест даст разные результаты каждый день.
Хороший пример:
// ✅ Использование фиксированной даты в тестах,
// что обеспечивает повторяемость результатов
it('shows current date', () => {
jest.useFakeTimers().setSystemTime(new Date('2024-01-01'));
render(<CurrentDateDisplay />);
expect(screen.getByText('2024-01-01')).toBeInTheDocument();
});
Плохой пример:
// ❌ Тест не автоматизирован полностью
it('renders correctly', () => {
const component = render(<MyComponent />);
console.log(component); // Требует проверки вывода
});
Хороший пример:
// ✅ Тест с проверкой
it('renders correctly', () => {
render(<MyComponent />);
expect(screen.getByText('Hello World')).toBeInTheDocument();
});
Этот тест полностью самодостаточен и не требует внешних действий для проверки.
Плохой пример:
// ❌ Тест проверяет только одно состояние компонента
it('shows loading state', () => {
render(<DataLoader />);
expect(screen.getByText('Loading...')).toBeInTheDocument();
});
Тест ограничен только проверкой состояния загрузки.
Хороший пример:
// ✅ Эти тесты охватывают разные возможные состояния компонента
it('shows loading state', () => {
render(<DataLoader />);
expect(screen.getByText('Loading...')).toBeInTheDocument();
});
it('displays data after loading', async () => {
const mockData = { text: 'Data loaded' };
fetchData.mockResolvedValue(mockData);
render(<DataLoader />);
expect(await screen.findByText('Data loaded')).toBeInTheDocument();
});
it('shows error when data fails to load', async () => {
fetchData.mockRejectedValue('Error loading data');
render(<DataLoader />);
expect(await screen.findByText('Error loading data')).toBeInTheDocument();
});
Присоединяйтесь в мой tg, там я пишу различные заметки по QA