~/blog/smoke-test

Проверка должна уметь падать

Зелёная проверка сообщает ровно одно: она не нашла нарушений. Не нашла потому, что всё в порядке, или потому, что не умеет ловить именно эту ошибку, — по зелёному не различить.

Поделиться
Содержание · 5 разделов

Зелёное не значит «работает»

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

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

1Проверка написана и сразу зелёнаямолча
2Проверка прогнана на состоянии до исправлениягромко
3Проверке показан образец с заведомым нарушениемгромко
4Проверка «выглядит правильной» при чтении кодамолча
5Проверка зелёная в чужой сборке, где её никто не смотрелмолча

Три способа увидеть красное

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

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

Третий, самый строгий: мутация. Испортить рабочий код в одном месте и убедиться, что упал именно тот тест, который это место охраняет. Здесь важна деталь, которую легко потерять: падение должно быть содержательным. Если код после порчи перестал разбираться, красное получено не из-за проверки, а из-за синтаксиса, и оно ничего не доказывает.

Пять слоёв: чем выше, тем тише отказзаголовок из курса harness-architecture, приведён дословно↑ Дёшево, воспроизводимо, молча не отказывает
1. Тест / dz guardдетерминированная проверка с кодом возврата
2. Файл ролиподаётся в контекст, но чтение не проверяется
3. Гейт шагамашинно-проверяемый выход на пути конвейера
4. Суждение навыкасработает, если навык вызван удачно
5. Память агента«надо не забыть» — отказывает молча
↓ Вероятностно, отказывает молча
Схема из курса, заголовок процитирован как есть. Направление читается по её собственным подписям: наверху — дёшево, воспроизводимо, отказ всегда виден; внизу — вероятностно и молча. Проверка, которую не видели красной, обладает главным свойством нижних ступеней: она может отказать молча — как бы она ни называлась.

Случай, когда проверка поймала саму себя

Недавний пример из этого проекта. Была написана проверка оформления: она обходит собранный сайт и ищет посторонние цвета и шрифты. С первого запуска — ноль нарушений.

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

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

Дефект нашла не проверка кода и не ревью. Его нашла попытка увидеть проверку красной.

Новый пример: отсутствие данных приняли за ноль

На SEO/GEO-ревью 8 октября нашёл ещё один такой случай. Отчёт сайта запросил у Яндекс Вебмастера показы за 5–7 октября. API вернул пустой список запросов и границы периода: начало — 5 октября, конец — 4 октября. То есть свежие данные ещё не были готовы.

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

Для нового теста нужен именно такой ответ API: пустой список и конец периода раньше начала. На нём правильный результат — «данные ещё не готовы» и неизвестные показатели. Отдельный образец с полным подтверждённым периодом и пустым списком проверяет другой случай: настоящий ноль популярных запросов. Их нельзя смешивать.

Код
{
  "count": 0,
  "queries": [],
  "date_from": "2026-10-05",
  "date_to": "2026-10-04"
}

Проверка содержит четыре сценария: задержку данных, подтверждённый пустой период, неполное покрытие и допустимость сравнения двух завершённых недель. На исправленном коде все четыре проходят. Чтобы проверить сам тест, в отдельной временной копии намеренно заменил состояние задержки на «данные доступны». Один из четырёх сценариев упал именно на этом нарушении. Основной код и данные сайта при этом не менялись.

Этот результат не доказывает правильность всех данных Вебмастера и не гарантирует индексацию сайта. Он доказывает более узкое свойство: тест различает неподготовленные данные и измеренный ноль. Такая граница полезнее общей надписи «отчёт проверен».

При чтении подобного отчёта сначала проверьте, за какие даты источник действительно вернул данные. Если они ещё не готовы, сравнение с прошлой неделей нужно отложить.

Правило проектирования

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

Стоит это недорого — обычно одна команда и один временный файл, — а отличает проверку от её имитации.

Проверка, которую никто не видел красной, — не проверка, а надпись «проверено».

Поделиться
практический пример

Курс skills-qe: свидетельство качества до слова «готово»

бесплатно, без регистрации
открыть курс skills-qe
Следить за новыми записями
aicoding.spaceКурсы, инструменты и мастерская по Agentic Engineering. Код инструментов открыт.
Считаем посещения обезличенно, на своём сервере. Данные не передаются третьим лицам и не используются для рекламы. На время демонстрации подключён виджет-помощник с sufler.aicoding.space: ему уходит только то, что вы ему пишете.