awesome-everything EN
↑ Обратно к восхождению

Инженерная практика

Тестовые дублёры: London против Detroit и ловушка чрезмерного мокинга

Суть Mockist-тесты (London) проверяют взаимодействия; classicist-тесты (Detroit) — состояние на настоящих коллабораторах. Мокайте границы — сеть, часы, оплату — внутри берите настоящие объекты. Чрезмерный мок привязывает тесты к структуре: ломаются на рефакторинге, зелены на багах.
Высота — путь к senior
НольJuniorMiddleSenior
Ты на middle-высоте — в небе
◷ 16 min

Команда мокает всё. Каждый коллаборатор в каждом юнит-тесте — это мок с expect(...).toHaveBeenCalledWith(...). Их набор — это 2400 тестов, все зелёные, 91% покрытия. Затем рефакторинг, разбивающий один сервис на два — чисто внутренняя реорганизация, поведение идентично, — за один день красит 600 тестов в красный. Ни один из них не поймал баг; они поймали лишь факт, что внутренние вызовы переместились. Хуже того, месяцем ранее реальный баг в ценообразовании уехал в продакшен с полностью зелёным набором, потому что моки возвращали заготовленные значения и никто не проверял реально вычисленную сумму. Всё это время набор измерял не то.

Пять видов дублёра и тот, что кусается

«Мок» используют как универсальный ярлык, но xUnit Test Patterns называет пять различных тестовых дублёров, и это различие решает, будет ли тест надёжным или хрупким. Dummy (пустышка) — это заглушка, которую передают, но никогда не используют. Stub (стаб) возвращает заготовленные ответы на вызовы — он подаёт состояние внутрь. Fake (фейк) — это рабочая облегчённая реализация (репозиторий в памяти вместо Postgres). Spy (шпион) записывает, как его вызывали, чтобы вы могли проинспектировать это после. Mock (мок) запрограммирован ожиданиями: он утверждает, что конкретные вызовы случились, конкретным образом, и валит тест, если их не было.

Первые четыре подают входы или наблюдают выходы; мок — единственный, кто утверждает взаимодействия, и именно отсюда берётся хрупкость. Мок вшивает структуру вызовов продакшен-кода в условие прохождения/падения теста. В тот миг, когда вы меняете, как код достигает результата — даже при идентичном наблюдаемом поведении, — ожидания мока больше не совпадают, и тест падает. Стабы и фейки так не делают; они просто подают данные и дают вам проверить конечное состояние. Хвататься за мок там, где хватило бы стаба, — это самый частый способ, которым команды производят хрупкие тесты.

London (mockist) против Detroit (classicist)

Эти две школы — реальное, поимённо названное разногласие. Школа London / mockist работает снаружи-внутрь: юнит — это один класс, вы мокаете всех его коллабораторов и тестируете, утверждая взаимодействия между объектами — что нужные сообщения были посланы в нужном порядке. Школа Detroit / classicist (также Chicago) работает изнутри-наружу: юнит — это поведение, которое может охватывать несколько настоящих объектов, вы используете настоящих коллабораторов везде, где можете, мокаете лишь то, что обязаны, и утверждаете конечное состояние, а не вызовы.

Практическое следствие — это то, что переживает рефакторинг. Classicist-тесты утверждают результаты, поэтому они позволяют беспощадный рефакторинг — вы можете свободно реструктурировать внутренности, и тест остаётся зелёным, пока результат верен. Mockist-тесты утверждают структуру вызовов, поэтому они фиксируют реализацию: они точно ловят замысел дизайна, но ломаются всякий раз, когда меняется внутреннее взаимодействие. Сила London — быстрая обратная связь по дизайну снаружи-внутрь и крошечные изолированные юниты; её режим отказа — это ровно тот день с 600 красными тестами: набор, настолько привязанный к структуре, что рефакторинг становится непомерно дорогим.

АспектLondon / mockistDetroit / classicist
Юнит = Один классПоведение через настоящих коллабораторов
КоллабораторыЗамоканыНастоящие везде, где возможно
ПроверяетВзаимодействия (какие вызовы случились)Конечное состояние / результат
Переживает рефакторинг?Часто ломается — привязан к структуре вызововДа — ломается только при смене поведения
Режим отказаХрупкий набор, дорогие рефакторингиТруднее локализовать падение

Правило границ разрешает спор

