Pull to refresh

Как я перестал бояться тестов и полюбил зелёный CI

Level of difficultyEasy
Reading time10 min
Views1.3K

> ✍️ О себе: Пишу боевые юнит-тесты для React уже 4 года в продакшене, а не для «todo-list». Знаю школы тестирования, прошёл путь от Enzyme до RTL — и создал свою библиотеку shallowly. Пишу код вслепую в Neovim и уверен: времени «нет» только у тех, кто предпочитает кликать мышью в IDE вместо написания тестов.

Словарь терминов

  • Юнит (Unit) — наименьшая тестируемая часть приложения. В React это обычно компонент, хук или функция в изоляции

  • Shallow rendering — поверхностный рендеринг компонента без рендеринга его дочерних компонентов

  • Mock — имитация зависимости, которая заменяет реальную реализацию в тестах

  • TDD (Test-Driven Development) — разработка через тестирование, когда тест пишется до кода

  • CI/CD — автоматизированная система сборки и деплоя

Исповедь бывшего кликера

Когда-то мой чек-лист «готова ли фича» выглядел как молитва джуна: открыть браузер, нажать пару кнопок, убедиться, что в консоли нет красного цвета (желтое — это нормально, да?), и смело делать merge.

В те времена React был еще зеленым, Backbone уходил в архив, а модные парни на конференциях говорили про какое-то тестирование. Я слушал их как индеец — много слов, мало понимания.

Проект жил быстро, но баги плодились еще быстрее. Каждый хот-фикс тянул за собой новый хот-фикс, как матрешка багов. Я начал бояться рефакторинга сильнее, чем дантист боится собственных зубов.

Переломный момент настал ночью перед релизом. Один пропущенный null-чек положил всю систему авторизации. Пока я в 3 утра откатывал релиз, понял: так больше жить нельзя.

Эволюция подходов: как я перестал бояться и полюбил тесты

Этап 1: Скриншот-тестирование «на коленке»

Первым делом попробовал snapshot-тесты. Логика была простая: «Если diff не красный — значит все хорошо». Быстро понял, что это как охранять дом сигнализацией, которая срабатывает от пролетающей мухи. Поменял padding на 2px — тест красный. Исправил опечатку в тексте — тест красный. Добавил запятую — красный.

Этап 2: E2E через страдания

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

Но оказалось, что E2E тесты как дорогая машина: выглядят круто, но постоянно ломаются, требуют ухода, и когда что-то не работает — фиг поймешь где проблема. Плюс ждать 10 минут результата теста — это как ждать загрузки интернета в 2003 году.

Этап 3: Юнит-тесты как просветление

И тут я понял истину: юнит-тесты это не про количество, а про качество жизни разработчика. Они:

  • Ловят очевидные ошибки еще до браузера (null-check привет!)

  • Дают смелость резать мертвый код без страха что-то сломать

  • Документируют намерения лучше любых комментариев

  • Работают молниеносно — фидбек за пару миллисекунд, а не минуты

Сейчас в нашем репозитории 3000+ тестов, а CI за 2–3 минуты говорит, можно ли деплоить. Да, писать тесты — «дорого», но постоянно чинить баги в сложном коде да еще и продакшене дороже в разы.

Типы тестирования: зоопарк подходов

Рассмотрим основные типы тестирования, чтобы понимать, откуда ноги растут. Кстати, интересный факт: если бы у тестирования был свой Оскар, то награда за лучшую мужскую роль досталась бы Кенту Беку за вклад в разработку через тестирование (TDD) в конце 90-х. Но давайте по порядку.

🧪 Юнит-тесты

Родоначальник: Кент Бек (да, он снова здесь) в рамках методологии экстремального программирования (XP) в конце 90-х.
Современные амбассадоры:

  • Мартин Фаулер, который до сих пор яростно спорит о том, что считать «юнитом»

  • Роберт (Боб) Мартин (Дядя Боб), который считает, что если у вас нет 100% покрытия, вы, вероятно, что-то упускаете

  • Стив Фримен (один из авторов «Growing Object-Oriented Software»), который знает о юнит-тестах всё и даже больше

  • Дэвид Хейнемейр Ханссон (DHH), который сначала ругал TDD, а потом полюбил его

Философия: Создавались как основа TDD (разработки через тестирование). Идея в том, чтобы проверять мельчайшие кирпичики кода в изоляции, как инженер проверяет шестерёнки перед сборкой механизма. Юниты — это про уверенность, что каждая функция работает как часы, даже если вокруг всё горит.

Что тестируют: Отдельные функции, компоненты в изоляции
Скорость: Молниеносно (миллисекунды)
Плюсы: Быстро, детерминированно, легко отлаживать
Минусы: Не видят проблем интеграции

