Тестовое задание про eval-раннер ломается тихо. Кандидат присылает зелёный прогон, второй проверяющий открывает его у себя и получает красное. Код при этом нормальный. Просто проверки сцеплены с конкретным эндпоинтом, а не с документом данных.
Дефект называется swap drift. Набор тестов зелёный только на том эндпоинте, который использовался в день черновика. Поменяли base URL, id модели или хост с фикстурой — assertions посыпались. Проверяющий часто записывает это в сбой сервиса, хотя тесты были привязаны к одному вендорскому конверту.
Почему это больно на handoff
Сравнивать кандидатов в цикле найма нечем, если у проверяющих нет общего документа и общего порта. Две оценки не могут быть честными, когда каждая демонстрация зависит от приватного ключа и процесса на ноутбуке, который засыпает в самый неподходящий момент.
Хостируемая фикстура с записанным хешем убирает отговорку для документа, который оба проверяющих обязаны прочитать. Сменный адаптер убирает парную отговорку для пути получения ответа — передача перестаёт зависеть от одной машины.
За рамками задания остаются ретраи, тайминги outbox и sticky-флаги. У этих историй свои листы оценки, и смешивать их с контрактом фикстуры не стоит.
Что уходит кандидату
Промпт держится на одной странице и называет файлы, а не любимый бренд. Отдельной строкой — что живой вызов опционален. Если ключа нет, сетевой путь пропускается, а полный отчёт по заглушке всё равно должен собраться. Числового лимита в задании нет: это число не часть проверяемого контракта.
Шесть условий
- Прочитать локальный fixture.json, вызвать один адаптер и записать report.json с одним объектом решения.
- Брать MODEL_BASE_URL, MODEL_API_KEY и FIXTURE_URL только из переменных окружения — никогда из закоммиченных файлов.
- Хранить SHA-256 байтов фикстуры и падать закрыто, если скачанные байты не совпали с закреплённым хешем.
- Держать три инварианта: цитаты только по id из фикстуры, неизвестные поля отбрасываются, один ключ идемпотентности даёт один decision id.
- Отдать stub-режим без сети и hosted-режим, который тянет тот же самый документ фикстуры.
- Перечислить все непроверенные утверждения — лимиты, сроки хранения, регионы — вместо того, чтобы вписывать их в тесты.
Живой путь можно нацелить на заявленный оператором бесплатный маршрут к модели, а фикстуру опубликовать через бесплатный серверный вариант. Оценка всё равно обязана пройти, когда живой путь пропущен из-за отсутствия ключа. Бесплатный доступ и хостинг здесь ценны ровно одним: оценщик может переиграть те же самые байты. И это операторские заявления, а не измеренная ёмкость — ни квоты токенов, ни профиля железа, ни списка регионов в задании нет, пока их не подтверждает первоисточник. Числовой лимит не должен всплывать ни в assertion, ни в поле фикстуры, ни в комментарии, который поздние тесты примут за факт.
Лист оценки для проверяющего
Оценивается поведение. Проза про продукт баллов не добавляет, зелёный скриншот без хеша не считается. Живой путь помечается как пропущенный, если он в этой сессии реально не запускался. И проход только на заглушке нельзя повышать до письменного утверждения про какой-то конкретный маршрут модели.
| Проверка | Проходит | Провалено |
|---|---|---|
| Фиксация фикстуры | В отчёте лежит SHA-256 ровно тех байтов | Assertion ищет id модели |
| Смена адаптера | Stub и live делят одну схему | Live-режим использует второй парсер |
| Забор области | Неизвестные поля отклонены или отброшены | Лишние поля скопированы в решение |
| Идемпотентность | Тот же ключ — тот же decision id | Каждый вызов чеканит новый id |
| Работа с секретами | Ключ читается только из окружения | Ключ или токен попал в отчёт |
| Гигиена утверждений | Непроверенные лимиты опущены | Фиксированная квота подана как факт |
Даже четыре пройденные проверки — полезный сигнал в этом узком срезе тестового. А вот отсутствие фиксации фикстуры — автоматический провал: все остальные пункты могут выглядеть зелёными на неправильном документе. Сама таблица — подспорье для оценки, а не заявление о том, что какой-то хостируемый маршрут держался в течение измеряемого окна.
Как выглядит раннер
Логика разбита на три функции, и её стоит описать словами, а не пересказывать построчно.
- load_fixture(url, local) — если url пуст, читает байты локального файла; иначе тянет по сети с таймаутом.
- decide(fixture, completion) — собирает множество допустимых id, вытаскивает из ответа токены вида id:…, отбрасывает лишние поля и возвращает один из трёх исходов: unknown_field, out_of_scope, in_scope.
- run(base) — берёт FIXTURE_URL, локальный fixture.json, считает digest, сверяет с FIXTURE_SHA256 и падает насмерть при расхождении, а затем пишет отчёт с полями fixture_sha256, live_configured и decision.
Эта реализация — предложенный образец, который проверяющие адаптируют под свой пакет. Она не запускалась для статьи и не является замером задержки или стоимости. Живой вызов остаётся опциональным, чтобы детерминированной оценкой для проверяющего был stub-путь. Кандидат, добавляющий настоящий HTTP-клиент, обязан сохранить ту же функцию decide для обоих режимов.
Ошибки, которые я вижу чаще всего
- Assertion проверяет строку с названием модели. Сменилась модель — сменился вердикт.
- Ключ уехал в отчёт или в коммит вместе с примером конфига.
- Каждый вызов генерирует новый decision id, и идемпотентность проверяется только на словах.
- Неизвестные поля из ответа аккуратно переложены в решение — и граница области исчезла.
- Квота, срок хранения и регион вписаны в тесты как данность. Их место в README с пометкой «не проверено».
Когда формат не подходит
Если задача про поведение под нагрузкой, ретраи и порядок сообщений в очереди — это другой лист оценки, и подмена его контрактом фикстуры ничего не покажет. Точно так же бессмысленно требовать фиксации хеша там, где кандидат по условию работает с потоком данных, который нельзя заморозить.
Смысл упражнения узкий и от этого честный: проверяется, умеет ли человек отделить контракт от вендора. Замороженная фикстура, общая схема решения и инварианты без имён — это и есть тот результат, который доживает до второго проверяющего. Всё остальное про конкретный эндпоинт живёт ровно до первого переключения base URL.
Комментарии (0)
Войдите, чтобы комментировать.
Пока нет комментариев. Будьте первым.