Синтез сеньора — это не «выберите школу». Это правило границ: мокайте то, что не можете запустить, используйте настоящие объекты для того, что можете. Мокайте вещи медленные, недетерминированные или с побочными эффектами, которые нельзя откатить — сеть, часы, платёжный процессор, отправитель писем, сторонний API. Для коллабораторов, которыми вы владеете и которые работают быстро и чисто, используйте настоящий объект или фейк; проверка конечного состояния поверх настоящих внутренних объектов даёт вам тест, который ломается только тогда, когда ломается поведение. Это в основном позиция classicist с дисциплиной London, применённой на краях системы, и это растворяет ловушку чрезмерного мокинга: вы никогда не мокаете объект-значение или чистый хелпер, потому что не от чего изолировать.

Решающий вопрос для любого дублёра: если я рефакторю внутренности, не меняя поведения, должен ли этот тест упасть? Если ответ «нет», вы используете мок там, где место стабу или настоящему объекту. Чрезмерный мокинг — это когда ответ «да» для тестов кода, которым вы управляете, — и это ровно то, что превращает зелёный набор в 600 красных тестов, не поймавших ни одного бага, пока реальная ошибка в ценообразовании просочилась, потому что моки возвращали заготовленные числа, которые никто никогда не сверял с реальным вычислением.

Почему это работает

Чрезмерный мокинг ощущается продуктивным, потому что делает каждый тест быстрым, изолированным и тривиально детерминированным — вы управляете каждым входом. Но этот контроль и есть ловушка: тест, где вы застабили каждого коллаборатора, в пределе — это тест того, что код вызывает методы, которые вы велели ему вызвать. Он не может поймать интеграционный баг между двумя настоящими объектами и не может поймать неверный результат, если вы застабили результат. Моки превращают тест в зеркало реализации, поэтому он отражает каждое структурное изменение как падение и не отражает ничего из поведенческой правды. Настоящие коллабораторы медленнее и труднее в настройке, но они — единственный способ, которым тест может разойтись с кодом.

Выбери лучший вариант

Вы тестируете PriceCalculator, который использует объект TaxRule (чистый, ваш собственный) и CurrencyApi (сторонний HTTP). Как продублировать каждый?

Викторина

Чем мок отличается от стаба и почему это важно для хрупкости?

Викторина

Чисто рефакторинговое разбиение одного сервиса на два красит 600 мок-нагруженных тестов в красный, ни один не ловит баг. В чём корневая причина?

Расставь шаги по порядку

Упорядочьте, как деградирует зелёный, но бесполезный чрезмерно замоканный набор:

  1. 1 Каждый коллаборатор замокан, включая чистые объекты, которыми вы владеете
  2. 2 Тесты проверяют, какие вызовы случились, а не итоговый результат
  3. 3 Застабленные возвращаемые значения означают, что реальное вычисление никогда не проверяется
  4. 4 Реальный баг уезжает в прод с полностью зелёным набором
  5. 5 Сохраняющий поведение рефакторинг красит сотни тестов в красный без всякого бага
Вспомните перед уходом
  1. 01
    Объясните London против Detroit в TDD и как правило границ разрешает это разногласие.
  2. 02
    Как чрезмерно замоканный набор может быть зелёным на 91% и всё же пропустить реальный баг, ломаясь на ничего не меняющем рефакторинге?
Итог

«Мок» — это универсальный ярлык, но пять тестовых дублёров различаются способами, которые решают надёжность: dummy, стабы, фейки и шпионы подают входы или наблюдают выходы, тогда как мок утверждает, что конкретные взаимодействия случились — и эта проверка вызовов и есть источник хрупкости. Школа London (mockist) трактует юнит как один класс, мокает каждого коллаборатора и проверяет взаимодействия; школа Detroit/Chicago (classicist) трактует юнит как поведение через настоящие объекты, мокает лишь то, что обязана, и проверяет конечное состояние. Classicist-тесты переживают рефакторинги, потому что проверяют результаты; mockist-тесты фиксируют реализацию и ломаются при смене взаимодействия, и так сохраняющий поведение рефакторинг красит 600 тестов в красный без единого бага среди них, пока реальная ошибка ценообразования уезжает зелёной за заготовленными значениями стабов. Решение сеньора — правило границ: мокайте то, что не можете запустить — сеть, часы, оплату, почту, сторонние API, — и используйте настоящие объекты или фейки для быстрого, чистого кода, которым владеете, проверяя конечное состояние. Решающий вопрос для любого дублёра — должен ли сохраняющий поведение рефакторинг уронить этот тест; если не должен, вы схватились за мок там, где место стабу или настоящему объекту.

Связанные уроки
Продолжить восхождение ↑Property-based-тестирование: инварианты вместо примеров, со сжатием контрпримера
хоткеи развернуть
поиск
K
пред. пьеса
k
след. пьеса
j
тиры
t
это меню
?
sources4
expand
  1. 01
  2. 02
  3. 03
  4. 04

Trademarks belong to their respective owners. Editorial reference only.