Перейти к содержимому

Test Strategy

Каждый раз, когда я слышу «у нас coverage 80%, всё нормально с тестами», у меня появляется один вопрос: «80% — это покрытие чего, и какое количество багов это покрытие реально ловит?». Coverage — diagnostic, не target. Лист — про архитектуру портфолио тестов: какие слои (unit / integration / contract / e2e), какой ценой поддерживаются, какой полнотой ловят регрессии. Третий лист под L1 Programming / Scripting, сосед к Programming Languages и CI/CD.

Главный навык на уровне L4 — проектировать testing portfolio под конкретную систему. Test pyramid (много unit → меньше integration → мало e2e) — guideline для большинства систем, но не догма. Для distributed systems по моим наблюдениям лучше работает honeycomb (упор на integration / contract). Для backend без сложной UI — trophy (Kent C. Dodds). Цена ошибки в выборе формы — годы накопления медленного / хрупкого suite.

L3

  • Пишет unit-тесты для своего кода (фокус — public API, не private internals); понимает разницу unit / integration / e2e.
  • Применяет table-driven tests / property-based testing для покрытия input space; не пишет N однотипных копий с разными константами.

L4

  • Проектирует testing portfolio для сервиса: какие слои, какие dependencies реальные vs замокированные, где живут integration-тесты (in-process testcontainers vs shared staging vs ephemeral env).
  • Применяет contract testing между сервисами (Pact / Spring Cloud Contract / Hoverfly) — consumer-driven contracts проверяются обеими сторонами в CI.
  • Управляет flaky tests как operational задачей: измеряет flake rate, изолирует / quarantine’ит flaky tests, докладывает root cause не «retry’ем покрыли».

L5

  • Проектирует test data strategy — fixtures vs factories vs builders vs golden data sets; persistent test DB vs ephemeral per-test; PII / GDPR considerations.
  • Внедряет mutation testing как метрику качества test suite (Pitest / Stryker / Cosmic Ray / go-mutesting).
  • Проектирует non-functional testing — performance / load / stress / soak как отдельные категории со своими environments, baselines и acceptance criteria.

L6+

  • Внедряет org-level testing standards — minimum bar (coverage / mutation thresholds per criticality bucket), CI gates, shared test infrastructure, test ownership и maintenance ritual.
  • Kent Beck — Test-Driven Development: By Example (Addison-Wesley, 2002). Канон TDD, но шире — про дисциплину малых шагов. Читать даже если не делаешь strict-TDD.
  • Lisa Crispin, Janet Gregory — Agile Testing (Addison-Wesley, 2008). Test quadrants, test pyramid, whole-team approach.
  • Roy Osherove — The Art of Unit Testing (Manning, 3rd ed. 2024). Прикладная книга по unit-testing patterns, mock / stub / spy distinction, test smells. Третье издание под современный стек.
  • Vladimir Khorikov — Unit Testing Principles, Practices, and Patterns (Manning, 2020). Schools of thought (London vs Detroit), что есть unit, integration vs unit boundaries. Более глубокий заход после Osherove.

Канонический публичный аргумент против e2e-heavy approach — Google Testing Blog «Just Say No to More End-to-End Tests» (2015). В нём Google публично объяснил, почему даже их огромная инфраструктура не справляется с поддержкой большого e2e-suite: dramatic flake rates, длинный feedback loop (часы вместо минут), высокая стоимость maintenance. Если в вашей команде кто-то предлагает «давайте больше e2e, они ближе к пользователю» — отправляйте на эту статью первым делом. Test pyramid (много дешёвых unit → меньше дорогих integration → мало e2e) — guideline именно из этого опыта.

Короткие правила:

  • Coverage — diagnostic, не target. Goodhart’s law: «80% coverage обязательно» → developer пишет тесты без assertions / тесты на trivial code (геттеры). Coverage growth есть, signal value — нет. Coverage полезен как обратный сигнал (что не покрыто) и сравнение модулей. Mutation score — гораздо более честная метрика качества, но дороже считать (CI run × N мутаций).
  • Contract testing — обязательный слой в distributed systems, не «когда-нибудь». Unit’ы проверяют изолированно, e2e медленно и нестабильно; consumer ломается на breaking change в provider’е только при совместном деплое (часто = постфактум, в проде). Pact / Spring Cloud Contract проверяют compatibility в CI каждой стороны независимо. Cost внедрения — час на сервис; ROI — class of integration bugs устранён.
  • Test environment management — ephemeral per CI run, не shared staging. Shared staging накапливает test data debt, тесты сталкиваются (data pollution, port conflicts, race conditions). Ephemeral env per PR (Docker Compose / Testcontainers / k8s namespace per branch) даёт isolation. Cost управляемый при правильном tooling.

