fedorthinks

Экспресс-курс · No. 21

Тест — это просто код, который автоматически проверяет твой код. Люди думают, что его работа — доказать, что сегодняшний код работает. Но настоящая отдача приходит позже: тесты дают менять, рефакторить и добавлять фичи завтра, не ломая того, что уже работало. Выучи виды и слова — и тестирование перестанет быть рутиной и станет тем, что даёт двигаться быстро.

19 понятий · схема у каждого · шпаргалка в конце

§ 01

Почти все неверно понимают, для чего тесты. Они выглядят как способ доказать, что код работает. Их настоящая ценность глубже и практичнее — и когда ты это видишь, ты пишешь тесты по-другому.

Карта: 19 понятий в семи главах

Карта метро: сначала видишь все линии целиком, потом едешь по станциям.

Все понятия курса разложены по семи главам. Сначала — зачем вообще тесты. Потом — виды тестов и их баланс. Потом — подделки, без которых не изолировать кусок кода. Дальше — ремесло хорошего теста, доверие к набору тестов и то, как всё это применять в работе.

1 · Why we testAutomated testRegression2 · Unit testsUnit testArrange–Act–Assert3 · PyramidIntegration testE2E testTest pyramid4 · FakesTest doubleStubMockOver-mocking5 · CraftTDDTest behaviourLiving docs6 · TrustCode coverageGoodhart's lawFlaky test7 · PracticeRisk-based testingCI gateCheat sheetall terms, at the end
Семь глав и 19 понятий курса. В конце — шпаргалка по всем.

Как читать курс

Паспорт: одинаковые поля у всех, поэтому их легко сравнивать.

У каждого понятия дальше одно и то же: английское название (запомни его — это термин, который ты встретишь в документации и на собеседованиях), образ из жизни, схема и карточка с полями: проблема, идея, пример, цена, когда брать и связи с другими понятиями. Главная мысль всего курса одна: тесты — это страховка для будущих изменений, а не справка о том, что код работал однажды.

Automated Test — тест как код, который проверяет код

Второй человек, который пересчитывает твои суммы на калькуляторе и отмечает каждую, что не сходится, — автоматически, без устали и одинаково каждый раз.

Тест (test) — это маленький кусок кода, который запускает твой код с известным вводом и проверяет, что результат тот, что ты ждёшь. «Если дать add числа 2 и 3, вернёт ли она 5?» Если да, тест проходит (pass); если нет — падает (fail) и сообщает тебе. Вот и вся механика. Сила в том, что это автоматически и повторяемо: тысячи таких проверок бегут за секунды при каждом изменении, и никому не надо перепроверять руками.

Inputadd(2, 3)Code under testadd()Assertionexpect 5PASSgot 5FAILgot 6automatic · repeatable · thousands of checks per run
Тест даёт коду известный ввод и сравнивает результат с ожиданием: совпало — pass, нет — fail.
ConceptAutomated Test· автотест
Проблема

Проверять руками долго и скучно — поэтому после каждой правки никто не перепроверяет всё.

Идея

Записать проверку кодом: известный ввод → вызов → сравнение с ожиданием. Итог — pass или fail.

Пример
«add(2, 3) должна вернуть 5» — одна строка, бежит за миллисекунды.
Цена
Тесты — тоже код: их надо писать, читать и поддерживать.
Когда брать
Для любого кода, который ты собираешься менять дальше.
Связи

Ловит Regression; бывает Unit, Integration и E2E; бежит в CI Gate.

Safety Net — настоящий смысл: менять без страха

Страховка у скалолаза: она не лезет за тебя, но даёт рискнуть смелым ходом, потому что срыв не будет смертельным. Ты лезешь смелее, ведь тебя поймают.

Вот главное недопонимание. Тесты в основном не про то, чтобы доказать, что сегодняшний код верен, — они про завтра. Когда ты меняешь код, рефакторишь его или добавляешь фичу, хороший набор тестов (test suite) сразу говорит, не сломал ли ты то, что раньше работало. Эта страховочная сеть (safety net) и даёт уверенно менять большую кодовую базу, а не бояться её трогать. Тесты — это способ двигаться быстро, чтобы за спиной ничего тихо не ломалось.

Changerefactor · featureTest suiteruns in secondsgreen ✓old behaviour okred ✗broke old codeShip itwith confidenceFix it nowbefore users see
Любое изменение проходит через набор тестов: зелёный — отгружай спокойно, красный — чини сейчас, а не после жалобы пользователя.

Regression — регрессия

Ряд растяжек по всему дому: как только что-то тревожит комнату, на которую ты даже не смотрел, срабатывает сигнал. Тебе не надо обходить каждую комнату самому.