// Пример юнит-теста
test("Calculator сумма двух чисел", () => {
  const calc = new Calculator();
  expect(calc.add(2, 3)).toBe(5);
});

🔄 Интеграционные тесты

История: Появились как ответ на проблему «все тесты проходят, а продакшен падает». Впервые систематизированы Майклом Фезерсом в середине 2000-х.
Современные гуру:

  • Сэм Ньюмен, автор книги «Монилитный кризис» и ярый проповедник микросервисов

  • Мартин Фаулер, который считает, что если ваш интеграционный тест не падает хотя бы раз в неделю, вы, вероятно, тестируете не всё

  • Ибрагим Чешмелиоглу, автор «Testing JavaScript Applications», который знает о тестировании фронтенда всё

  • Сэнди Метц, которая умеет объяснять сложные концепции тестирования так, что даже менеджеры кивают

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

// Пример интеграционного теста
test("Button должен вызывать onClick при клике", () => {
  const onClick = jest.fn();
  const { getByText } = render(<button>Click me</button>);

  fireEvent.click(getByText("Click me"));
  expect(onClick).toHaveBeenCalledTimes(1);
});
// да да для меня это интеграционный тест . Чуть позже раскажу почему.

Что тестируют: Взаимодействие между компонентами/модулями
Скорость: Средняя (секунды)
Плюсы: Видят проблемы интеграции, ближе к реальности
Минусы: Сложнее отлаживать, медленнее

🌐 E2E тесты

История: Зародились в эпоху мейнфреймов, но второе рождение получили с приходом веба. Селин Ласик (Selenium, 2004) — наш герой, который подарил нам все эти browser.click().
Современные гуру:

  • Гилен Дебелп (Gleb Bahmutov), который, кажется, может написать E2E-тест быстрее, чем вы успеете произнести «флакки-тест»

  • Андрей Симонов, создатель CodeceptJS, который доказал, что E2E-тесты могут быть не только медленными, но и быстрыми

  • Филипп Харитонов, автор Playwright, который заставил всех поверить, что стабильные E2E-тесты — это не миф

  • Пол Гросс (Paul Grosse), который умудряется тестировать даже самые сложные веб-приложения, не теряя рассудка

Философия: Это взгляд сверху на всю систему глазами пользователя. Создавались, чтобы убедиться, что путь от логина до чекаута не превратится в квест «найди баг». E2E-тесты — это как генеральная репетиция перед премьерой: всё должно работать как в жизни, даже если под капотом творится хаос.

Что тестируют: Полные пользовательские сценарии
Скорость: Медленно (минуты)
Плюсы: Максимально близко к реальности
Минусы: Хрупкие, медленные, сложные в отладке

Модели тестирования: философские школы и их гуру

Как говорил один мой знакомый тестировщик: «Есть два типа команд: те, кто следует моделям тестирования, и те, кто делает вид, что их не существует». Давайте познакомимся с основными подходами и их создателями.

Далее у нас есть методология тестирования которые определяют сколько и каких тестов должно быть.

🛕 Пирамида тестирования (классика)

Авторство: Майк Кан (Mike Cohn) в книге «Succeeding with Agile» (2009).
Интересный факт: Изначально пирамида была перевернута, но потом Майк одумался (или просто перевернул слайд, история умалчивает).
Современные амбассадоры:

  • Джеральд Вайнберг, который до сих пор учит нас, что «чем ниже, тем лучше»

  • Лиза Криспин, соавтор «Agile Testing», которая знает о пирамиде всё и даже больше

  • Джанет Грегори, которая умеет объяснить пирамиду тестирования так, что даже бизнес-аналитики начинают её использовать

  • Кент Бек, который, кажется, успел оставить свой след во всём, что связано с тестированием

🌐 E2E (мало) — как вишенка на торте
🔄 Интеграционные (средне) — кремовая начинка
🧪 Юнит-тесты (много) — прочное бисквитное основание

Философия: Чем ниже уровень — тем больше тестов. Они дешевле и быстрее. Это как диета: если перевернуть пирамиду, получится торт, который упадёт вам на голову при первой же попытке деплоя.
Когда использовать: Стабильные большие проекты, команды с опытом, которые понимают разницу между тестом и кликбейт-заголовком.

💎 Тестовый алмаз

