Test Strategy
«У нас coverage 80%, с тестами всё нормально» — каждый раз, когда я это слышу, у меня появляется один встречный вопрос. Покрытие чего именно, и сколько багов оно поймало? Покрытие — диагностика, а не цель. Этот лист про архитектуру портфеля тестов: какие слои (unit, integration, contract, e2e), какой ценой они поддерживаются и какие регрессии ловят. Третий лист под L1 Programming / Scripting, сосед к Programming Languages и CI/CD.
Что должен уметь
Заголовок раздела «Что должен уметь»Главный навык на уровне L4 — проектировать портфель тестов под конкретную систему. Пирамида (много unit, меньше integration, совсем мало e2e) — ориентир для большинства систем, но не догма. Для распределённых систем, по моим наблюдениям, лучше работает honeycomb с упором на integration и contract. Для backend без сложного UI — trophy (Kent C. Dodds). Ошибка в выборе формы стоит дорого и оплачивается годами: медленный и хрупкий набор накапливается незаметно.
L3
- Пишет unit-тесты для своего кода: проверяет публичный интерфейс, а не внутренности; понимает разницу между
unit,integrationиe2e. - Применяет табличные тесты и тесты на свойствах, чтобы покрыть пространство входов; не пишет десяток однотипных копий с разными константами.
L4
- Проектирует портфель тестов для сервиса: какие слои нужны, какие зависимости берутся настоящими, а какие подменяются, где живут интеграционные тесты — в контейнерах рядом с прогоном, в общем staging или в одноразовом окружении.
- Применяет contract testing между сервисами (Pact / Spring Cloud Contract / Hoverfly) — контракт задаёт потребитель, а проверяют его в CI обе стороны.
- Относится к нестабильным тестам как к эксплуатационной задаче: измеряет долю миганий, отправляет такие тесты в карантин и разбирает причину, а не отчитывается «перезапуском закрыли».
L5
- Проектирует стратегию тестовых данных: фикстуры, фабрики, билдеры или эталонные наборы; постоянная тестовая база или одноразовая на каждый тест; отдельно — что делать с персональными данными.
- Внедряет мутационное тестирование как метрику качества набора тестов (Pitest / Stryker / Cosmic Ray /
go-mutesting). - Проектирует нефункциональные проверки — производительность, нагрузка, стресс и длительный прогон — как отдельные категории со своими окружениями, точками отсчёта и критериями приёмки.
L6+
- Внедряет стандарты уровня организации: минимальная планка по покрытию и мутационному счёту в зависимости от критичности сервиса, рубежи в CI, общая тестовая инфраструктура, владелец у каждого набора и регулярная чистка.
Материалы
Заголовок раздела «Материалы»- Kent Beck — Test-Driven Development: By Example (Addison-Wesley, 2002). Канон TDD, но шире — про дисциплину малых шагов. Читать стоит, даже если строгий TDD вы не практикуете.
- Lisa Crispin, Janet Gregory — Agile Testing (Addison-Wesley, 2008). Квадранты тестирования, пирамида, тестирование как дело всей команды.
- Roy Osherove — The Art of Unit Testing (Manning, 3rd ed. 2024). Прикладная книга про паттерны модульных тестов, разницу между подменами разного рода и признаки плохих тестов. Третье издание переписано под современный стек.
- Vladimir Khorikov — Unit Testing Principles, Practices, and Patterns (Manning, 2020). Две школы (лондонская и детройтская), что вообще считать модулем и где проходит граница с интеграционными тестами. Более глубокий заход после Osherove.
Статьи и доклады
Заголовок раздела «Статьи и доклады»- Mike Cohn — The Forgotten Layer of the Test Automation Pyramid (2009). Оригинал идеи пирамиды тестов.
- Martin Fowler — Test Pyramid и On the Diverse And Fantastical Shapes of Testing. Фаулер возвращается к пирамиде двенадцать лет спустя и разбирает другие формы под разные системы. Полезно ради понимания, что пирамида — ориентир, а не догма.
- Google Testing Blog — Just Say No to More End-to-End Tests (2015). Главный кейс листа — см. ниже.
- Martin Fowler — Eradicating Non-Determinism in Tests. Канонический разбор причин миганий и способов их устранить. Читать до того, как отправлять тесты в карантин.
- John Micco — Flaky Tests at Google and How We Mitigate Them (Google Testing Blog, 2016). Масштаб проблемы в цифрах: 1.5% всех прогонов заканчиваются flake’ом, почти 16% тестов хоть раз мигали, 84% переходов pass → fail оказываются flake’ом, а не регрессией. Держу под рукой как аргумент для тех, кто считает мигающий тест мелочью.
- Pact docs intro. Объяснение того, как контракт задаётся со стороны потребителя.
- Stryker — An introduction to mutation testing. Мутационный счёт как метрика, дополняющая покрытие.
- Spotify Labs — Testing of Microservices. Соты вместо пирамиды: как перебалансировать портфель под микросервисы.
- Andrew Trenk, Adam Bender — Software Engineering at Google: Testing Overview (O’Reilly, 2020). Как в Google смотрят на тестирование на своём масштабе.
Инструменты
Заголовок раздела «Инструменты»- Прогон unit и integration: JUnit (Java/Kotlin), pytest (Python), Jest / Vitest (JS/TS), go test + testify (Go), cargo test (Rust).
- Тесты на свойствах: Hypothesis (Python), proptest (Rust), jqwik (Java), fast-check (JS/TS).
- Подмены зависимостей: Mockito (Java), unittest.mock (Python), WireMock (HTTP), gomock (Go).
- Контейнеры под интеграционные тесты: Testcontainers — одноразовые база, Kafka или Redis в Docker на каждый прогон. По моим наблюдениям, стандарт во всех языках с приличной поддержкой Docker. LocalStack поднимает сервисы AWS локально.
- Контрактные тесты: Pact (контракт со стороны потребителя, с брокером), Spring Cloud Contract, Hoverfly.
- Сквозные и браузерные: Playwright — к 2026 выбор по умолчанию для веба; Cypress — альтернатива; Selenium — наследие, но всё ещё нужный запасной вариант.
- Покрытие: Coverage.py, JaCoCo, Istanbul/nyc,
go test -coverprofile. - Мутационное тестирование: PIT (Pitest) (JVM), Stryker (JS / .NET / Scala), mutmut (Python).
- Нагрузка и производительность: k6 — по моим наблюдениям, нынешний выбор по умолчанию; Locust (Python), Gatling, Vegeta.
- Учёт миганий: BuildPulse, Trunk Flaky Tests, Datadog CI Visibility. Без инструмента долю миганий никто не считает, и это остаётся слепым пятном.
Best practices
Заголовок раздела «Best practices»Канонический публичный аргумент против перекоса в сквозные тесты — Google Testing Blog «Just Say No to More End-to-End Tests» (2015). В нём Google публично объяснил, почему даже их огромная инфраструктура не справляется с поддержкой большого набора e2e: тесты постоянно мигают, обратная связь занимает часы вместо минут, а поддержка стоит непропорционально дорого. Аргумент простой. Если кто-то в команде предлагает «давайте больше e2e, они же ближе к пользователю» — отправляйте читать эту статью первым делом. Пирамида — много дешёвых unit, меньше дорогих integration, совсем мало e2e — выросла именно из этого опыта.
Отсюда первое правило: покрытие — диагностика, а не цель. Стоит объявить «80% обязательно», и закон Гудхарта срабатывает мгновенно: появляются тесты без единой проверки и тесты на геттеры. Цифра растёт, сигнала нет. Полезно покрытие ровно в обратную сторону: он неплохо показывает, что не покрыто вообще, и позволяет сравнить между собой модули, которые писали разные люди в разное время и с разным отношением к тестам. Честнее меряет мутационный счёт, но и стоит он дороже: каждый прогон умножается на число мутаций.
Контрактные тесты я считаю обязательным слоем в распределённых системах, а не задачей «когда-нибудь потом». Модульные тесты проверяют сервис изолированно, сквозные медленные и нестабильные, а потребитель ломается о несовместимое изменение у поставщика только при совместной выкатке — то есть постфактум и обычно в проде. Pact и Spring Cloud Contract переносят эту проверку в CI каждой стороны отдельно. Внедрение стоит примерно час на сервис, а взамен из жизни уходит целый класс интеграционных багов.
Окружение для тестов — эфемерное на каждый прогон, не общий staging. Общий staging копит мусор в тестовых данных, а тесты в нём сталкиваются друг с другом: данные грязные после соседнего прогона, порты заняты, гонки возникают на ровном месте и воспроизводятся раз в десять запусков. Отдельное окружение на PR (Docker Compose, Testcontainers, namespace в k8s на ветку) даёт изоляцию. При нормальных инструментах стоит это вменяемых денег.
Пирамида — отправная точка, а не догма: форма выбирается под систему. Перевёрнутая пирамида, где сквозных тестов много, а модульных мало, даёт обратную связь через часы, хрупкость и дорогую поддержку. Базовая пирамида подходит большинству. Для распределённых систем, по моим наблюдениям, лучше ложатся соты — упор на интеграционные и контрактные тесты, туда, где эти баги и появляются. Для бэкенда без сложного интерфейса — трофей (Kent C. Dodds). Главное, чтобы форма была выбрана осознанно, а не по принципу «у всех пирамида, и у нас пирамида».
Долю миганий меряют, нестабильные тесты изолируют. «Перезапуск помог, идём дальше» — короткая дорога к тому, что красному CI перестают верить вообще: инженер видит красное, жмёт перезапуск не глядя, а настоящая регрессия проезжает мимо. Порог тут жёстче, чем кажется на глаз. В Software Engineering at Google он назван прямо: по опыту авторов набор начинает терять ценность уже на подходе к одному проценту миганий, а собственный уровень Google держится около 0.15%. Лечится это метрикой на каждый тест, дашбордом и еженедельным разбором. Дальше жизненный цикл: карантин (тест не блокирует merge) → разбор причины → починить или удалить. Trunk, BuildPulse и Datadog CI Visibility делают мигания видимыми, без них это слепое пятно.
Тестовые данные — отдельное решение, а не «выгрузим из прода». Я регулярно вижу два антипаттерна. Первый — дамп прода в staging без анонимизации: чистый провал с персональными данными, о котором юристы узнают последними и очень некстати. Второй — руками написанные фикстуры в каждом тесте, которые расползаются, дублируются и превращают поддержку в кошмар. Дальше выбор по вкусу. Фабрики дают собираемые из кусочков данные, билдеры — читаемость, эталонные наборы — точку отсчёта для регрессий, одноразовая база через Testcontainers — изоляцию.
Тесты — такой же боевой код: ревью, владелец, рефакторинг, удаление. Позиция «код тестов неважен, как-нибудь работает» превращает половину репозитория в скрытую стоимость поддержки. Тест, который ничего не ловит, но дорого живёт, — кандидат на удаление. Тест, который поймал баг дважды, — оставляем и углубляем. Сломанный тест — это баг либо в коде, либо в самом тесте, и в обоих случаях он идёт в разбор, а не под @Disabled без тикета и срока.
Связанные листья
Заголовок раздела «Связанные листья»- CI/CD — тесты живут в конвейере, и он остаётся быстрым только без мигающих тестов. Без стратегии зелёный CI даёт ложную уверенность. Сосед под тем же L1.
- Programming Languages — у каждого языка свои инструменты: табличные тесты в
go test, фикстуры вpytest, аннотации в JUnit. Сосед под тем же L1. - Progressive Delivery — канареечная выкатка с проверкой здоровья — это тест уже на живом трафике. Граница простая: тесты до выкатки, канарейка после.
- Chaos Engineering — chaos ставит гипотезы на живой системе, а нагрузочные тесты создают заранее известное давление в staging. Дополняют друг друга, но не заменяют.
- Resilience Patterns — тестируемость — свойство архитектуры: идемпотентность, внедрение зависимостей, побочные эффекты, вынесенные на границы. Устойчивый дизайн и тестируемый обычно совпадают.
- SLO Engineering — нагрузочные тесты дают точки отсчёта для SLI и SLO. Без них целевые числа — догадка.
- Toil Tracking — ручной прогон тестов — это toil; автоматизация его убирает.
- Vulnerability Management — проверки безопасности (SAST, DAST, фаззинг) — отдельная категория в портфеле.
- Performance & Profiling — нагрузочные тесты меряют систему под нагрузкой, а профилирование продолжает эти измерения уже после выкатки. Соседние листы.
Открытые вопросы
Заголовок раздела «Открытые вопросы»Четыре темы я держу в уме, но пока не написал. Build reproducibility и hermetic builds (TBD) — детерминированная сборка в духе Bazel; это либо отдельный лист под Programming / Scripting, либо подраздел Supply Chain Security, и я не решил, куда честнее. Фаззинг (TBD) — libFuzzer, AFL, jazzer, OSS-Fuzz, встроенный фаззер в Go: пахнет безопасностью, а по технике это те же тесты на свойствах, только с генерируемым пространством входов. TDD (TBD) — граница с этим листом проходит по тому, что TDD задаёт порядок работы, а Test Strategy — архитектуру портфеля. И теневой трафик (TBD): запись боевых запросов и их повтор против новой версии в staging — штука, которая одновременно про chaos, нагрузку, регрессии и контракты.