Регрессия (regression) — это когда изменение ломает то, что раньше работало, часто где-то далеко от того места, которое ты трогал. Без тестов регрессии прячутся, пока их не найдёт пользователь. С тестами в тот же миг, как твоё изменение ломает старое поведение, тест краснеет и показывает, где именно. Это будничная отдача: внёс правку, прогнал тесты — и сразу знаешь, не сломал ли что-то ещё. Набор тестов стережёт весь дом, пока ты работаешь в одной комнате.

CodebaseCartyou changed thisPricingCheckoutInvoicesbroke silentlyinvoice test ✗a tripwire in a room you weren't looking at
Ты поменял корзину, а сломались счета — три модуля дальше. Тест на счета краснеет сразу.
TermRegression· регрессия
Что это
Изменение ломает поведение, которое раньше работало.
Подвох
Ломается часто не там, где ты правил, а в далёкой части системы.
Пример
Поменял расчёт корзины — и счета начали выходить с неверной суммой.
Как ловить

Набор тестов на старое поведение, который гоняют на каждом изменении.

Совет
Нашёл баг — добавь тест на него, чтобы он не вернулся.
Связи
Ловится Automated Test; страхует автоматически через CI Gate.

Тест проверяет код автоматически. Его настоящая ценность — не одноразовое доказательство, а свобода менять завтра, не ломая тихо то, что работает сегодня.

§ 02

Фундамент тестирования — юнит-тест: быстрая, точная проверка одного маленького куска кода самого по себе. Большинство твоих тестов будут такими, и не зря.

Unit Test — один маленький кусок в изоляции

Проверить, что один кубик Lego правильной формы и размера, прежде чем строить из него, — а не собирать весь замок, чтобы потом узнать, что одна деталь была кривой.

Юнит-тест (unit test) проверяет один маленький юнит (unit) — обычно одну функцию — в изоляции (isolation) от остальной системы. Дал ввод, проверил вывод, больше никто не участвует. Кусок маленький и сам по себе, поэтому, когда тест падает, ты точно знаешь, что сломалось и где. Юнит-тесты — микроскоп тестирования: узкий, точный, наведённый на одну вещь за раз.

isolationUnitone functioninputexpected?not touched:DatabaseNetworkDisk
Юнит-тест: ввод → одна функция → проверка вывода. База, сеть и диск не участвуют.
ConceptUnit Test· юнит-тест, модульный тест
Проблема

Когда падает большая проверка, непонятно, какая из сотни частей виновата.

Идея
Проверять по одной функции, отрезав её от базы, сети и всего остального.
Пример

calculateTax(100) возвращает 20 — без базы, без HTTP, за миллисекунды.

Цена
Не видит, как куски работают вместе, — для этого нужны другие тесты.
Когда брать

Для большей части тестов: бизнес-логика, расчёты, правила, краевые случаи.

Связи

Основание Test Pyramid; изоляцию дают Test Doubles; форма — Arrange–Act–Assert.

Fast and many — быстро и много, вот вся идея

Проверка орфографии, которая проходит весь документ за мгновение, — так быстро, что её гоняешь постоянно, а не раз в месяц, как медленное ревью.

Каждый юнит-тест крошечный и не трогает ничего внешнего, поэтому он бежит за миллисекунды. Значит, их можно иметь тысячи и гонять все на каждом изменении, постоянно. В этой скорости и суть: набор, который бежит за секунды, запускают всё время — и поломку ловят в момент, когда она случилась. Медленный набор пропускают, а пропущенный набор не защищает ничего. Быстро и много — вот что делает юнит-тесты рабочей лошадкой.

time per testunitmillisecondsintegrationslowere2eslowestfast suite → run on every change → breakage caught instantly
Чем ниже уровень, тем быстрее тест. Быстрый набор гоняют на каждой правке — поэтому он и ловит поломки.

Arrange–Act–Assert — подготовь, сделай, проверь

Простой опыт: подготовь условия, сделай одно действие, проверь результат. Ясная подготовка, один ход, одна проверка.

У хорошего юнит-теста три простых шага — паттерн Arrange–Act–Assert (AAA): подготовь вводы, один раз вызови то, что тестируешь, потом проверь, что результат тот, что ты ждал. Ассерт (assertion) — это сама проверка: «ответ должен быть равен 5». Тесты в этой чистой форме легко читать, а их падение сразу понятно. Тест, который трудно прочесть, — это тест, которому никто не будет доверять и который никто не станет поддерживать.

1 · Arrangecart = new Cart()cart.add(item, 2)2 · Acttotal = cart.total()3 · Assertexpect(total) .toBe(20)set up · do one thing · check the resultassertion = the check itself
Три шага теста: Arrange готовит корзину, Act считает итог, Assert сверяет его с ожиданием.
PatternArrange–Act–Assert· AAA, подготовь-сделай-проверь
Проблема

Тест-каша: подготовка, вызовы и проверки вперемешку — не понять, что он проверяет.

