Регрессионное тестирование — это повторная проверка уже работавшей функциональности после изменения в коде, конфигурации или окружении, чтобы убедиться, что она не сломалась. То, что работало и перестало, называют регрессией (регрессионным дефектом).
Содержание
Регресс легко спутать с двумя соседними проверками. Ретест (в терминах ISTQB — confirmation testing) отвечает на вопрос «исправлен ли конкретный баг». Смоук-тест — «пригодна ли сборка для дальнейшей проверки вообще». Регресс спрашивает третье: «не сломалось ли что-то рядом и дальше». Ниже — когда его запускать, как собрать набор тестов и как автоматизировать его в CI на примере pytest.
Смоук, ретест и регресс: в чем разница
| Проверка | Вопрос | Объем | Когда |
|---|---|---|---|
| Смоук (smoke) | Сборка вообще работает? | 5-30 ключевых сценариев, минуты | Сразу после сборки или деплоя на стенд |
| Ретест (confirmation) | Исправлен ли именно этот баг? | Шаги из баг-репорта | После фикса дефекта |
| Регресс (regression) | Не сломалось ли то, что работало? | От части набора до полного | После любого изменения, перед релизом |
| Санити (sanity) | Разумно ли ведет себя измененная область? | Узко, вокруг изменения | После небольшой правки |
Термин «санити» в разных источниках трактуют по-разному, иногда как синоним смоука, — важнее договориться о словах внутри команды.
Смоук отсеивает сломанную сборку, ретест подтверждает фикс, регресс проверяет последствия. Успешный ретест не означает, что регресс можно пропустить: фикс мог задеть соседний модуль.
Когда запускать регрессионное тестирование
Поводом служит любое изменение, которое может повлиять на уже работающее поведение:
- исправление дефекта (фикс в одном месте ломает другое чаще, чем кажется);
- новая функциональность, которая использует общий код или данные;
- рефакторинг, даже если «поведение не менялось»;
- обновление зависимостей: библиотеки, фреймворка, драйвера БД, версии языка;
- смена окружения: ОС, версии СУБД, браузера, конфигурации сервера;
- слияние веток и подготовка релиза.
На практике регресс запускают слоями разной глубины: быстрый срез — часто, полный — реже.
| Событие | Что запускать | Типичное время |
|---|---|---|
| Коммит или merge request | Смоук + тесты измененного модуля | Минуты |
| Слияние в основную ветку | Полный автоматический регресс | Десятки минут |
| Ночная сборка | Длинные e2e и интеграционные сценарии | Часы |
| Перед релизом | Полный автоматический регресс + ручные проверки рисковых зон | По плану релиза |
Время в таблице — ориентир для небольшого проекта; у крупной системы полный регресс может идти сутками.
Как выбрать набор тестов для регресса
Полный регресс (все тесты) надежнее, но дорог; частичный быстрее, но пропускает то, что не попало в выборку. Выбор делают по риску:
- Определить, что изменилось. Файлы и модули из diff, затронутые таблицы БД, API, конфигурация.
- Найти зависимости. Кто вызывает измененный код, кто читает те же данные, какие интеграции проходят через модуль.
- Добавить критичные бизнес-сценарии. Регистрация, оплата, оформление заказа — то, без чего продукт теряет деньги или пользователей. Они входят в любой срез.
- Добавить зоны с историей дефектов. Модули, где регрессии уже случались, ломаются снова.
- Добавить тесты на недавно исправленные баги. Каждый исправленный дефект стоит закрепить автотестом, чтобы он не вернулся незаметно.
- Расставить приоритеты. Высокий — критичные сценарии и измененная область; средний — зависимые модули; низкий — редко используемые функции. При нехватке времени срезают снизу.
Набор нужно поддерживать: удалять тесты на удаленную функциональность, чинить нестабильные. Регресс, который краснеет без причины, перестают читать.
Пример: регрессионный тест на pytest
Функция расчета суммы заказа, в которой уже исправляли баг #142: промокод в нижнем регистре не давал скидку. Проверено на Python 3.9 и pytest 8.4.
# pricing.py
def total_price(price: float, qty: int, promo: str = "") -> float:
"""Сумма заказа: скидка 10% по промокоду SALE10."""
if qty < 0:
raise ValueError("qty must be >= 0")
total = price * qty
if promo.strip().upper() == "SALE10":
total *= 0.9
return round(total, 2)
Тесты помечаем маркерами: smoke — быстрые базовые проверки, regression — закрепление исправленных дефектов.
# test_pricing.py
import pytest
from pricing import total_price
@pytest.mark.smoke
def test_basic_total():
assert total_price(100, 3) == 300
@pytest.mark.smoke
def test_promo_applies():
assert total_price(100, 1, "SALE10") == 90
@pytest.mark.regression
def test_promo_is_case_insensitive():
# баг #142: промокод в нижнем регистре не давал скидку
assert total_price(100, 1, "sale10") == 90
@pytest.mark.regression
def test_negative_qty_rejected():
with pytest.raises(ValueError):
total_price(100, -1)
Маркеры регистрируются в pytest.ini рядом с тестами. Опция --strict-markers превращает опечатку в имени маркера в ошибку — зачем это нужно, видно ниже.
[pytest]
markers =
smoke: быстрые проверки, что сборка работоспособна
regression: тесты на ранее исправленные дефекты
addopts = --strict-markers
Запуск по слоям. -m smoke выбирает только смоук, без фильтра идет весь набор:
$ pytest -m smoke -q
.. [100%]
2 passed, 2 deselected in 0.03s
$ pytest -q
.... [100%]
4 passed in 0.02s
Как регресс ловит возврат бага
Допустим, кто-то «упростил» условие до if promo == "SALE10":. Смоук остается зеленым — в нем промокод передается заглавными буквами. Полный прогон показывает регрессию:
$ pytest -q
..F. [100%]
> assert total_price(100, 1, "sale10") == 90
E AssertionError: assert 100 == 90
FAILED test_pricing.py::test_promo_is_case_insensitive
1 failed, 3 passed in 0.04s
Смоук подтвердил, что сборка жива, но без регресса старый дефект ушел бы в релиз.
Ошибка: опечатка в маркере
Неверно: в тестах написано @pytest.mark.regresion (одна буква «s»), а --strict-markers в конфиге нет.
Результат: pytest выдает только предупреждение PytestUnknownMarkWarning, а команда pytest -m regression не выбирает ни одного теста (4 deselected). Если в CI отбор идет выражением вроде -m "smoke or regression", пайплайн будет зеленым, хотя регресс фактически не выполнялся.
Исправление: зарегистрировать маркеры и включить --strict-markers, как в конфиге выше. Тогда опечатка останавливает сбор тестов:
ERROR test_pricing.py - Failed: 'regresion' not found in `markers` configuration option
1 error in 0.05s
Автоматизация регресса в CI
Регресс — первый кандидат на автоматизацию: одни и те же проверки повторяются на каждое изменение. Типовая схема в CI (GitLab CI, GitHub Actions, Jenkins устроены похоже):
- на merge request —
pytest -m smokeи тесты затронутого модуля; падение блокирует слияние; - после слияния в основную ветку — полный
pytestс отчетом--junitxml=report.xml, который CI показывает в интерфейсе; - по расписанию — длинные e2e-сценарии (UI через Playwright или Selenium, API-цепочки);
- после падения —
pytest --lfперезапускает только упавшие в прошлый раз тесты, чтобы быстро проверить фикс.
Большой набор ускоряет параллельный запуск, например плагином pytest-xdist (pytest -n auto), если тесты не зависят друг от друга и от общего состояния.
| Автоматизировать | Оставить ручным |
|---|---|
| Стабильные сценарии, которые повторяются каждый релиз | Новую функциональность, пока требования меняются |
| Расчеты, API, бизнес-правила | Удобство интерфейса и визуальное восприятие |
| Тесты на исправленные баги | Исследовательское тестирование рисковых зон |
| Проверки на разных данных и окружениях | Разовые проверки, которые дешевле пройти руками |
Если регресс не работает: симптомы и что делать
- Прогон идет часами, его перестали запускать. Разделить набор на слои (смоук / модуль / полный), распараллелить, вынести медленные e2e в ночную сборку.
- Тесты падают случайно (flaky). Не перезапускать до зеленого: изолировать тест в отдельную группу, найти причину — общее состояние, время, порядок запуска, сетевые зависимости.
- Регресс зеленый, а баг дошел до пользователей. Написать тест на дефект и найти, почему область не попала в выборку (часто — неучтенная зависимость, шаг 2).
Выводы
- Регрессионное тестирование проверяет, что изменение не сломало уже работавшее поведение; ретест проверяет сам фикс, смоук — работоспособность сборки.
- Повод для регресса — любое изменение: фикс, новая функция, рефакторинг, обновление зависимостей или окружения.
- Набор выбирают по риску: изменения и их зависимости, критичные сценарии, зоны с историей дефектов, тесты на исправленные баги.
- Регресс удобно запускать слоями: быстрый срез на каждый merge request, полный прогон после слияния и перед релизом.
- В pytest слои задаются маркерами;
--strict-markersзащищает от опечатки, из-за которой регресс молча не выполняется.
Где применяется / связь с практикой
Освойте тему на практике
Регрессионный набор — основная рабочая зона инженера по автоматизации тестирования: какие проверки автоматизировать, как разложить их по слоям и встроить в CI. Если хочется освоить это на практике — автотесты на Python, pytest, UI- и API-проверки, — посмотри программу курса «Автоматизация тестирования веб-приложений на Python».
Освойте тему на практике
Разобраться в отдельных темах тестирования и задать вопросы практикующим инженерам можно на бесплатных открытых уроках Otus.
FAQ
Можно ли проводить регрессионное тестирование полностью вручную?
Можно, на небольших проектах так часто делают по чек-листу. С ростом продукта ручной регресс дорожает, и стабильные сценарии переводят в автотесты.
Кто пишет регрессионные тесты — тестировщик или разработчик?
Зависит от команды: модульные тесты на исправленный баг часто пишет разработчик вместе с фиксом, а сквозные UI- и API-сценарии — инженер по автоматизации тестирования. Важно, чтобы тест на дефект появлялся в том же изменении, что и исправление.
Нужен ли регресс, если покрытие кода тестами 100%?
Нужен: покрытие показывает, какие строки выполнялись при тестах, но не гарантирует, что проверены все значимые комбинации данных и сценарии.