Подробнее:

Test pyramid как baseline, но не догма — выбирай shape под систему. Антипаттерн ice cream cone (много e2e, мало unit) — медленный feedback (часы), хрупкость, высокая стоимость поддержки. Базовая pyramid подходит большинству; для distributed systems по моим наблюдениям лучше работает honeycomb (упор на integration / contract — те классы багов, где они появляются); для backend без сложной UI — trophy (Kent C. Dodds). Решение должно быть обдуманным под характеристики системы, не cargo cult «у всех pyramid и у нас».

Flake rate измеряется, flaky tests изолируются. «Retry помог, идём дальше» — путь к 5% flake rate, что равно 100% trust loss в CI: developer видит red → нажимает retry, не глядя на root cause; реальный regression проходит мимо. Метрика — flake rate per test, dashboard, weekly review. Lifecycle: quarantine (не блокирует merge) → root cause analysis → fix или delete. Trunk / BuildPulse / Datadog CI Visibility делают это видимым; без tooling — слепое пятно.

Test data strategy — explicit decision, не «выгрузим из прода». Я регулярно вижу два антипаттерна. Первый: prod dump в staging без анонимизации — GDPR / PII fail в чистом виде, юристы вашей компании не одобрят, когда узнают. Второй: ручные fixtures в каждом тесте — drift, дублирование, кошмар поддержки. Factories (FactoryBot / factory_boy / mommy_factories) дают composable test data; builder pattern даёт читаемость; golden datasets — regression baseline; ephemeral DB per test (Testcontainers) — изоляция.

Tests как production code — review, ownership, refactor, deletion. «Test code не важен, как-нибудь работает» — типичная позиция, которая приводит к 30–50% test code volume как хидденному maintenance cost. Тесты, которые ничего не ловят, но дорого maintain’ить — кандидаты на deletion; тесты, которые ловят bug дважды — keep и углублять; broken tests — bug в коде или в тесте (всегда триаж, никогда @Disabled без followup ticket с дедлайном).

  • CI/CD — тесты живут в pipeline (CI ≤ 10 минут + zero flaky tests). Без testing strategy CI green = false confidence. Сосед под одним L1.
  • Programming Languages — language-specific testing tools (go test table-driven, pytest fixtures, JUnit annotations). Сосед под одним L1.
  • Progressive Delivery — canary с health gate = runtime test; pre-deploy tests — compile/build/staging. Граница: тесты до деплоя, canary после.
  • Chaos Engineering — chaos = hypothesis-driven experiments на real system; load / performance tests — known forcing function в staging. Дополняют, не заменяют друг друга.
  • Resilience Patterns — testability = атрибут архитектуры: idempotency, dependency injection, side effects на границах. Resilient design ↔ testable design.
  • SLO Engineering — performance / load tests генерируют baselines для SLI / SLO. Без load testing SLO targets — guess.
  • Toil Tracking — manual test execution = toil; automation тестов — toil reduction.
  • Vulnerability Management — security testing (SAST / DAST / fuzzing) — отдельная категория в test portfolio.
  • Performance & Profiling — load / performance testing — runtime-side проверки; profile-driven optimization — продолжение измерения после first deploy. Соседние листы.
  • Build Reproducibility / Hermetic Builds (TBD) — Bazel-style hermetic compilation, deterministic builds. Может стать отдельным листом под Programming / Scripting либо подсекцией Supply Chain Security.
  • Fuzzing как отдельная подобласть (TBD) — libFuzzer / AFL / jazzer / OSS-Fuzz / Go native fuzz. Security-flavoured, но техника — property-based testing с генеративным input space.
  • TDD как practice vs principle (TBD) — граница к этому листу: TDD = workflow (red-green-refactor); Test Strategy = portfolio architecture.
  • Shadow Traffic / Traffic Replay testing (TBD) — запись prod traffic и replay против new version в staging. Polyglot между chaos / load / regression / contract.