Идея
Три блока по порядку: подготовка → одно действие → проверка (assertion).
Пример

Arrange: корзина с двумя товарами. Act: cart.total(). Assert: итог равен 20.

Цена
Почти никакой — нужна только дисциплина «одно действие на тест».
Когда брать
Всегда, в любом тесте — юнит, интеграционном или сквозном.
Связи

Делает тест читаемым — а значит, Living Documentation; основа Unit Test.

Юнит-тест проверяет один маленький кусок в изоляции за миллисекунды — поэтому таких тестов тысячи, и они бегут постоянно. Подготовь ввод, сделай одно действие, проверь результат.

§ 03

Юнит-тесты проверяют куски поодиночке, но софт — это куски, которые работают вместе. Это покрывают два более широких вида тестов, а у правильного баланса всех трёх есть форма, которую стоит знать.

Integration Test — куски, работающие вместе

Проверив каждый кубик Lego, ты скрепляешь несколько вместе — убедиться, что они правда соединяются. По отдельности кубики были в порядке, но подходят ли они друг к другу?

Интеграционный тест (integration test) проверяет, что несколько частей верно работают вместе: твой код и база данных, два сервиса, которые обмениваются данными. Каждый юнит может быть идеален сам по себе и всё равно не состыковаться: не тот формат, неверное допущение о другой стороне. Интеграционные тесты ловят именно эти швы (seams). Они медленнее юнит-тестов, потому что в них больше настоящих движущихся частей, но проверяют стыки, которые юнит-тесты по замыслу пропускают.

seamYour codeDatabasequeryrowsService AService BJSONreplybugs hide in the joins: format mismatch, wrong assumptions
Интеграционный тест проверяет шов: запрос к базе и ответ, JSON между сервисами. Баги прячутся именно в стыках.
ConceptIntegration Test· интеграционный тест
Проблема
Каждая часть проходит свои юнит-тесты, а вместе они не работают.
Идея
Запустить несколько настоящих частей вместе и проверить стык между ними.
Пример

Репозиторий пишет пользователя в настоящую тестовую базу и читает его обратно.

Цена
Медленнее юнит-тестов, нужна настройка окружения (база, сервисы).
Когда брать
Для швов: код ↔ база, сервис ↔ сервис, твой код ↔ чужой API.
Связи
Средний слой Test Pyramid; ловит то, что прячет Over-mocking.

End-to-End Test — вся система глазами пользователя

Генеральная репетиция всей пьесы от начала до конца на настоящей сцене — не проверка реплик или реквизита по отдельности, а весь спектакль, как его увидит зритель.

Сквозной тест (end-to-end test, E2E) гоняет всю систему так, как это делал бы настоящий пользователь: нажать кнопку, заполнить форму, проверить, что нужное случилось через весь стек. Это самый реалистичный тест и самое ценное подтверждение, что продукт правда работает. Но он же самый медленный и хрупкий (fragile): должно работать много частей сразу, а мелкое изменение интерфейса может его сломать. Поэтому e2e берут для немногих критичных путей, а не для всего.

the whole stack, really runningUserclicks, typesUIbrowserAPIserverDatabaseOrder placed?assertmost realistic · slowest · most fragileuse it for the few critical journeys
E2E-тест идёт от пользователя через интерфейс, API и базу — и проверяет итог: заказ создан?
ConceptEnd-to-End Test· E2E, сквозной тест
Проблема
Все части проверены, но работает ли продукт целиком — неизвестно.
Идея
Пройти путь пользователя через весь настоящий стек и проверить итог.
Пример

Браузер-робот: войти, положить товар в корзину, оплатить, увидеть «заказ оформлен».

Цена
Самый медленный и хрупкий; ломается от мелких правок интерфейса.
Когда брать
Для нескольких критичных путей: вход, оплата, главный сценарий.
Связи

Вершина Test Pyramid; если таких тестов слишком много — «рожок мороженого».

Test Pyramid — пирамида тестов

Пирамида стоит, потому что основание у неё широкое, а вершина узкая. Переверни её на острие — и она упадёт. Сама форма и даёт устойчивость.

Пирамида тестов (test pyramid) — правило большого пальца для баланса: много быстрых юнит-тестов в основании, меньше интеграционных в середине и горстка медленных сквозных на вершине. Такая форма дёшево даёт широкую и быструю проверку, а сверху — ровно столько реалистичных проверок, сколько нужно. Антипаттерн — куча медленных e2e и мало юнит-тестов — называют «рожком мороженого» (ice-cream cone): он медленный, флакающий и мучительный. Целься в пирамиду, а не в рожок.

E2EIntegrationsomeUnitmany, fastTest pyramid ✓E2Emany, slowIntegrationunit (few)Ice-cream cone ✗
Слева пирамида: много юнит-тестов, меньше интеграционных, пара e2e. Справа рожок мороженого — та же пирамида вверх ногами.
PatternTest Pyramid· пирамида тестов
Проблема

