Регрессия — это баг, который появляется после внесения изменений в код, хотя до этого тот же функционал работал корректно. Регрессия означает, что новое изменение «сломало» то, что уже было написано и протестировано ранее. Это одна из самых частых и опасных проблем в разработке: исправляя один баг, разработчик может незаметно сломать три других функции. По данным Capers Jones Software Engineering 2023, средняя плотность регрессионных багов составляет 1–3 на каждые 100 изменённых строк кода. Разбираем причины регрессий, методы их обнаружения и стратегии предотвращения.
Главное
Регрессия — это ситуация, когда функционал, который работал в предыдущей версии, перестаёт работать после внесения изменений. Изменение может быть любым: исправление бага, добавление новой фичи, рефакторинг, обновление библиотеки или даже изменение конфигурации. Регрессия — главный враг стабильности: каждое изменение рискует сломать что-то, что уже было проверено и выпущено.
Термин пришёл из тестирования: регрессионное тестирование — это повторный прогон существующих тестов после каждого изменения. Если ранее проходивший тест падает — значит произошла регрессия. В широком смысле регрессия — это не только падение теста, но и любое ухудшение поведения, замеченное пользователем или QA. По данным Tricentis State of Testing 2023, регрессии составляют 35–45% всех багов, находимых в production.
От обычного бага регрессия отличается временным контекстом: баг мог существовать всегда, а регрессия — это всегда результат изменения. Это важное отличие, потому что поиск причины регрессии начинается с анализа изменений: что было изменено между «работало» и «перестало работать». Git bisect — стандартный инструмент для поиска коммита, вызвавшего регрессию.
Локальная регрессия — изменение в модуле A ломает функционал в том же модуле A. Пример: разработчик переписывает функцию сортировки, и она перестаёт правильно обрабатывать пустой массив. Локальная регрессия — самая простая для обнаружения и исправления, потому что причина и следствие находятся рядом.
Удалённая регрессия — изменение в модуле A ломает функционал в модуле B, который не связан напрямую по коду, но связан по данным или по времени. Пример: изменение схемы базы данных в модуле «Пользователи» ломает отчёт в модуле «Аналитика», который использует ту же таблицу. Удалённые регрессии — самые коварные: разработчик не подозревает, что его изменение повлияет на другой модуль.
Side-effect регрессия — изменение побочного эффекта (логирование, кэширование, отправка уведомлений) ломает ожидаемое поведение. Пример: разработчик добавил кэширование для ускорения работы, но из-за stale cache пользователи видят устаревшие данные. Side-effect регрессии сложно ловить автоматическими тестами, потому что побочные эффекты часто не покрыты тестами.
Performance регрессия — код продолжает работать корректно функционально, но медленнее, чем раньше. Пример: новый алгоритм шифрования даёт те же результаты, но время выполнения выросло с 2 мс до 200 мс. Performance регрессии не ловятся обычными unit-тестами — нужны бенчмарки и профилирование.
| Тип регрессии | Пример | Способ обнаружения |
|---|---|---|
| Локальная | Сломанная сортировка | Unit-тесты |
| Удалённая | Изменение схемы БД | Интеграционные тесты |
| Side-effect | Stale cache | E2E-тесты |
| Performance | Замедление ответа | Бенчмарки |
Первая причина — связанность кода (coupling). Чем сильнее модули зависят друг от друга, тем выше вероятность, что изменение в одном вызовет регрессию в другом. Классические антипаттерны: God Object (объект, который делает всё), Shotgun Surgery (изменение одного требует правки в десятке мест), Circular Dependency. Снижение coupling — задача архитектуры: SOLID-принципы, Dependency Injection, гексагональная архитектура.
Вторая причина — отсутствие тестов для изменяемого функционала. Если код не покрыт тестами, разработчик узнаёт о регрессии только от QA или пользователей. По данным Google Testing Blog, проекты с покрытием тестов >75% имеют в 5 раз меньше регрессий, чем проекты с покрытием <25%. TDD (Test-Driven Development) гарантирует, что тесты написаны до кода, а не «когда будет время».
Третья причина — человеческий фактор. Разработчик не знает о существовании смежного функционала, не понимает всех зависимостей или просто торопится. Причина — недостаточная кодовая база knowledge sharing. Решения: code review с участием разработчиков из других модулей, pair programming, документация архитектуры. Bus factor проекта обратно пропорционален количеству зафиксированных архитектурных решений.
Регрессионное тестирование — это процесс повторного выполнения существующих тестов после каждого изменения для проверки, что старая функциональность не сломалась. Это единственный способ гарантировать, что новое изменение не нарушило работу существующего кода. Без регрессионного тестирования каждый релиз — это лотерея: разработчик надеется, что ничего не сломал, но не может это подтвердить.
Ручное регрессионное тестирование — самый дорогой и неэффективный подход. По мере роста проекта количество регрессионных тестовых сценариев растёт линейно, а время на их ручной прогон — экспоненциально. Через 2–3 года разработки ручной прогон регрессии может занимать 2–3 недели, что делает частые релизы невозможными. Единственный выход — автоматизация.
Автоматизированное регрессионное тестирование делится на уровни по пирамиде тестирования:
По данным Google Testing Blog, оптимальное соотношение: 70% unit-тестов, 20% интеграционных, 10% E2E. Отклонение от этой пропорции снижает эффективность регрессионного тестирования: избыток E2E-тестов замедляет пайплайн, недостаток unit-тестов оставляет микробаги незамеченными.
Первая стратегия — Full Regression. Запускаются все тесты проекта. Самый надёжный, но и самый медленный подход. Применим для небольших проектов (до 10 000 тестов, время прогона <30 минут). Для крупных проектов full regression может занимать часы, что делает CI/CD пайплайн непрактичным.
Вторая стратегия — Selective Regression. Запускаются только тесты, связанные с изменённым кодом. Для определения связей используется dependency graph кода. Инструменты: Bazel (Google), Nx (JavaScript), sbt (Scala). Selective regression экономит 60–80% времени прогона, но требует точного построения dependency graph — ошибки приводят к пропущенным регрессиям.
Третья стратегия — Prioritized Regression. Все тесты ранжируются по приоритету: critical path (самые важные пользовательские сценарии), high risk (код с историей багов), changed code (код, затронутый изменением). Сначала запускаются самые приоритетные тесты — если они проходят, разработчик получает быструю обратную связь. Time-boxed прогон: за 10 минут проверяются critical-тесты, остальные — в фоне.
Первый и самый важный шаг — культура написания тестов. Каждое изменение должно сопровождаться тестом, который проверяет, что изменение работает, и тестом, который проверяет, что ничего не сломалось. TDD (Test-Driven Development) даёт лучшие результаты: разработчик сначала пишет падающий тест, потом — код, который его проходит. Это гарантирует, что тест существует до кода.
Второй шаг — CI/CD пайплайн с обязательным прогоном тестов. Pull request не может быть смёржен, пока все тесты не пройдены. Нельзя «пропустить» тесты из-за срочности — срочные изменения проходят ускоренный, но обязательный набор тестов. По данным Google DevOps Research, команды с обязательным CI/CD имеют в 3 раза меньше регрессий в production.
Третий шаг — мониторинг в production. Даже лучшие тесты не гарантируют 100% защиты от регрессий. Инструменты observability (Sentry, Datadog, New Relic) должны отслеживать ключевые метрики после каждого деплоя: error rate, latency, throughput. Автоматический откат (rollback) при превышении порогов — подушка безопасности, если регрессия всё же попала в продакшен.
Четвёртый шаг — code review с регрессионным мышлением. Ревьюер должен задавать вопрос: «Какие ещё модули могут сломаться от этого изменения?». Не достаточно проверить, что код корректен — нужно проверить, что он не нарушит смежный функционал. Чек-лист для code review должен включать пункт «проверка на регрессии в смежных модулях».
Часто задаваемые вопросы
Регрессия — это баг, которого не было раньше. Обычный баг мог существовать с момента создания фичи. Регрессия всегда привязана к конкретному изменению — это позволяет использовать git bisect для поиска причины.
Используйте git bisect: укажите коммит, где всё работало, и коммит, где сломалось. Git выполнит бинарный поиск по истории и найдёт коммит, вызвавший регрессию. Это работает даже для больших проектов с тысячами коммитов.
Однозначного числа нет, но есть эмпирическое правило: покрытие ключевых user flows должно быть 100%, покрытие всех функций — не менее 70%. Важнее не количество, а качество: тест, проверяющий edge case, ценнее десяти тестов на happy path.
Да, и это называется infrastructure regression. Обновление ОС, версии базы данных, SSL-сертификата или конфигурации веб-сервера может сломать работавший код. IaC (Infrastructure as Code) и тестирование инфраструктуры (Test Kitchen, Terratest) помогают ловить такие регрессии.
Начните с одного critical user flow. Напишите автоматический тест для самого важного сценария (логин, оформление заказа). Покажите на демо, как тест ловит регрессию. Когда команда увидит пользу — внедряйте тестирование постепенно, расширяя покрытие.
Итоги
Мы разработаем мобильное приложение под ключ
IT Sectr создаёт приложения для iOS и Android для стартапов и бизнеса с 2017 года. Мы проконсультируем вас и предложим наилучшее решение.
Читайте также