Покупатель оформил заказ, деньги списались, а письмо не пришло. В чате три сообщения: «функция отправки письма покрыта тестами», «сервис писем отвечает, в его журнале запрос есть», «я этот сценарий проходил утром, письмо приходило». Все трое правы — просто проверяли разное: кусочек кода, соседний сервис и путь покупателя целиком.
Масштаб того, что проверяют за один раз, называют уровнем тестирования. Это не этапы календаря, а размер куска, который вы держите в руках, когда нажимаете «проверить»: все четыре уровня одной задачи могут случиться в один день, а могут растянуться на месяцы — зависит от модели разработки. Вопросы у них разные, и верхний уровень не заменяет нижний:
- модульный — эта функция считает правильно?
- интеграционный — двое соседей одинаково поняли договорённость?
- системный — покупатель дошёл до конца и получил то, чего ждал?
- приёмочный — то ли это, что заказывали, и можно ли это выпускать?
Разберём каждый на магазине со скидкой по промокоду, оплатой картой и письмом покупателю.
Снизу вверх растёт не сложность, а масштаб: одна функция, стык двух кусков, путь покупателя целиком, решение о выпуске. Чем выше проверка, тем она ближе к пользователю — и тем дороже обходится каждый прогон.
Модульное тестирование: одна функция под лупой
Изнутри программа собрана из маленьких кусков, и самый частый — функция: именованный участок кода, который получает что-то на вход и возвращает результат. «Дай сумму заказа и промокод — верну размер скидки». Проверку одного куска называют модульной (unit): функцию дёргают напрямую, без браузера, поэтому она идёт миллисекунды — таких делают тысячи и гоняют автоматически на каждое изменение кода, ещё до стенда. Передали отрицательную сумму при пороге «скидка действует от 1000» — ждём внятную ошибку, а не скидку, которая превратится в доплату покупателю. Пишут их разработчики, и вам важно одно: модульный тест проверяет код таким, каким его понял разработчик. Требование поняли неправильно — тест зелёный, а поведение неверное; «покрыто тестами» означает лишь, что строки кода выполнялись, а не что кто-то сравнил результат с требованием.
Заглушки и моки: как проверяют кусок в отрыве от соседей
Функция расчёта скидки лезет в базу за промокодом, а ходить туда проверке нельзя: промокод завтра истечёт, и тест покраснеет без правок кода. Поэтому соседа подменяют. Кусок, который на вопрос «что за промокод LETO25» всегда отвечает «скидка 25%, действует до конца года», называют заглушкой (stub): она умеет одно — вернуть заранее записанный ответ. Мок (mock) — та же подмена, но с памятью, и зачем она, видно на требовании «при двойном нажатии деньги не списываются дважды»: результат одинаковый, заказ оплачен, отличается только число обращений к платёжному сервису, а настоящий дёргать нельзя — это настоящие деньги. Мок и спрашивают: сколько раз к тебе обратились? Один — хорошо, два — баг, который иначе не увидеть.
Тем же словом называют подмену на стенде: «оплата на стенде заглушена» значит, что за кнопкой «Оплатить» стоит не банк, а кусок, который через полсекунды всегда отвечает «успешно». Отклонённая карта, «банк думает сорок секунд», повторное списание там не воспроизводятся: кейс «прошёл» — вы проверили заглушку, а не оплату, и в отчёте пишут именно так: «оплата проверена на заглушке, настоящие отказы карты не проверялись». Настоящий сосед, в отличие от заглушки, отвечает медленно, иногда ошибкой, иногда молчит до конца ожидания — целый класс багов живёт на этой границе и ловится уровнем выше.
Интеграционное тестирование: проверяем стык, а не кусок
Два куска проверили по отдельности, оба зелёные; собрали вместе — не работает. У стыка есть собственное содержание: договорённость о том, что и в каком виде один кусок передаёт другому. Корзина посчитала к оплате 3400 рублей и отправила платёжному сервису число 3400, а тот по своим правилам ждёт копейки и понимает это как 34 рубля: тесты у обеих сторон зелёные, а покупатель платит 34 рубля за заказ на 3400. Проверку связки двух и более кусков называют интеграционной (integration), и стыковые баги сводятся к четырём вопросам: в каком виде данные (рубли или копейки, 05.09.2026 или 2026-09-05), кто что считает (корзина применила скидку, а оплата применяет её второй раз — «скидка удвоилась»), что делать с отказом соседа (ошибка или молчание тридцать секунд: кто повторит попытку и не спишет ли повтор деньги дважды) и порядок с повторами (письмо ушло раньше, чем заказ записался в базу, — в нём пустой номер). Ваша половина там, где стык видно снаружи: запрос к сервису, вкладка «Сеть» и запрос к базе. Грабля одна: проверить только удачный ответ соседа, — а точное описание стыка делает баг-репорт адресным.
Системное тестирование: весь путь покупателя
Все стыки проверены попарно, а покупатель всё равно не доходит до конца: сценарий не равен сумме стыков — каждый шаг зависит от накопившегося на предыдущих. Системная проверка берёт продукт целиком и проходит путь от поиска товара до письма о заказе без подмен: настоящий интерфейс, настоящая база, настоящие соседи или их официальные песочницы. Ловится тут то, чего не видно на кусках: данные, которые тянутся через весь путь (адрес выбрали на втором шаге, а в письме он из профиля), состояние, которое меняется по дороге (промокод истёк между корзиной и оплатой) и возвраты с обрывами (нажали «назад» с оплаты, поменяли количество, оплатили — какая сумма ушла). Заодно тут меряют «насколько хорошо»: нефункциональные виды живут почти всегда на системном уровне. Это основная работа ручного тестировщика, и главный её враг — отличия стенда от боевого: заглушенная оплата, сто товаров вместо миллиона, один пользователь вместо тысячи одновременных; список отличий повторяют в отчёте.
Два зеркальных случая объясняют, почему верхний уровень не заменяет нижний. Скидка считается верно на всех сорока значениях, модульные тесты зелёные — а на экране оплаты сумма без скидки, потому что туда передали цену до её применения: функция ни при чём, её спросили не в том месте. Обратно: сценарий прошёл целиком, стоит «пройдено» — а в формуле прячется ошибка округления на суммах, заканчивающихся на пять копеек: сценарий честно прошёл по одной сумме из сотни. Поэтому на слово «проверено» уточняйте, что проверено — функция или путь; от ответа зависит, что вы напишете в отчёте.
Приёмочное тестирование: решение «выпускаем»
Продукт можно собрать без единого бага и всё равно не выпустить: ошибку сделали раньше — в том, что решили делать. Поэтому в конце идёт проверка другого рода: не «есть ли дефекты», а «то ли это, что было нужно, и можно ли это выпускать». Она называется приёмочной (acceptance) и идёт по критериям приёмки — заранее записанным условиям: скидка применяется к сумме товаров без доставки, два кода одновременно применить нельзя. Без оговорённых заранее критериев приёмка превращается в поток вкусовых правок, у которого нет конца. Проверяет заказчик или будущие пользователи; отсюда название UAT (User Acceptance Testing). Рядом стоит эксплуатационная приёмка, её проводят те, кому это обслуживать: как откатить неудачное обновление, где смотреть журналы ночью, есть ли резервная копия и пробовал ли кто-нибудь из неё восстановиться.
Ваше дело на приёмке — подготовить стенд и данные и разложить замечания на две стопки: работает не так, как договаривались, — баг разработчику; работает как договаривались, но заказчик хочет иначе — изменение требования с отдельным сроком. Не разделить стопки — команда получит два десятка «срочных багов», половина из которых новые пожелания.
Пирамида и почему она переворачивается в стакан
Одну и ту же ошибку в формуле скидки можно поймать на любом уровне — разница в цене. Тысяча модульных проверок проходит быстрее, чем вы успеете открыть браузер; та же ошибка через интерфейс — это минуты на стенд, вход, корзину и промокод, и проверка ломается, когда кнопку перенесли в другой угол, хотя расчёт никто не трогал. Отсюда образ пирамиды тестирования: внизу много дешёвых модульных проверок, выше поменьше интеграционных, на верхушке совсем немного прогонов через интерфейс. В жизни она часто переворачивается в стакан: модульных проверок мало — их некому писать, старый продукт не разбирается на куски, — надёжность добирают ручными прогонами, и чувствуется это по многочасовым прогонам перед выпуском и по падениям из-за переехавшей кнопки. Дальше — разговор про автоматизацию, и начинается он с цифры вроде «на перепроверку скидок уходит два часа каждого выпуска».
Коротко
- Уровень — масштаб куска, который проверяют за один раз, а не этап календаря; верхний не заменяет нижний.
- Модульная проверка дёргает одну функцию и идёт миллисекунды; зелёный прогон означает лишь, что код делает задуманное разработчиком.
- Заглушка возвращает заранее записанный ответ, мок вдобавок помнит число обращений; «оплата заглушена» значит, что отказ карты и двойное списание там не воспроизвести.
- Интеграционная проверка — про договорённость на стыке: вид данных, кто что считает, отказ соседа, повторы. Системная — про путь целиком, и её враг — отличия стенда от боевого.
- Приёмка отвечает «то ли мы сделали и можно ли выпускать» по заранее записанным критериям; рядом эксплуатационная — как откатить, есть ли резервная копия.
- Пирамида — про цену проверки: без дешёвого низа она превращается в стакан с многочасовыми прогонами.
Что почитать дальше
- Модели разработки ПО: waterfall, V-модель и итерации — откуда взялась связка «стадия проекта ↔ свой уровень проверки».
- Виды тестирования: функциональное и нефункциональное — что именно проверяют внутри каждого уровня.
- Ручное и автоматизированное тестирование — почему нижние уровни отдают коду и что означает зелёный прогон.
- Основы API-тестирования в Postman — как посмотреть на стык, не открывая интерфейс.