Тестов много, но набор медленный и нестабильный — его перестают гонять.

Идея
Много дешёвых быстрых тестов внизу, мало дорогих медленных наверху.
Пример

Тысячи юнит-тестов, сотни интеграционных, десяток e2e на главные сценарии.

Цена
Нужно осознанно решать, на каком уровне проверять каждую вещь.
Антипаттерн

Ice-cream cone — пирамида вверх ногами: много e2e, мало юнитов.

Связи

Слои — Unit, Integration, E2E; лишние e2e плодят Flaky Tests.

Юнит-тесты проверяют куски поодиночке, интеграционные — вместе, сквозные — всю систему глазами пользователя. Держи пирамиду: много быстрых, мало медленных.

§ 04

Чтобы проверить кусок в изоляции, часто нужно подменить настоящие вещи, от которых он зависит. Это и есть моки и стабы — и ими так же легко навредить, как помочь.

Test Double — подделка вместо настоящей зависимости

Авиатренажёр вместо настоящего самолёта: поддельные приборы и вид из окна дают отработать один навык безопасно, без цены и риска настоящего полёта.

Чтобы проверить кусок в изоляции, его настоящие зависимости (dependencies) — базу, платёжный сервис, отправку писем — подменяют подделками. Общее название для любой такой подделки — тестовый двойник (test double), как дублёр в кино. Так ты проверяешь свой код без медленной, дорогой или непредсказуемой настоящей вещи. Юнит-тест не должен на самом деле списывать деньги с карты или слать письмо — ты подставляешь подделку, которая делает вид, и тест остаётся быстрым, надёжным и самодостаточным.

test doublereal thingCode under testFake DBFake paymentsFake emailPostgresslowStripecosts moneySMTPsends for real
Код под тестом говорит с подделками, а не с настоящими Postgres, Stripe и почтой — те медленные, платные или шлют всё по-настоящему.
ConceptTest Double· тестовый двойник, подделка, fake
Проблема

Настоящие зависимости медленные, платные или непредсказуемые — тест с ними хрупкий.

Идея

Подставить вместо зависимости объект, который ведёт себя как она, но понарошку.

Пример
Вместо Stripe — подделка, которая всегда отвечает «оплата прошла».
Цена
Подделка может врать: настоящая вещь ведёт себя иначе.
Когда брать

Для внешнего, медленного и случайного: сеть, платежи, почта, время.

Связи

Два главных вида — Stub и Mock; даёт изоляцию Unit Test; в избытке — Over-mocking.

Stub vs Mock — заготовленный ответ против проверки вызова

Стаб — картонная фигура, которая по сигналу говорит одну реплику. Мок — актёр, который ещё и докладывает, что именно ты ему сказал и как.

Два вида подделок часто путают. Стаб (stub) просто возвращает фиксированный, заготовленный ответ — «пусть база вернёт вот этого пользователя», — чтобы проверить, как твой код обработает такой ввод. Мок (mock) ещё и проверяет взаимодействие — «убедись, что мой код вызвал sendEmail ровно один раз и с этим адресом». Стаб кормит твой код данными; мок следит, как твой код с ним обращается. Нужны данные — бери стаб; нужно убедиться, что случился побочный эффект, — бери мок.

Stubsupplies dataStubgetUser()Your codereturns { name: 'Ann' }test checks your code's outputMockverifies the callYour codeMocksendEmail()expect(sendEmail) .calledOnceWith(addr)test checks the call happened
Стаб подаёт данные, и тест проверяет вывод твоего кода. Мок записывает вызов, и тест проверяет сам вызов.
TermStub· стаб, заглушка
Что это
Подделка, которая возвращает заготовленный ответ.
Пример
getUser() всегда возвращает { name: 'Ann' }.
Что проверяем
Вывод твоего кода при таких данных.
Когда брать
Когда коду нужно что-то получить от зависимости.
TermMock· мок
Что это
Подделка, которая запоминает вызовы и даёт их проверить.
Пример
Проверить, что sendEmail вызван ровно один раз с нужным адресом.
Что проверяем
Сам вызов: был ли, сколько раз, с какими аргументами.
Когда брать
Когда важен побочный эффект: письмо ушло, платёж отправлен.

Over-mocking — моки, которые врут

Репетировать пьесу целиком с картонными фигурами вместо всех остальных актёров: свои реплики ты отточишь, но так и не узнаешь, сыграет ли настоящая труппа вместе.

Подделки мощные, но в избытке опасны. Если ты мокаешь всё, тест проверяет твои допущения о том, как ведут себя другие части, — а не то, как они ведут себя на деле. Тест зеленеет, пока настоящая интеграция сломана. Это антипаттерн чрезмерного моканья (over-mocking). Поэтому мокай медленные, внешние и непредсказуемые зависимости, но не подменяй подделкой ту самую вещь, которую пытаешься проверить. Тест, построенный целиком на моках, может пройти, пока настоящая система падает, — самый опасный вид зелёного.

