End-to-end тест проверяет систему так, как её видит пользователь: запускает приложение в среде, близкой к рабочей, взаимодействует с ним через браузер и наблюдает результат на публичных границах. Обычно один такой тест проходит сквозь весь стек - UI, frontend, HTTP, backend и базу данных - и утверждает не про отдельную функцию, а про то, что все части соединены и работают вместе. В этом его уникальная сила: ничто другое не доказывает связность системы целиком.
Место E2E проще всего понять через пирамиду уровней. Юнит-тест проверяет точное правило и делает это быстро, но ничего не говорит о том, соединяются ли части. Интеграционный проверяет границу между модулями или с базой, но не весь пользовательский путь. E2E проходит критический путь целиком - и именно поэтому не может и не должен проверять все комбинации бизнес-логики: это работа нижних уровней, где каждая проверка дешевле и точнее локализует поломку.
| Уровень | Сильная сторона | Не доказывает |
|---|---|---|
| Unit | Точное правило, быстро | Связность частей |
| Integration | Граница модулей и базы | Полный пользовательский путь |
| E2E | Критический путь целиком | Все комбинации бизнес-логики |
Отсюда следует главный принцип экономии. E2E-тесты дороги во всём: их дольше писать, дольше прогонять, тяжелее поддерживать, и каждый из них - обязательство, которое кто-то будет чинить при следующем изменении интерфейса. Поэтому нельзя переносить в E2E все юнит-сценарии: набор раздувается, замедляет CI и рассыпается от любой правки. E2E берут на себя немногое, но самое ценное.
Что именно ценно - определяет бизнес, а не покрытие. Проверять стоит несколько самых дорогих обещаний системы: вход в аккаунт, покупку, публикацию, проверку прав доступа, восстановление после сбоя. Это пути, поломка которых стоит компании денег или репутации напрямую. Полдюжины таких сценариев, работающих надёжно, дают больше уверенности, чем сотня мелких E2E, дублирующих юнит-логику через браузер.
Оговорка про "всё настоящее" важна. E2E ценят за реальную связность, но это не значит, что в тесте должно тратиться настоящее. Платёжный провайдер, отправку SMS, внешний OAuth или опасную интеграцию заменяют sandbox или контролируемым fake - но делают это на внешней границе системы, а не внутри неё. Суть E2E - доказать, что ваша система соединена и работает, а не провести реальный платёж на каждый прогон.
Граница "настоящего" проходит по владению. Всё, что принадлежит вам - frontend, ваш API, ваша база, - в E2E должно быть настоящим: именно их связность вы и проверяете. Всё, что снаружи, нестабильно или опасно - чужой платёж, погодный сервис, СМС-шлюз, - заменяют управляемой границей. Спутать эти две стороны - типичная ошибка: замокав собственный API, тест перестаёт что-либо доказывать про интеграцию, ради которой написан.
Полезно один раз свести уровни и то, что каждый доказывает, чтобы не спорить об этом в каждом code review. Ниже - короткая карта, к которой возвращаешься, когда решаешь, на каком уровне ловить конкретный риск: правило - юнитом, границу - интеграцией, дорогой сквозной путь - E2E.
Типичные провалы этого уровня растут из непонимания его роли. Перенести все юнит-случаи в E2E - получить медленный и хрупкий набор, который проверяет то, что дешевле поймать ниже. Замокать собственную систему ради стабильности - получить зелёный тест, не доказывающий связность. И гнаться за покрытием комбинаций бизнес-логики через браузер - тратить часы прогона на то, для чего E2E не предназначен. Держите на этом уровне немногое и самое дорогое.