Уровни тестирования
Я проверил один и тот же расчёт корзины тремя способами и замерил каждый. Вызвать функцию напрямую - 1,2 микросекунды. Прогнать через настоящую базу с записью и чтением - 13,9 микросекунды. Дёрнуть работающий сервер по сети - 507 микросекунд. Одна и та же проверка, разница в 422 раза.
Стержень: чем выше уровень, тем дороже и нестабильнее тест, поэтому основную массу проверок держат внизу, а наверх поднимают единицы.
// Формулировки: «какие уровни тестирования?», «что такое пирамида?», «чем smoke отличается от sanity?».
Замер: сколько стоит каждый уровень
Разберём те три способа. Первый - вызвать функцию расчёта с готовыми числами и сверить ответ: 1,2 микросекунды. Это юнит-тест: проверка одной функции или класса в отрыве от всего остального. Второй - положить корзину в реальную базу, сохранить, прочитать обратно и посчитать: 13,9 микросекунды, в 12 раз дороже. Это интеграционный: проверяется стык кода с базой. Третий - поднять сервер и сходить к нему по сети, как ходит браузер: 507 микросекунд, в 422 раза дороже юнита.
И честная оговорка про третий замер: там был голый сетевой вызов, без браузера и без прогрева базы. Настоящий сценарий «открыл страницу, заполнил форму, нажал кнопку» через браузер стоит уже секунды. То есть в жизни разрыв между низом и верхом больше моего замера, а не меньше.
// Всего уровней четыре, по масштабу проверяемого куска. Юнит - одна функция. Интеграционный - как модули договариваются между собой. Системный - собранный продукт целиком, через интерфейс и сетевые вызовы. Приёмочный - готов ли продукт с точки зрения заказчика и пользователя; сюда же входит UAT (user acceptance testing), когда продукт щупают не тестировщики, а реальные пользователи или заказчик.
- юнит-тест
- проверка одной функции в отрыве от базы, сети и соседей
- E2E
- end-to-end, сквозной тест: сценарий целиком - интерфейс, сервер, база
Пирамида и что ловит каждый уровень
Пирамида тестирования - это про пропорции: в основании много быстрых юнит-тестов, посередине меньше интеграционных, наверху единицы сквозных. Основание держит скорость, вершина - уверенность, что всё вместе работает.
Уровни ловят разные дефекты, и это не вопрос вкуса. Ошибку в формуле скидки юнит найдёт за микросекунды и покажет пальцем на строку. А вот то, что два сервиса разошлись в формате даты, юнит не найдёт никогда: он по определению изолирован, соседа ему подменяет заглушка - это подставной объект, который отвечает заранее заготовленным ответом вместо настоящего модуля. Заглушка отвечает так, как её научили, а не так, как ответит живой сервис.
// Обратная беда - перевёрнутая пирамида, когда всё гонят через интерфейс. Набор становится в сотни раз медленнее, дороже в поддержке, и главное - бесполезен для диагностики: сквозной тест упал, а где именно из десяти шагов, ищи сам. Правило простое: лови дефект на самом низком уровне, где его вообще можно поймать.
- пирамида тестирования
- много быстрых юнитов внизу, единицы сквозных наверху
- заглушка (стаб)
- подставной объект с заранее заготовленным ответом вместо настоящего модуля
Почему вершина шатается: замер флаки
Я написал тест, который ждёт ответа не дольше 20 миллисекунд, а сервис отвечает то за 5, то за 25 - как повезёт с нагрузкой. Прогнал 300 раз: 68 падений, 23%. Поднял таймаут - то самое предельное ожидание - до 26 миллисекунд, и падений стало 0 из 300. Код не изменился ни на строку, изменилось только ожидание.
Это флаки-тест: тест, который на одном и том же коде то падает, то проходит. Вторая классическая порода - гонка: два потока прибавляют к общему счётчику, каждый читает старое значение и пишет своё. Мой замер: тест «в счётчике должно быть 2» упал 271 раз из 300. Чем выше уровень, тем больше таких источников случайности - сеть, время, общие данные, чужие прогоны рядом.
// И вот почему флаки опаснее обычного падения. Красный тест чинят. Мигающий - перезапускают, а через месяц набор перестают читать вообще: «а, это опять оно». Дальше настоящая поломка уезжает в прод под прикрытием привычного мигания. Лечится не поднятием таймаута, а убиранием случайности: ожидание события вместо сна на удачу, свои данные на прогон, изоляция.
- флаки-тест
- на неизменном коде то падает, то проходит
- гонка
- результат зависит от того, кто из потоков успел первым
Регресс, смоук, санити
Эти три слова - не уровни, а назначения проверки, и путают их постоянно. Регрессионное тестирование отвечает на вопрос «то, что работало вчера, работает сегодня?». Оно живёт на всех уровнях и первым уходит в автоматизацию: при релизе раз в неделю руками его ещё можно успеть, при релизе каждый день - уже нет.
Смоук - быстрая проверка «сборка вообще пригодна?»: приложение поднялось, главные экраны открываются, вход в аккаунт проходит, оплата не падает на первом шаге. Широко и мелко, минуты. Санити - точечная проверка «эту конкретную правку сделали?»: узко и глубоко, ровно вокруг изменения.
// Смоук из 400 шагов - это уже не смоук. Смысл смоука в том, чтобы за минуты решить, стоит ли вообще запускать основное тестирование; если он идёт полдня, решение принимается слишком поздно и функцию свою он не выполняет.
- регресс
- проверка, что новое не сломало работавшее старое
- смоук / санити
- пригодна ли сборка вообще / сделана ли конкретная правка
Как отвечать: «Что такое пирамида тестирования и зачем она?»
Пирамида - это принцип распределения тестов по уровням: много быстрых юнит-тестов в основании, меньше интеграционных, единицы сквозных наверху. Причина в цене, и её легко померить: я как-то проверял один и тот же расчёт корзины тремя способами - прямой вызов функции занял чуть больше микросекунды, прогон через реальную базу в 12 раз дороже, а поход к серверу по сети в 400 с лишним раз дороже. И это без браузера, с ним разрыв ещё больше. Вторая причина - стабильность: чем выше уровень, тем больше случайности, сеть и тайминги дают мигающие тесты, которые то падают, то нет. Третья - диагностика: юнит показывает пальцем на сломанную функцию, а красный сквозной тест говорит только, что где-то в десяти шагах беда. Поэтому логику проверяют внизу, а наверх поднимают несколько критичных сценариев целиком - оформление заказа, оплата. При этом уровни не взаимозаменяемы: расхождение форматов между двумя сервисами юнит не найдёт в принципе, он изолирован заглушками. Смысл пирамиды - ловить каждый дефект на самом низком уровне, где его можно поймать.
Почему это сильный ответ: пропорции объяснены через измеренную цену уровней, названы три причины (цена, стабильность, диагностика) и добавлено, что уровни ловят разные дефекты, а не дублируют друг друга.
На чём валят
- −Гнать всё через интерфейс: тот же тест дороже юнита в сотни раз и не показывает, где сломалось.
- −Пропустить интеграционный уровень: юниты зелёные, а сервисы разошлись в формате данных.
- −Лечить мигающий тест поднятием таймаута - я так убрал 68 падений из 300, не тронув причину.
- −Смоук на 400 шагов: решение «пригоден ли билд» приходит через полдня, когда оно уже не нужно.
- −Держать регресс только руками при частых релизах - либо не успеваешь, либо режешь глубину.
Проверьте себя
Пять вопросов из банка по этой подтеме. Всего их 13, остальные разбираются в тренажёре.
- Что проверяет модульное (unit) тестирование и кто его обычно пишет?A)Взаимодействие нескольких модулей между собой через их общие интерфейсы и контрактыB)Отдельный компонент в изоляции; пишут обычно разработчики, с моками зависимостейC)Всю систему целиком глазами конечного пользователя по сквозным бизнес-сценариямD)Внешний вид интерфейса приложения, который вручную проверяет тестировщик по макету
показать ответ и разбор
+B)Отдельный компонент в изоляции; пишут обычно разработчики, с моками зависимостей// разбор: Unit-тест проверяет наименьшую тестируемую единицу — функцию, метод, класс — изолированно от остального: внешние зависимости (БД, сеть, другие модули) подменяют моками/стабами, чтобы проверять только логику самого компонента. Пишут их, как правило, разработчики, потому что нужно знание внутренней структуры (белый ящик), и гоняют часто (на каждый коммит в CI) — они быстрые и дешёвые. Unit ловит дефекты логики компонента рано, но не видит проблем на стыках модулей — это уже уровень интеграционного тестирования.
- Что именно проверяет интеграционное тестирование?A)Логику одной изолированной функции без обращения к каким-либо внешним модулям и сервисамB)Готовность собранного продукта к приёмке со стороны бизнеса и конечного заказчикаC)Корректность взаимодействия модулей через интерфейсы: передачу данных, вызовы APID)Скорость ответа системы под высокой нагрузкой при множестве одновременных пользователей
показать ответ и разбор
+C)Корректность взаимодействия модулей через интерфейсы: передачу данных, вызовы API// разбор: Интеграционное тестирование проверяет стыки: отдельные модули могут быть исправны по unit-тестам, но при соединении всплывают дефекты интерфейсов — несовпадение форматов данных, неверный контракт API, потеря или искажение данных при передаче, ошибки в порядке вызовов. Проверяют как внутренние интеграции (модуль A ↔ модуль B), так и внешние (сервис ↔ БД, сервис ↔ сторонний API). Именно здесь ловятся баги, которые unit пропускает по определению (он изолирует компонент), а системное увидит слишком поздно и труднее локализует. Стратегии сборки — сверху вниз, снизу вверх, big bang.
- Что отличает системное тестирование от интеграционного?A)Системное проверяет один модуль в изоляции, а интеграционное — всю систему целиком под нагрузкойB)Между ними нет разницы: оба термина описывают проверку взаимодействия двух соседних модулейC)Системное — это проверка производительности, а интеграционное — функцийD)Всю собранную систему как единое целое против требований, а не отдельные стыки
показать ответ и разбор
+D)Всю собранную систему как единое целое против требований, а не отдельные стыки// разбор: Системное тестирование берёт продукт целиком, в окружении, близком к боевому, и проверяет его поведение против функциональных и нефункциональных требований — сквозными пользовательскими сценариями от начала до конца. В отличие от интеграционного (которое смотрит на конкретные стыки пары компонентов), системное оценивает продукт как единое целое: проходит ли пользователь весь путь, выполняются ли бизнес-требования, держит ли система нагрузку и безопасность. Обычно это чёрный ящик по требованиям, выполняемый командой QA. Это последний уровень перед приёмкой заказчиком.
- В чём суть приёмочного тестирования (UAT)?A)Готовность продукта к передаче глазами заказчика: решает ли он реальную бизнес-задачуB)Финальная проверка кода разработчиками на соответствие внутренним стандартам оформленияC)Проверка взаимодействия модулей системы между собой перед сборкой финального билда продуктаD)Автоматический прогон всех регрессионных тестов после каждого изменения в исходном коде
показать ответ и разбор
+A)Готовность продукта к передаче глазами заказчика: решает ли он реальную бизнес-задачу// разбор: Приёмочное тестирование (acceptance, часто UAT — user acceptance testing) отвечает на вопрос «принимаем ли продукт». Его ведут со стороны заказчика или конечных пользователей (а не только QA-команды разработчика), на реальных или приближенных к реальным данных и сценариях, против бизнес-требований и критериев приёмки. Цель — не столько найти технические баги (их ловят на предыдущих уровнях), сколько подтвердить, что продукт делает то, что нужно бизнесу, и готов к выпуску. Это по сути валидация. Провал UAT означает, что даже технически исправный продукт не решает задачу пользователя.
- Чем альфа-тестирование отличается от бета-тестирования?A)Альфа проводят внешние пользователи, а бета — внутренние сотрудники компании-разработчикаB)Альфа — внутри компании-разработчика; бета — реальными внешними пользователями «в поле»C)Альфа — это автоматизированные тесты, а бета — ручные проверки перед выпуском продуктаD)Альфа выполняется после релиза продукта, а бета — на самой ранней стадии проектирования
показать ответ и разбор
+B)Альфа — внутри компании-разработчика; бета — реальными внешними пользователями «в поле»// разбор: Оба относятся к приёмочному тестированию перед релизом, но отличаются исполнителем и средой. Альфа-тестирование идёт внутри организации-разработчика: продукт гоняют свои сотрудники (не из команды разработки — например, внутренние пользователи, поддержка) в контролируемой среде, имитируя реальное использование. Бета-тестирование выносится наружу: продукт дают ограниченному кругу реальных внешних пользователей, которые применяют его в своих настоящих условиях и на своих данных, присылая обратную связь и баги. Бета ловит проблемы реального окружения и разнообразия конфигураций, которые внутри воспроизвести трудно.
дальше
Теорию прочитали. Навык ставится повторением
В Сеньорчике эта подтема идёт в ежедневных сессиях: движок возвращает её, пока ответы не станут уверенными, и ведёт прогресс отдельно по каждой подтеме. Теория внутри тоже бесплатна, лимит только на количество вопросов в день.