in the test: everything mockedCodeMock DBMock APIPASS ✓in production: the real partsCodeReal DBReal APIFAIL ✗green test, broken system — the most dangerous green
В тесте всё замокано — и он зелёный. В продакшене настоящие база и API — и всё падает.
Anti-patternOver-mocking· чрезмерное моканье
Симптом
Тест зелёный, а в продакшене та же функция падает.
Причина

Подделано всё вокруг — тест проверяет только твои допущения о других частях.

Пример

Мок базы всегда отвечает «ок», а настоящая база отвергает запрос из-за неверного поля.

Лекарство

Мокай только внешнее и медленное; стыки проверяй Integration Test.

Связи

Злоупотребление Test Double; ломает доверие так же, как Flaky Test.

Моки и стабы подменяют настоящие зависимости, чтобы тестировать в изоляции. Стаб даёт данные, мок проверяет вызов. Замокаешь слишком много — тест пройдёт, пока реальность падает.

§ 05

Кроме видов тестов есть ремесло — писать хорошие. И есть одна дисциплина, разработка через тесты, которая переворачивает привычный порядок и меняет то, как ты проектируешь код.

TDD — сначала тест, потом код

Нарисовать мишень до выстрела, а не после, — чтобы целиться в ясную цель, а не обводить яблочко вокруг того места, куда стрела случайно попала.

Разработка через тесты (test-driven development, TDD) переворачивает обычный порядок: ты пишешь тест до кода. Цикл называется red, green, refactor (красный, зелёный, рефакторинг): напиши падающий тест на то, что хочешь (красный), напиши ровно столько кода, чтобы он прошёл (зелёный), потом приведи код в порядок под защитой теста (рефакторинг). Тест первым заставляет точно определить, что значит успех, ещё до постройки, и гарантирует, что код можно тестировать — ведь он родился из теста.

Redwrite a failing testGreenjust enough codeRefactorclean up, stay greenrepeat
Цикл TDD: красный (тест падает) → зелёный (минимум кода) → рефакторинг (чистим, тесты зелёные) → снова.
PracticeTest-Driven Development· TDD, red-green-refactor
Проблема
Тесты пишут «потом» — и код выходит неудобным для тестирования.
Идея
Сначала падающий тест, потом минимум кода, потом уборка. И так по кругу.
Пример

Тест «пустая корзина → итог 0» падает; пишешь total(); тест зелёный; чистишь код.

Цена
Непривычно и поначалу медленнее; сложно, когда задача ещё не ясна.
Когда брать

Для логики с понятными правилами: расчёты, валидация, исправление багов.

Связи

Даёт тестируемый код и Living Documentation; рефакторинг держится на Test Behaviour.

Test Behaviour, Not Implementation — проверяй что, а не как

Судить повара по тому, вкусное ли блюдо, а не по тому, в какой руке он держал нож. Важен итог, а не личные детали того, как он к нему пришёл.

Важнейшая привычка для долгоживущих тестов: проверяй, что код делает, а не как он это делает — поведение (behaviour), а не реализацию (implementation). Проверяй, что getDiscount возвращает верную цену, а не что он вызвал три конкретных внутренних помощника по порядку. Тесты, привязанные к итогу, переживают рефакторинг: можно переписать всё нутро, а они всё ещё проходят. Тесты, привязанные к реализации, ломаются при каждой уборке кода — и люди привыкают им не доверять и удалять их. Проверяй поведение, и тест остаётся полезным.

getDiscount()helperhelperhelperinternals (free to change)inputcart, couponprice90✓ check the outcome✗ test coupled to internals — breaks on every refactor
Хороший тест смотрит на вход и выход getDiscount. Плохой тест лезет во внутренних помощников — и ломается при каждом рефакторинге.
PrincipleTest Behaviour, Not Implementation· поведение, а не реализация
Проблема
Тесты ломаются от каждого рефакторинга, хотя поведение не менялось.
Идея
Проверять только наблюдаемый итог через публичный интерфейс.
Пример

Проверить, что getDiscount вернул 90, — а не какие помощники он вызвал внутри.

Цена
Иногда сложнее добраться до краевого случая только через вход и выход.
Когда брать
Всегда по умолчанию.
Связи

Делает возможным рефакторинг в TDD; лишние Mock часто привязывают к реализации.

Living Documentation — тесты как документация

Хорошо написанный набор тестов читается как список обещаний: «при этом оно делает то». Это яснее любого комментария о том, как код должен себя вести.

