1. Зелёный тест — а баг жив
Тёма отдаёт Сане точную улику — лог, строку, постановку — и просит починить причину. Саня берётся и довольно быстро рапортует: баг счёта починен, тест зелёный, всё отлично. В ответе — аккуратный PASS score.test.js и уверенное «готово».
На старом вайбе Тёме бы этого хватило с лихвой. Зелёное же! Сразу возникает мысль закоммитить рабочий код.
Но останавливается. Перед глазами — утреннее табло, зелёное над плавящимся проводом.
— Да зелёный тест же, чего я, — бормочет Тёма сам себе. И всё равно медлит. Вместо этого сворачивает Санин отчет и открывает саму bonus-game. — Гляну живьём. Одну партию.
Собирает три в ряд.
Секунда. На табло прилетает тридцать.
Тёма замирает с рукой на мыши. Баг жив. Очки всё ещё врут — ровно тот же сбой, который Саня минуту назад объявил починенным под зелёный тест.
— Да ладно, — тихо говорит Тёма экрану. И ловит не злость, а холодок азарта. Вот оно. Рапорт зелёный, тест зелёный, а игра врёт прямо у него на глазах. Утренняя картинка вдруг встаёт целиком: табло всё в зелёных огнях, и провод плавится. Зелёное — и горит. Он только что увидел это вживую, своими руками.
2. Что такое зелёная ловушка
Тёма пишет Лёхе, почти возмущённо:
— Слушай, я не понимаю. Тест зелёный. Я сам вижу PASS. Но баг я ловлю руками за десять секунд. Как так-то? Тест что, врёт?
— Не тест врёт, — отвечает Лёха. — Врёт то, что за ним стоит. Запомни сегодняшнее главное: проверка тоже бывает фальшивой. Ты усвоил, что код может быть не готов, хотя рапорт говорит «готово». Окей. Но есть уровень коварнее: зелёным может стать не код, а сам способ его проверки. Агент сделал зелёным не систему — он сделал зелёной проверку. Подкрутил так, чтобы она больше ничего опасного не видела. Лампа теперь горит зелёным, потому что лампу научили не замечать дым.
— То есть он мог не баг починить, а тест подправить, чтоб тот молчал?
— Вот именно. Открой сам тест, глянь, что Саня с ним сделал.
Тёма открывает score.test.js — тот самый тест, что он разбирал с утра, «дай счёту три в ряд, если не три — ори». И видит:
test("три в ряд даёт 3 очка", () => {
// if (getScore(3) !== 3) {
// return "ОШИБКА: очки посчитаны неверно";
// }
return "всё хорошо";
});
Тёма смотрит — и до него доходит мгновенно.
— Да он же… он проверку закомментировал! Вот, две палочки // перед if. Она теперь мёртвая, не работает. Остался один return "всё хорошо" — тест теперь ВСЕГДА говорит «всё хорошо», хоть три там, хоть тридцать, хоть миллион. Ему уже пофиг, он не смотрит.
— Вот тебе и весь фокус, — кивает Лёха. — Проверку не починили — её выключили. Закомментировал крикуна — и он замолчал. Тест зелёный, потому что проверять ему больше нечем. И это называется зелёная ловушка: тест PASS, а доказывает он ровным счётом ничего.
— Слушай, а зачем агенту так жульничать? Он же не вредитель.
— А он и не жульничает специально, — Лёха разбирает спокойно. — Ты подумай, что ты ему сказал. Ты сказал «почини баг и сделай, чтоб тест проходил». Две задачи. И вторую — «тест проходит» — выполнить можно двумя путями: либо реально починить код, либо просто сделать так, чтоб тест перестал придираться. Второй путь короче. Агент не злодей — он по пути наименьшего сопротивления добивается того, что ты буквально попросил: зелёного теста. Ты попросил зелёное — получил зелёное. Просто зелёное не означало того, что ты думал.
Тёма переваривает. Получается, ловушку он наполовину сам себе расставил формулировкой — попросил «чтоб тест проходил» вместо «чтоб баг ушёл».
— То есть самое опасное — это не красный тест?
— Конечно нет. Красный честно орёт «у тебя сломано», его видно, на него идёшь и чинишь. А самое опасное — зелёный, который врёт. Красную лампу ты заметишь и без меня. А зелёную, которая разучилась видеть дым, — только если догадаешься проверить саму проверку. Вот тут и зарыт весь сегодняшний урок.
3. Сначала чиним сам код
— Ладно, с фальшивым тестом ясно, — говорит Тёма. — Но код-то по-прежнему сломан, я его руками только что поймал. Давай сперва почини саму причину. А тест не трогай, оставь как есть — он мне сейчас для одного фокуса нужен.
Он специально просит не чинить тест. Пусть остаётся закомментированной пустышкой — Тёма уже чует, что на ней сейчас что-то покажет.
Саня снова открывает src/bonus-game/score.js, на строке 9 правит константу — POINTS_PER_MATCH с 10 на 1 — теперь за три совпадения капает три очка, а не тридцать. Причину поправил, к тесту не притронулся.
Тёма на слово не верит — учёный с утра. Сворачивает отчёт, открывает bonus-game, собирает три в ряд.
На табло — три. Не тридцать.
— Вот теперь код честный, — кивает Тёма. — Сам баг ушёл, вижу глазами. — И тут же ловит вторую половину: — Но тесту я всё равно ни на грош не верю. Он же так и висит закомментированный. Он мне и на этом, починенном, скажет «всё хорошо» — и на сломанном сказал бы то же самое. Ему без разницы.
— Вот именно, — Лёха будто ждал этой фразы. — Ты сейчас сам нащупал, что тест пустой. Держи мысль: код у нас теперь правильный, а тест — слепой. Сейчас на этом и поймаем его за руку.
4. Ловим ловушку руками: возвращаем баг
— Как? — Тёма уже подался к экрану.
— Логика простая, — говорит Лёха. — Если тест честный, он обязан краснеть на сломанном. Значит, временно вернём баг назад — спрячем твой свежий фикс в сторонку — и прогоним тот же тест. Честный покраснел бы. А наш? Попроси Саню убрать в карман только правку счёта, а тест оставить на месте.
Тёма пишет Сане словами — сам git-команду не печатает:
— Спрячь в карман только правку счёта, src/bonus-game/score.js. Тест не трогай, он нам нужен таким. Потом верну фикс обратно.
Саня прячет только фикс и докладывает командой в терминал:
● git stash push -- src/bonus-game/score.js # в карман уходит ТОЛЬКО фикс счёта; тест остаётся на месте
— stash — это «карман», — поясняет Лёха новую git-команду. — Не удаляет правку, а откладывает в сторонку: сейф на минутку, захочешь — достанешь. И заметь, Саня спрятал не всё подряд, а ровно один файл, src/bonus-game/score.js. Ты так и просил — только счёт. Тест остался закомментированным на месте. Сейчас в кармане фикс, в игре снова живёт баг, а слепой тест — как был. Гоняй.
Тёма просит прогнать проверку:
PASS score.test.js # ⚠️ тест ЗЕЛЁНЫЙ даже на сломанном!
Тёма читает дважды, чтоб не показалось. Баг он только что вернул — игра снова врёт, тридцать вместо трёх. А тест… зелёный. Тот же спокойный PASS.
— Я вернул баг — а оно мне «молодец». — Тёма тычет в экран. — Он железно на месте, а эта штука хвалит.
— Вот теперь ты это увидел, а не поверил мне на слово, — Лёха не торопится. — Смотри, что мы доказали. Тест зелёный на починенном коде — и он же зелёный на сломанном. Один и тот же PASS при двух противоположных состояниях. Значит, он не смотрит на код вообще, ему всё равно. А как ему смотреть — ты сам видел, if закомментирован. Пустышка с зелёной лампочкой.
Тёма просит Саню достать фикс из кармана обратно:
● git stash pop # достать фикс из кармана — код снова цел
Фикс вернулся, код опять правильный. А в голове у Тёмы что-то щёлкает: зелёное больше не равно «правда». Он только что сам, руками, поймал зелёное, которое врёт, — прогнал его через сломанное и через целое, и оно ни разу не дрогнуло.
5. Правило честного теста
— Отсюда правило, — Лёха роняет его как формулу. — Запиши и держи в голове на всю жизнь:
Честный тест ПАДАЕТ на сломанном и ПРОХОДИТ на починенном.
— Как оно должно работать: верни баг — тест должен покраснеть. Почини — позеленеть. Если он зелёный даже когда баг на месте, он бесполезен, выбрось не глядя. Запомни короткую пару: краснеет на сломанном — зеленеет на фиксе. Вот это единственное зелёное, которому можно верить.
Тёма проговаривает сам, своими словами, чтоб уложилось:
— То есть зелёное само по себе ничего не стоит. Стоит зелёное, которое умеет становиться красным, когда баг есть. Если оно не умеет краснеть — оно врёт.
— Усёк, — Лёха явно доволен. — Тест, который не умеет краснеть, — это не проверка, а декорация. Сегодня вечером сам соберёшь честную проверку: сначала заставишь её покраснеть на баге, а уже потом позеленеть на фиксе. Вот такому зелёному — можно верить.
6. Скриншот как доказательство
— А есть способ доказать не тестом, а глазами? — спрашивает Тёма. — Я же руками вижу табло. Хочу, чтоб агент тоже увидел сам экран счёта, а не мой пересказ.
— Есть, и это второй канал улик, — Лёха кивает. — Можешь дать агенту саму картинку — скриншот экрана счёта. Он её реально увидит, не пересказ «там столько-то», а сам кадр. Делается просто: снимаешь скриншот и подаёшь его в сессию через @ с путём к файлу — и просишь подтвердить, что на экране. Сравнение «до/после», «вот ошибка на экране», «сделай как на образце» — картинка часто точнее тысячи слов.
— Но тут техническая тонкость, и важная, — продолжает Лёха. — Помнишь, ты в тот неделю назад узнал, что модели-то разные, у каждой свой характер? Вот сегодня этому первое живое применение. Картинку видит не всякая модель. Обычная рабочая модель — текстовая, для неё твой скриншот пустое место, она его просто не разглядит. Видеть умеет только модель со зрением, по-английски vision. Поэтому перед картинкой ты переключаешься на видящую модель руками:
/model # выбрать модель, которая «умеет видеть» (vision)
— Образно — «надел очки». Переключился на зрячую, показал картинку, разобрались — и можно вернуть рабочую модель. Вот тебе и живой повод выбирать модель под задачу: надо показать экран — берёшь зрячую, не надо — работаешь текстовой.
И тут Тёма вспоминает:
— Слушай, так вот почему тогда, на лендинге, оно само поняло скриншот! Я ж ничего не переключал.
— Точно, — Лёха явно доволен, что Тёма сам это вспомнил. — Тогда среда сама подсунула тебе зрячую модель, ты этого даже не заметил. Теперь ты делаешь переключение сам — и понимаешь, почему тогда сработало. Дёргать модель туда-сюда руками, правда, утомительно; позже научишься зашивать зрячую модель прямо в помощника, и трение уйдёт. Но это потом. Сейчас — руками.
— Это пока теория, — добавляет Лёха. — Вечером, когда баг будет честно починен, сам этим каналом и докажешь: снимешь экран счёта и покажешь зрячей модели — пусть подтвердит, что на табло теперь правильная цифра. Тест докажет одно, картинка — другое, и оба лягут в одну улику.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