История: Зародился в недрах микросервисных команд в начале 2010-х, когда разработчики устали объяснять, почему их сервисы не работают вместе, несмотря на зелёные тесты.
Современные амбассадоры:

  • Ник Тюн (Nick Tune), который рисует такие схемы, что хочется распечатать их как постер и повесить на стену с подписью «Вот как надо!»

  • Сэм Ньюмен, который, кажется, знает о микросервисах и их тестировании всё

  • Крис Ричардсон, автор «Microservices Patterns», который умеет объяснять сложные концепции простыми словами

  • Мартин Фаулер, который успел высказаться практически обо всём в мире разработки, включая и эту модель

🌐 E2E (мало) — потому что никто не любит ждать
🔄 Интеграционные (МНОГО) — вот где настоящая магия
🧪 Юнит-тесты (средне) — на всякий случай

Философия: Если юнит-тесты — это проверка каждой шестерёнки по отдельности, то интеграционные — это когда вы собираете велосипед и надеетесь, что он поедет. Акцент на реальные взаимодействия компонентов, потому что в реальном мире никто не живёт в вакууме (кроме, возможно, некоторых менеджеров).
Когда использовать: Микросервисы, сложная интеграция и когда хочется почувствовать себя повелителем распределённых систем.

🍦 Мороженое (антипаттерн)

История: Никто не «придумал» это специально, но стало мемом благодаря командам, которые в 2000-х годах полагались только на Selenium и молитвы.
Известные последователи:

  • Тот самый тимлид в вашей компании, который говорит: «У нас нет времени на юнит-тесты, давайте просто накрутим E2E»

  • Джо Армстронг (создатель Erlang), который как-то сказал: «Единственный способ отлаживать код — это добавлять операторы вывода»

  • Все, кто когда-либо говорил: «Да ладно, у нас же CI/CD, пусть сами тесты падают»

  • Легендарный «Петя из отдела тестирования», который до сих пор верит, что ручное тестирование — это будущее

🌐 E2E (МНОГО) — потому что кому нужны тесты, если можно просто нажать F5?
🔄 Интеграционные (мало) — случайно затесались
🧪 Юнит-тесты (почти нет) — «это же лишний код»

Философия: Строим дом с крыши. Выглядит сладко, пока не начнётся дождь багов. А когда начинается — все дружно удивляемся, почему же так медленно и ничего не работает.
Результат: Медленно, дорого, нестабильно. Но зато можно сказать, что у вас «полная тестовая документация» (из 2000 тестов, которые падают в случайном порядке).
Кто так делает: Те, кто считает, что «тестирование — это когда QA посидел, понажимал кнопочки и сказал, что всё ок».

🏆 Тестовый трофей (Kent C. Dodds, ~2017)

🌐 E2E
🔄 Интеграционные (основа)
🧪 Юнит-тесты
⚡ Статический анализ

Контекст: Появился как ответ на сложности тестирования React-приложений. Кент, кстати, до сих пор уверяет, что это не он придумал, а всего лишь «систематизировал».

Ключевые адепты:

  • Кент С. Доддс (Kent C. Dodds), который, кажется, может написать тест быстрее, чем вы успеете сказать «почему это не работает?»

  • Мишель Титус (Michelle Titus), которая доказывает, что тестирование React может быть не только эффективным, но и приятным

  • Райан Флоренс (Ryan Florence), создатель React Router, который знает о тестировании React-приложений всё (и даже немного больше)

  • Марко Полетто (Marco Piraccini), который умеет объяснить, почему статический анализ — это не просто мода, а must-have

Философия: «Тестируй как пользователь» + акцент на интеграционные
Инструменты: React Testing Library

🧊 Тестовый куб (для больших систем)

Автор: Брайан Оккенфельс (Brian Okken), автор книги «Python Testing with pytest».
Фишка: Когда двумерных моделей стало мало, пришлось добавить третье измерение. Типичная история для программистов.

Многомерная модель с тремя осями:

  • Что: компоненты, контракты, интеграции

  • Как: юнит, e2e, нагрузочные тесты

  • Где: локально, CI, staging, production

Когда использовать: Энтерпрайз, микросервисы, большие команды

Школы юнит-тестирования

теперь почему я говорю что React Testing Library это не юнит тесты.

📚 Классическая школа (Detroit School)

Гуру:

  • Кент Бек, который, кажется, успел основать всё, что можно

  • Мартин Фаулер, который умеет объяснить, почему это работает

  • Сэнди Метц, которая знает о тестировании объектов всё

  • Дэвид Хейнемейр Ханссон (DHH), который сначала ругал TDD, а потом полюбил его

Принципы:

  • Тестируй поведение, а не реализацию

  • Минимум моков, максимум реальных объектов

  • Тест должен быть понятен без изучения кода

🏭 Лондонская школа (Mockist School)