Ясный набор тестов работает как живая документация (living documentation). Каждый тест записывает обещание — «при пустой корзине итог равен нулю». И раз тесты бегут, эта документация не устаревает, как комментарии: если поведение меняется, тест ломается. Поэтому пиши тесты так, чтобы их читали: ясные имена, одно поведение на тест, очевидный arrange-act-assert. Новичок должен понять, что делает код, просто читая его тесты. Эта читаемость стоит столько же, сколько сама проверка.

ConceptLiving Documentation· живая документация
Проблема
Комментарии и вики врут: код поменялся, а текст остался старым.
Идея
Тесты — обещания, записанные кодом. Сломалось обещание — тест покраснел.
Пример

Имя теста: «пустая корзина — итог равен нулю». Читается как спецификация.

Цена
Нужно время на понятные имена и одно поведение на тест.
Когда брать
Всегда — особенно в коде, который читают новички.
Связи
Держится на Arrange–Act–Assert и Test Behaviour.

Пиши тест первым (red-green-refactor), проверяй поведение, а не реализацию, и делай тест читаемым — тогда тесты переживают рефакторинг и работают документацией, которая не устаревает.

§ 06

Одно число и одна напасть решают, можно ли на деле доверять набору тестов. Неверно пойми покрытие или терпи флакающие тесты — и зелёный прогон перестанет что-либо значить.

Code Coverage — что протестировано, а не насколько хорошо

Карта улиц, по которым проехал патруль: удобно заметить районы, куда никто не заезжал, но проехать мимо дома — не значит проверить замки.

Покрытие кода (code coverage) — это процент кода, который выполняется во время тестов. Это полезный ориентир, чтобы найти то, что не тестировано совсем, — улицы, где патруль не был. Но высокое покрытие не значит хорошие тесты: код может выполниться в тесте, который ничего толком не проверяет, — 100% покрытия при почти нулевой проверке. Используй покрытие, чтобы находить слепые пятна, а не как доказательство качества. Тронутая строка — не то же самое, что проверенная строка.

12345678executed + assertedexecuted, nothing assertednever executedcoverage: 6 of 8 lines (75%)really checked: 3 of 8a touched line is not a checked line
Покрытие 75%: выполнилось 6 строк из 8. Но по-настоящему проверены (есть assertion) только 3.
MetricCode Coverage· покрытие кода
Что это
Доля строк (или веток) кода, которые выполнились во время тестов.
Чем полезно
Показывает слепые пятна — код, который не запускал ни один тест.
Подвох

Не меряет качество: строка может выполниться, но ни один assertion её не проверит.

Пример

80% покрытия, но вся логика скидок в непокрытых 20% — вот где искать риск.

Когда брать
Как фонарик для поиска пробелов, а не как цель.
Связи

Превращается в ловушку по закону Гудхарта; не заменяет Risk-based Testing.

Goodhart's Law — не дай покрытию стать целью

Платить рабочим за километр покрашенной разметки и смотреть, как они рисуют длинные бесполезные линии на пустырях, чтобы добить план. Метрика выросла, дороги безопаснее не стали.

