Однако время выполнения теста — это всего лишь синтетическая метрика, которой недостаточно для принятия обоснованного решения о приоритете различных видов тестирования. Наша главная цель — не просто «прогнать тесты быстро», а гарантировать соответствие кода требованиям (как функциональным, так и нефункциональным).
Предлагаю рассмотреть две ключевые характеристики:
- Время получения обратной связи от теста
- Качество обратной связи
Время выполнения теста и время получения обратной связи — это разные вещи. Допустим, у нас есть E2E-тесты, которые запускаются на выделенном контуре. Чтобы проверить свежий код, необходимо:
- Закоммитить изменения
- Дождаться прохождения quality gates
- Провести ревью (если оно требуется перед выкатыванием на стенд)
- Выкатить изменения на стенд
- Запустить тесты
Таким образом, время от момента готовности кода до получения результатов тестов — это и есть время получения обратной связи. Если сами E2E-тесты выполняются, например, за час, то фактическое время получения обратной связи может исчисляться часами или даже днями.
При сравнении unit-тестов, интеграционных, компонентных и E2E-тестов наблюдается обратная зависимость: чем быстрее тест даёт обратную связь, тем менее точную и полную информацию о работоспособности системы он предоставляет — и наоборот.
Интеграционные тесты представляют собой компромиссное решение: они обеспечивают качество обратной связи, приближенное к E2E, но при этом их выполнение занимает время, сопоставимое с unit-тестами.
При этом интеграционные тесты не «цементируют» код так, как это делают unit-тесты. Они позволяют сохранять гибкость (evolvability) — способность кода изменяться с минимальными затратами на сопровождение. Это особенно важно для сервисов на ранних этапах их развития.
Такой подход отражён во многих книгах, включая Angry Tests Е. Бугаенко и Принципы юнит-тестирования В. Хорикова.
Важно не время выполнения теста, а время получения обратной связи от теста о работоспособности системы. Т.е. есть код и нужно проверить выполняет ли он требуемый контракт. Время выполнения тесты и время получения обратной связи это разные вещи. Давайте представим, что у нас есть e2e тесты и они запускаются на специально выделенном контуре. Чтобы проверить свежий код, нужно его закомитить, подождать прохождения quality gates, возможность review, если оно требуется для выката на стенд. Выкатить на стенд. Запустить тесты. Вот все это время от готовности кода к проверки и фактического запуска теста против этого кода и есть время получения обратной связи. Если для unit тестов это одна и та же величина, то для e2e это разные величины. При этом для e2e это время может измерятся часами, а то и больше. В то время как для unit тестов это миллисекунды.
Интеграционные контрактные тесты по качеству обратной связи ближе к e2e теста, но по скорости ее получения ближе к unit.
Время выполнения теста
Время выполнения теста это синтетическая характеристика, которая по сути ни о чем не говорит. Предлагаю рассмотреть такие:
- скорость получения обратной связи от теста
- качество обратной связи
Смотри, есть такие типы тестирования:
- unit
- интеграционные тесты (поднимается все приложение, внепроцессные зависимости мокируются c wiremock, проверка через api приложения)
- component testing (поднимается приложение и все его внепроцесные зависимости в docker и тестируется набором тестов похожим на e2e, но облегченным)
- e2e тесты
Баланс между скоростью и качеством обратной связи у разных видов тестов
При сравнении unit-тестов, интеграционных, компонентных и E2E (end-to-end) тестов наблюдается обратная зависимость: чем быстрее тест дает обратную связь, тем менее всеобъемлющую информацию о работоспособности системы он предоставляет, и наоборот. Ниже приведен график, иллюстрирующий эту зависимость (ось X – скорость получения обратной связи, ось Y – «качество» обратной связи, подразумевающее степень уверенности в корректной работе системы):
↑ Качество обратной связи (выше лучше)
| ● E2E-тесты (сквозные)
| ● Интеграционные тесты
| ● Компонентные тесты
| ● Unit-тесты
+-----------------------------------------→ Скорость обратной связи (быстрее)
Unit-тесты. Юнит-тесты проверяют небольшие участки кода в изоляции. Они дают практически мгновенный фидбэк – выполняются за миллисекунды, что обеспечивает очень высокую скорость обратной связи. Однако охват у них самый узкий: unit-тест гарантирует корректность отдельного модуля (функции, класса), но не выявляет проблем во взаимодействии компонентов. Качество обратной связи от таких тестов относительно невысокое в том смысле, что зелёные юнит-тесты еще не гарантируют работоспособность системы на уровне бизнес-сценариев. Зато сами по себе они чрезвычайно точечно указывают на место сбоя и легко локализуют дефекты в конкретном модуле.
Интеграционные тесты. Интеграционные тесты проверяют совместную работу нескольких модулей или сервисов. Они работают медленнее юнит-тестов (порядка секунд на тест), поэтому фидбэк приходит не так быстро. Зато эти тесты покрывают взаимодействия компонентов, повышая уверенность в том, что части системы правильно работают вместе. По качеству обратной связи интеграционные тесты занимают промежуточное положение: они выявляют дефекты на стыках модулей (что не видно юнит-тестам), хотя всё еще не охватывают систему целиком. При падении интеграционного теста приходится немного потрудиться, чтобы найти, в каком компоненте сбой, но обычно локализовать проблему проще, чем с E2E.
Компонентные тесты. Компонентное тестирование можно рассматривать как проверку более крупного блока системы в относительно изолированной среде (например, тестирование отдельного сервиса целиком с его зависимостями, или UI-компонента со всеми подкомпонентами). По скорости такие тесты уступают юнит-тестам, так как затрагивают больше кода и могут требовать поднятия дополнительных частей системы (базы данных, оболочки приложения и т.д.). Однако компонентные тесты обычно быстрее полноценных сквозных E2E, поскольку сфокусированы на одном компоненте и могут выполнять часть действий в контролируемой среде. Качество обратной связи от компонентных тестов выше, чем у низкоуровневых тестов: они дают уверенность, что отдельный компонент (подсистема) в целом выполняет требуемые функции. Тем не менее, они не заменяют E2E, так как не охватывают взаимодействие данного компонента с остальной системой.
E2E-тесты. End-to-end тесты имитируют работу системы глазами пользователя, проверяя полный пользовательский сценарий от начала до конца. Такие тесты дают наибольшую ценность и качество обратной связи – высокий уровень уверенности, что вся система соответствует ожиданиям конечного пользователя. Иными словами, если E2E-тесты проходят, значит основные бизнес-функции работают корректно в реальных условиях. Однако расплата за это – низкая скорость фидбэка. E2E-тесты самые медленные: один сценарий может выполняться десятки секунд или минуты, а полный прогон большого набора E2E способен занять часы. Это значительно замедляет цикл обратной связи разработчика с системой. Кроме того, в случае падения E2E-теста известен лишь факт сбоя в сценарии, но не очевидна причина на уровне кода – приходится тратить время на поиски, в каком сервисе или модуле произошла ошибка. Таким образом, сквозные тесты обеспечивают самую полную проверку (высокое качество обратной связи), но при самой медленной отдаче результата.
Вывод: быстрые низкоуровневые тесты (юнит) дают мгновенную обратную связь, но проверяют ограниченный контекст, тогда как медленные сквозные тесты охватывают систему целиком, давая более ценную информацию о её работе. Интеграционные и компонентные тесты занимают промежуточный баланс между этими крайностями. На графике это отражено положением точек: юнит-тесты – в правом нижнем углу (максимальная скорость, минимальный охват), E2E – в левом верхнем (низкая скорость, максимальный охват), а интеграционное и компонентное тестирование располагаются между ними по диагонали. Такая диаграмма подчёркивает необходимость комбинировать разные уровни тестов, чтобы команда получала и быструю обратную связь при изменениях, и уверенность в качестве продукта на высоком уровне. Например, Google рекомендует стратегию тестирования с преобладанием быстрых юнит-тестов, дополняемых некоторым количеством интеграционных и небольшим числом E2E-тестов – это позволяет добиться разумного баланса между скоростью и качеством обратной связи.
Интеграционные тесты медленнее unit
Факт, но время выполнения теста это синтетическая характеристика, которой не достаточно для того, чтобы качественно принять решение о том, какому виду тестирования отдавать предпочтение. Наша главная задача - не выполнить тесты быстро, а гарантировать соблюдением кодом требований (функциональные и не функциональные). Предлагаю рассмотреть такие характеристики:
- время получения обратной связи от теста
- качество обратной связи
Время выполнения теста и время получения обратной связи это разные вещи. Давайте представим, что у нас есть e2e тесты и они запускаются на специально выделенном контуре. Чтобы проверить свежий код, нужно его закомитить, подождать прохождения quality gates, возможность review, если оно требуется для выката на стенд. Выкатить на стенд. Запустить тесты. Вот все это время от готовности кода к проверке и фактического завершения запуска теста против этого кода и есть время получения обратной связи. Для e2e это могут быть как часы, так и дни.
При сравнении unit-тестов, интеграционных, компонентных и E2E (end-to-end) тестов наблюдается обратная зависимость: чем быстрее тест дает обратную связь, тем менее точную и полную информацию о работоспособности системы он предоставляет, и наоборот.
Интеграционные тесты это компромисное решение, которое дает качество обратной связи ближе к e2e, а время получение этой связи близкое к unit.
При этом интеграционные тесты не цементируют код, как это делают unit тесты и позволяют выдерживать кодом характеристику evolvability (способность меняться, быть гибким, легким, минимизируют издержки на сопровождение), что особенно важно для сервисов на их раннем этапе. Данное утверждение находит отражение в книгах многих авторов, в том числе у Е. Бугаенко Angry Tests и у В. Хориков Принципы юнит-тестирования.