Гуру:

  • Стив Фримен, соавтор «Growing Object-Oriented Software»

  • Нейт Шугармен, который умеет объяснить, зачем вообще нужны моки

  • Мартин Фаулер, который успел высказаться и на эту тему

Принципы:

  • Тотальная изоляция через моки

  • Тестируй взаимодействия между объектами

  • Каждый тест проверяет одну единицу

Тут идет различие что считать юнитом и в каком окружении он должен быть. Мое мнение что юнит это компонент в изоляции от окружающего мира как можно зависимостей особено от библиотек которые мы используем в этом юните.

Так что же насчёт Мартина Фаулера?

Когда речь заходит о тестировании, все почему-то ждут, что Мартин Фаулер даст однозначный ответ. Но он, как настоящий мудрец, предпочитает не вставать ни на чью сторону, оставляя место для манёвра:

  1. Прагматичный баланс — он не фанатик ни одной из школ, а скорее адепт здравого смысла. «Тестируйте так, чтобы было эффективно, а не чтобы угодить гуру» — вот его негласный девиз.

  2. Рефакторинг — наше всё — его подход к тестированию неотделим от рефакторинга. Если ваши тесты не дают вам уверенности при рефакторинге, значит, вы делаете что-то не так. Или делаете это с закрытыми глазами.

  3. Догмам — нет! — Фаулера раздражают священные войны между адептами разных подходов. «Гибкость, а не фанатизм» — мог бы быть его девиз, если бы он любил девизы.

  4. Unit-тесты как страховка — в своей настольной книге «Refactoring» он делает ставку на unit-тесты, но без фанатизма. Примерно как с кофе: одна чашка бодрит, десять — вызывают тремор.

  5. Реализм превыше всего — он признаёт, что в реальных проектах идеальная пирамида тестирования часто превращается в нечто среднее между алмазом и снежинкой, и это нормально. Главное — понимать, зачем вы это делаете.

Если бы Фаулеру пришлось выбирать модель тестирования, он, вероятно, выбрал бы «пирамиду с человеческим лицом» — с акцентом на unit-тесты для рефакторинга и достаточным количеством интеграционных тестов, чтобы не краснеть перед продакшеном. Но он бы точно предупредил: «Любая модель — это всего лишь модель. Не путайте карту с местностью, а тесты — с реальным кодом».

Практические выводы

После 4 лет боевого тестирования вот что я понял:

  1. Начинайте с юнитов — они дают максимум пользы за минимум времени

  2. Пирамида работает для большинства проектов, но не бойтесь адаптировать

  3. Качество важнее количества — лучше 10 хороших тестов, чем 100 плохих

  4. Тесты — это инвестиция — но тесты не должны быть дорогими, потом окупается с лихвой

Рекомендуемая литература

Книги на русском

  1. Мартин Фаулер, «Рефакторинг. Улучшение существующего кода» — классика о тестировании как основе для безопасного рефакторинга.

  2. Кент Бек, «Разработка через тестирование» — манифест TDD от его создателя.

  3. Стив Фримен, Рой Ошероув, «Тестирование JavaScript-приложений» — про тестирование в экосистеме JavaScript.

  4. Виктор Сапаров, «Тестирование Дот Ком» — отличное введение в тестирование от российского автора.

  5. Сергей Аверин, «Тестирование программного обеспечения» — базовый учебник по тестированию.

  6. Владимир Хориков. «Принципы юнит-тестирования» — Фундаментальная книга отечественного автора про юнит тестироание.

  7. Лукас да Коста. «Тестирование JavaScript» — Довольно не плохая книга, которая в полной мере раскрывает тему автоматизированного тестирования.

Книги на английском

  1. Robert C. Martin, "Clean Code" — главы про тестирование обязательны к прочтению.

  2. Kent Beck, "Test-Driven Development: By Example" — must-read для понимания TDD.

  3. Luca Mezzalira, "Frontend Architecture for Design Systems" — про тестирование в современных фронтенд-архитектурах.

  4. Eric Elliott, "Composing Software" — о композиционном подходе к тестированию.

Статьи и онлайн-ресурсы

На русском:

На английском:

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


В следующих частях разберем, почему юнит-тесты — это турбо-режим для разработки, как TDD меняет подход к коду, и почему я создал библиотеку shallowly вместо того, чтобы мучиться с RTL.


Следующая статья: «Почему именно юнит-тесты: от TDD до архитектуры компонентов»

P.S. Если вы до сих пор тестируете только через браузер — не расстраивайтесь. Мы все когда-то были такими. Главное — начать. А с этими книгами вы точно поймете, что к чему в мире тестирования.

Tags:
Hubs:
+3
Comments7

Articles