Когда число становится целью, люди оптимизируют число, а не то, что оно мерило, — это закон Гудхарта (Goodhart's law). Потребуй «100% покрытия» — и получишь тесты, написанные, чтобы тронуть строки, а не поймать баги: пустые тесты, которые ничего не проверяют и просто обыгрывают метрику. Покрытие — фонарик для поиска пробелов, а не кубок, который надо набить до максимума. Гонись за настоящей уверенностью — ловят ли тесты поломку на деле? — и пусть покрытие будет одной подсказкой из нескольких, а не самой целью.

Coveragea measurebecomes“100% or no merge”a targetHollow testsassert nothingthe number went up — the bugs stayedcoverage is a flashlight, not a trophy
Мера становится целью — и рождаются пустые тесты: число растёт, а баги остаются.
LawGoodhart's Law· закон Гудхарта
Суть
Когда мера становится целью, она перестаёт быть хорошей мерой.
В тестах

Правило «без 100% покрытия не мёржим» рождает тесты без assertion — ради числа.

Пример

Тест вызывает функцию и ничего не проверяет — покрытие растёт, защиты ноль.

Лекарство

Спрашивать «поймает ли тест поломку?», а покрытие держать подсказкой.

Связи
Главная ловушка Code Coverage.

Flaky Test — флакающий тест хуже, чем никакого

Пожарная сигнализация, которая срабатывает случайно и без причины: через неделю её все игнорируют, и когда начинается настоящий пожар, никто не двигается.

Флакающий тест (flaky test) — тот, что проходит и падает случайно, хотя код не менялся. Обычно виноваты тайминг, порядок запуска или скрытая зависимость. Флакающие тесты — яд: когда красный результат может быть просто шумом, люди перестают верить любому красному, и настоящий сбой пропускают со словами «наверное, опять флакает». Тест должен быть детерминированным (deterministic): тот же код — тот же результат, каждый раз. Чини флакающие тесты или удаляй их; набор, которому не доверяешь, не защищает ничего.

same code, nothing changed — six runsPASSrun 1FAILrun 2PASSrun 3PASSrun 4FAILrun 5PASSrun 6red might be noise → people stop trusting any reddeterministic: same code → same result, every time
Тот же код, шесть прогонов — и два случайных падения. После такого любой красный считают шумом.
TermFlaky Test· флакающий, нестабильный тест
Что это
Тест, который то проходит, то падает без изменения кода.
Причины
Тайминг (sleep, гонки), порядок тестов, скрытая общая зависимость.
Пример

Тест ждёт 100 мс ответа сервиса; на загруженной CI-машине иногда не дожидается.

Вред
Учит команду игнорировать красный — и настоящий сбой проходит мимо.
Лекарство

Сделать тест deterministic — или удалить. Терпеть флакающий тест нельзя.

Связи
Чаще всего живёт в E2E; ломает доверие к CI Gate.

Покрытие показывает, что протестировано, а не насколько хорошо, — не делай его целью. А флакающий тест хуже, чем никакого: раз красный может быть шумом, игнорируют любой красный.

§ 07

Тестирование — это суждение, а не ритуал. Навык в том, чтобы тестировать важное, в правильных пропорциях, и встроить это в то, как ты выпускаешь код, — чтобы набор заслуживал доверия, а не превращался в отписку.

Risk-based Testing — тестируй рискованное, а не всё

Парашют и тормоза ты проверяешь трижды; подстаканник на прочность не испытываешь. Усилия идут туда, где сбой правда больно бьёт.

Не весь код заслуживает одинакового внимания. Тестирование по рискам (risk-based testing) — это сосредоточить усилия там, где баг был бы дорогим или вероятным: ключевая бизнес-логика, деньги, безопасность, хитрые краевые случаи, части, которые часто меняются. Простому коду с низким риском можно уделить меньше. Гнаться за тестами для всего — значит тратить силы и получить набор такой большой и медленный, что его никто не запускает. Целься в тесты, которые ловят сбои, о которых ты правда пожалел бы, а не в число: покрытие важного лучше покрытия всего.

Test wellrare but costlyTest hardestmoney · security · core logicTest lightlytrivial, low-risk codeTest someedge cases · frequent changeshigh costlow costunlikely to breaklikely to breakeffort goes where failure actually hurts
Чем дороже сбой и чем вероятнее поломка, тем больше тестов. Деньги, безопасность и ключевая логика — в правом верхнем углу.
PracticeRisk-based Testing· тестирование по рискам
Проблема

Сил на тесты мало, а пытаться покрыть всё — значит размазать их тонко.

Идея

Больше тестов там, где сбой дорогой или вероятный; меньше — на тривиальном.

Пример

Расчёт платежей и права доступа — плотно; геттеры и простая вёрстка — по минимуму.

Цена
Нужно честно оценивать риск, а не тестировать то, что легко.
Когда брать
Всегда, когда решаешь, на что тратить время на тесты.
Связи

Заменяет погоню за Code Coverage; плотнее всего — Unit Test на логике.

CI Gate — тесты на каждом изменении, автоматически

Турникет, который не откроется, пока билет не действителен: проверка встроена в ворота, а не зависит от того, вспомнит ли кто-то посмотреть.

Тесты защищают, только если они правда запускаются. Встрой их в пайплайн (pipeline), чтобы они бежали автоматически на каждом изменении, и блокируй мёрж, если они падают, — это CI-гейт (CI gate) из курса о деплое. Именно это превращает тесты из того, что ты, может быть, запустишь, в гарантию, что ничего сломанного не попадёт в основную ветку. «Без тестов не выпускаем» — стандарт, который стоит держать: непротестированное изменение в продакшене — это ставка, которую ты не обязан был делать.

Pusha changeCI pipelineruns every testGateall green?Merge ✓into mainBlocked ✗fix firsttests or it didn't ship
Каждое изменение проходит через CI: все тесты зелёные — мёрж; хоть один красный — мёрж заблокирован.
PracticeCI Gate· CI-гейт, проверка перед мёржем
Проблема

Тесты есть, но их забывают запускать — и сломанный код уходит в main.

Идея

CI сам гоняет все тесты на каждом изменении и не пускает мёрж, пока они не зелёные.

Пример
Pull request: CI запускает тесты, один упал — кнопка мёржа неактивна.
Цена
Время ожидания CI; флакающие тесты здесь бьют больнее всего.
Когда брать
В любом проекте, где больше одного изменения в неделю.
Связи

Запускает весь Test Pyramid; не терпит Flaky Test; ловит Regression до пользователя.

Как всё связано

Оркестр: каждый инструмент важен, но музыка получается только вместе.

Понятия курса работают как одна система. Пирамида задаёт форму набора, TDD его пишет, подделки дают юнит-тестам изоляцию, а CI-гейт гоняет всё на каждом изменении — и это даёт главное: менять код без страха. Против работают флакающие тесты и чрезмерное моканье — они подрывают доверие к зелёному. А покрытие лишь подсказывает, где пробелы.

Test pyramidshapes the suiteTDDwrites tests firstTest doublesisolate unitsTest suiteunit · integration · e2eOver-mockingfalse greenChange without fearregressions caughtFlaky testsdestroy trustCoveragehints at gapsCI gateruns on every change
Пирамида, TDD и подделки строят набор тестов; CI-гейт гоняет его и даёт менять без страха. Флакающие тесты и лишние моки бьют по доверию, покрытие лишь подсказывает.
Прежде чем доверять набору тестов
  • Даёт ли он менять уверенно — поймал бы он регрессию завтра?
  • Есть ли у него форма пирамиды — много юнит, немного интеграционных, пара e2e?
  • Проверяют ли тесты поведение, а не внутреннюю реализацию, которая ломается на рефакторинге?
  • Мокаются ли только настоящие внешние зависимости, а не то, что под тестом?
  • Быстрый ли он и детерминированный — нет флакающих тестов, которые подтачивают доверие?
  • Бежит ли он в CI на каждом изменении, блокируя сломанные мёржи?
Признаки, что ты тестируешь хорошо
  • Ты меняешь код уверенно и доверяешь набору поймать то, что сломал.
  • Твои тесты образуют пирамиду: быстрые в основании, пара реалистичных сверху.
  • Тесты проверяют поведение и переживают рефакторинг, а не ломаются на каждой уборке.
  • Набор быстрый и детерминированный, а флакающие тесты ты чинишь или удаляешь.
  • Тесты бегут в CI на каждом изменении, а покрытие для тебя — подсказка, а не цель.

Шпаргалка: все 19 понятий

Зачем мы тестируем

Automated Testавтотест

Код, который проверяет код: ввод → вызов → assertion → pass или fail.

Regressionрегрессия

Изменение ломает то, что раньше работало, часто далеко от места правки.

Юнит-тесты

Unit Testюнит-тест

Одна функция в изоляции, за миллисекунды. Их тысячи.

Arrange–Act–AssertAAA

Подготовь, сделай одно действие, проверь результат.

Интеграция, e2e и пирамида

Integration Testинтеграционный тест

Несколько настоящих частей вместе — проверка швов.

End-to-End TestE2E, сквозной

Вся система глазами пользователя. Реалистично, но медленно и хрупко.

Test Pyramidпирамида тестов

Много юнит, меньше интеграционных, пара e2e. Не рожок мороженого.

Подделки

Test Doubleтестовый двойник

Подделка вместо медленной, платной или случайной зависимости.

Stubстаб

Возвращает заготовленный ответ — кормит код данными.

Mockмок

Запоминает вызовы — проверяет, что побочный эффект случился.

Over-mockingчрезмерное моканье

Замокано всё — тест зелёный, а настоящая система падает.

TDD и хорошие тесты

TDDred-green-refactor

Сначала падающий тест, потом минимум кода, потом уборка.

Test Behaviour, Not Implementationповедение, а не реализация

Проверяй итог через публичный интерфейс — тест переживёт рефакторинг.

Living Documentationживая документация

Тесты — обещания кодом, которые не устаревают.

Доверие к набору

Code Coverageпокрытие

Сколько кода выполнилось в тестах. Фонарик для пробелов, не мера качества.

Goodhart's Lawзакон Гудхарта

Мера, ставшая целью, перестаёт мерить. 100% покрытия → пустые тесты.

Flaky Testфлакающий тест

Падает случайно. Лекарство — deterministic-тест или удаление.

Применять хорошо

Risk-based Testingтестирование по рискам

Больше тестов там, где сбой дорогой или вероятный.

CI GateCI-гейт

Тесты на каждом изменении; красный — мёрж заблокирован.

Хорошее тестирование — это суждение: тестируй рискованное, держи пирамиду, проверяй поведение, а не реализацию, и гоняй всё автоматически — чтобы зелёный набор правда значил «можно менять».

Конец экспресс-курса · 7 глав · 19 понятий

Дальше — практика: возьми одну важную функцию и напиши для неё пару юнит-тестов — arrange, act, assert, — потом намеренно сломай функцию и посмотри, как тест краснеет и говорит, что именно не так. Потом добавь тест на следующий баг, который найдёшь, — пусть набор растёт вдоль сбоев, которые реально случаются. Но держи одну мысль выше остальных: тесты не про доказательство, что сегодняшний код работает. Это страховка, которая даёт менять завтра без страха, — и именно эта свобода даёт хорошему софту расти дальше, а не медленно каменеть.