Тесты пайплайнов данных
Пайплайн данных тестируют не так, как думают новички: не гоняя его целиком в прод-БД, а отделив чистую логику от I/O. Собес проверяет, умеешь ли ты сделать трансформации тестируемыми и покрыть края, а не только happy path.
Стержень: логика трансформации - чистые функции на маленьких данных, I/O мокается; данные проверяются и в рантайме, не только в тестах.
// Формулировки: «как тестируешь пайплайн?», «что мокаешь?», «какие края проверяешь?».
Отделить логику от I/O
Первый принцип - вынести чистую логику трансформации из чтения-записи. Тогда трансформации тестируются юнитами на маленьких списках или DataFrame, а I/O живёт в тонком слое и мокается. Логика, переплетённая с чтением и записью в одной функции, нетестируемый монолит.
Тест трансформации это рукотворный вход и ожидаемый выход, обязательно включая края: пустой вход, NULL и пропуски, дубли, одна строка, битые значения.
// Тестируют не только happy path, но и обработку сбоев: что делает пайплайн на таймауте, битой записи, пустом ответе. Поведение под сбоем - тоже часть контракта, и именно на нём прод падает первым.
- чистая функция
- трансформация без I/O - тестируется юнитом
- края (edge cases)
- пусто, NULL, дубли, одна строка, битое
Инструменты и моки
pytest даёт нужное: fixtures готовят данные, parametrize прогоняет матрицу входов-ожиданий одним тестом, tmp_path - для файлов, conftest.py шарит фикстуры между тестами.
Внешние системы мокают: responses или monkeypatch для API, sqlite или testcontainers для БД. Юнит-тесты не должны ходить в сеть и прод, иначе они флаки, медленные и зависят от чужого состояния.
// Для DataFrame сравнивают через assert_frame_equal из pandas.testing - с допуском по float и понятным диффом, а не ручным df1.equals(df2), который на несовпадении молчит про причину. И float сравнивают через approx, не ==.
- parametrize
- один тест × таблица входов-ожиданий
- testcontainers
- реальные сервисы в докере на время теста
Golden-тесты и рантайм-контракты
Golden-тесты: эталонный вход и зафиксированный выход целого шага. Они ловят незапланированные изменения поведения при рефакторинге - вывод «поехал», тест краснеет.
Но тесты проверяют код на известных данных, а прод получает неизвестные. Поэтому данные проверяют и в рантайме: контракты на схему и инварианты (not null, уникальность ключа, диапазоны, свежесть) на входе и выходе шага.
// Классическая дыра - зелёные тесты на схему, которую прод уже не шлёт: тесты проверяют вчерашний контракт, а источник поменялся. Контракты обязаны жить и в рантайме.
- golden test
- сравнение с зафиксированным эталонным выходом
- data contract
- формальные ожидания к схеме и инвариантам данных
Как отвечать: «Как тестировать пайплайн данных?»
Начинаю с того, что отделяю чистую логику трансформации от ввода-вывода. Трансформации делаю функциями, которые принимают данные и возвращают данные, без чтения и записи внутри, их я покрываю юнит-тестами на маленьких рукотворных входах. И тестирую не только счастливый путь, а именно края: пустой вход, NULL, дубли, одна строка, битые значения - прод падает как раз на них. Внешние системы мокаю: API через responses, базу через sqlite или testcontainers, чтобы тесты не ходили в сеть и не были флаки. Для целых шагов держу golden-тесты - эталонный вход и зафиксированный выход, они ловят регресс при рефакторинге. И, что важно, не полагаюсь только на тесты: прод получает неизвестные данные, поэтому в рантайме проверяю контракты на схему и инварианты на входе и выходе, потому что источник меняется, а тесты знают лишь вчерашний формат.
Почему это сильный ответ: главный принцип (логика без I/O), акцент на краях и сбоях, правильные моки, golden-тесты и - ключевое - рантайм-контракты поверх тестов; видно, что человек ловил прод-сюрпризы.
На чём валят
- −Тестировать только happy path - прод падает на первом NULL.
- −Тесты, ходящие в реальное API/БД - флаки, медленно, зависят от чужого стейта.
- −Сравнение float через == вместо approx/assert_frame_equal.
- −Логика внутри функции с чтением/записью - нетестируемый монолит; раздели.
- −Зелёные тесты на схему, которой прод уже не шлёт - контракты живут в рантайме тоже.
Проверьте себя
Пять вопросов из банка по этой подтеме. Всего их 11, остальные разбираются в тренажёре.
- Зачем писать автоматические тесты для data-пайплайна?A)Автотесты для data-пайплайна не нужны, ведь входные данные и так всё время меняютсяB)Достаточно один раз глазами посмотреть на выгрузку и больше её не проверятьC)Тесты пайплайна замеряют скорость его работы под нагрузкойD)Логика трансформаций — код; тесты ловят регресс при правках
показать ответ и разбор
+D)Логика трансформаций — код; тесты ловят регресс при правках// разбор: Логика трансформаций — это код, и он ломается при правках, как любой другой. Тест на фиксированных входных данных с ожидаемым выходом фиксирует поведение и ловит регресс при рефакторинге или добавлении фич. Это отдельно от проверок качества данных (те валидируют текущие данные, тесты — логику кода).
- Почему юнит-тест трансформации не должен ходить в боевую БД или сеть?A)Тест должен быть детерминирован и быстр — фикстуры и моки, а не продB)Юнит-тест должен ходить в боевую продовую базу, иначе он проверяет не реальное поведениеC)Тесты специально гоняют по сети, чтобы заодно нагрузить внешние сервисыD)Детерминированность теста в дата-инженерии не имеет вообще никакого значения
показать ответ и разбор
+A)Тест должен быть детерминирован и быстр — фикстуры и моки, а не прод// разбор: Поход в боевую БД или сеть делает тест медленным, недетерминированным (данные меняются, сервис флапает) и небезопасным. Логику проверяют на маленьких фикстурах с известным входом и ожидаемым выходом, а внешние вызовы подменяют моками. Тест обязан давать один и тот же результат при каждом запуске.
- Почему тестировать пайплайн только на «счастливом» датасете недостаточно?A)Достаточно протестировать пайплайн на одном чистом «счастливом» датасетеB)Проверять и краевые случаи: пустой вход, дубли, null, битые типыC)Краевые случаи в данных на проде встречаются редко, ими можно пренебречьD)Тестировать трансформации на реальных данных бессмысленно
показать ответ и разбор
+B)Проверять и краевые случаи: пустой вход, дубли, null, битые типы// разбор: Happy path прячет ровно те баги, что взрываются в проде: пустая партиция, дубли ключей, null в обязательном поле, неожиданный тип, смена схемы. «Золотой» датасет с ожидаемым выходом плюс явные edge-кейсы дают уверенность, что трансформация не молча портит данные на нестандартном входе.
- Зачем валидировать данные схемой (pydantic/Great Expectations), а не только тестировать код?A)Юнит-тесты самого кода заодно обеспечивают корректность входных данных пайплайнаB)Код может быть верным, а данные — грязными; валидация ловит нарушения контракта в рантаймеC)Валидация данных заменяет собой юнит-тесты логики трансформацийD)Схема данных нужна для ускорения работы пайплайна на больших объёмах
показать ответ и разбор
+B)Код может быть верным, а данные — грязными; валидация ловит нарушения контракта в рантайме// разбор: Тесты кода проверяют логику на фиксированных примерах, но ничего не говорят о реальных данных, что придут в проде. Валидация схемой в рантайме проверяет фактический поток — типы, диапазоны, обязательность, уникальность — и ловит битый источник до того, как он отравит витрину. Это дополняет юнит-тесты, а не заменяет их.
- Как тестировать функцию, которая ходит во внешний API, не вызывая его по-настоящему?A)Вызывать боевой внешний API прямо изнутри теста, чтобы проверка была честнее и ближе к продовому поведениюB)Удалить сетевой код из функции на время прогона тестовC)Подменить вызов моком/стабом, возвращающим заранее заданный фиксированный ответD)Запускать такой тест вручную раз в месяц по расписанию
показать ответ и разбор
+C)Подменить вызов моком/стабом, возвращающим заранее заданный фиксированный ответ// разбор: Боевой вызов делает тест медленным, недетерминированным и небезопасным. Внешний вызов подменяют моком/стабом, возвращающим фиксированный ответ, — тогда логика функции проверяется изолированно и воспроизводимо. Моком удобно и смоделировать сбой (таймаут, 500), чтобы проверить, что функция корректно его обрабатывает и ретраит.
дальше
Теорию прочитали. Навык ставится повторением
В Сеньорчике эта подтема идёт в ежедневных сессиях: движок возвращает её, пока ответы не станут уверенными, и ведёт прогресс отдельно по каждой подтеме. Теория внутри тоже бесплатна, лимит только на количество вопросов в день.