dbt tests на собеседовании Data Engineer
Содержание:
Почему dbt tests спрашивают
Тесты — это то, что отличает «накидал SQL-моделей» от «построил надёжный DWH». На собесе Data Engineer вопрос про dbt tests проверяет, понимаете ли вы разницу между кодом, который просто работает, и кодом, которому можно доверять в проде. Если пайплайн молча пропускает дубли или NULL там, где их быть не должно, дашборды врут, а бизнес принимает решения по кривым цифрам — и это уже не техническая проблема, а репутационная.
Механика простая: dbt-тест — это SQL-запрос, который должен вернуть ноль строк. Каждая вернувшаяся строка — это нарушение. dbt test прогоняет все тесты, а dbt build чередует построение моделей и их проверку, чтобы битые данные не поехали дальше по DAG. На собесе обычно просят разобрать четыре вещи: встроенные generic-тесты, singular-тесты, свои переиспользуемые тесты и настройку критичности (severity). Разберём по порядку.
Generic tests
Generic-тесты (их же называют schema-тестами) — это готовые проверки, которые вешаются на колонку прямо в YAML. Из коробки в dbt их четыре, и они закрывают львиную долю ежедневных проверок качества:
unique— в колонке нет дублей. Классика для первичных ключей.not_null— в колонке нет NULL. Проверяет обязательные поля.accepted_values— значения из заранее заданного списка. Ловит опечатки в статусах и категориях.relationships— ссылочная целостность (FK): каждое значение есть в родительской таблице.
columns:
- name: id
tests: [unique, not_null]
- name: status
tests:
- accepted_values:
values: ['active', 'inactive']
- name: customer_id
tests:
- relationships:
to: ref('customers')
field: idПод капотом каждый такой тест dbt компилирует в обычный SELECT. Например, not_null превращается в SELECT * FROM model WHERE id IS NULL: вернулись строки — тест не прошёл. Это удобно объяснять на собесе: никакой магии, просто сгенерированный запрос, который ищет нарушения.
Singular tests
Singular-тесты — это одноразовые проверки под конкретную бизнес-логику, которую не выразить готовыми generic-тестами. Пишутся как обычные .sql-файлы в папке tests/ и представляют собой запрос, выбирающий «плохие» строки.
-- tests/no_negative_amounts.sql
SELECT *
FROM {{ ref('orders') }}
WHERE amount < 0;Правило то же: тест проходит, если запрос вернул ноль строк, и падает, если вернул хотя бы одну. Singular-тесты хороши, когда проверка привязана к одной конкретной модели — например, «сумма заказа не может быть отрицательной» или «дата отгрузки не раньше даты заказа». Но если такую же логику хочется применить к десятку моделей, копипастить SQL — плохая идея. Тогда её выносят в custom generic test.
Custom generic tests
Custom generic test — это ваш собственный переиспользуемый тест, который можно навесить на любую колонку в любой модели. Определяется через блок {% test %} (обычно в macros/ или tests/generic/), а внутри — тот же запрос, выбирающий нарушения.
-- macros/test_positive_value.sql
{% test positive_value(model, column_name) %}
SELECT *
FROM {{ model }}
WHERE {{ column_name }} <= 0
{% endtest %}Дальше он применяется так же, как встроенный:
columns:
- name: amount
tests:
- positive_valueСмысл — DRY: один раз описали проверку «значение должно быть положительным» и переиспользуете её во всех моделях, где это нужно. На собесе именно это отличает мидла от джуна: джун напишет десять singular-тестов, мидл вынесет логику в один параметризованный generic-тест.
Severity и пороги
По умолчанию упавший тест — это ошибка (severity: error), которая роняет весь прогон. Но не каждое нарушение критично: небольшое расхождение в данных иногда допустимо и не должно останавливать пайплайн. Для этого есть уровни критичности и пороги.
columns:
- name: email
tests:
- unique:
severity: warn # или error
warn_if: ">100"
error_if: ">1000"error— прогон падает, данные дальше не едут. Ставится на всё, что ломает продукт: битые PK, потерянные FK.warn— dbt пишет предупреждение в лог, но продолжает. Подходит для метрик качества, где небольшой дрейф терпим.warn_if/error_if— пороги по числу вернувшихся строк. В примере выше: до 100 нарушений — тихо, от 100 — предупреждение, от 1000 — падение.
Это удобно, когда небольшое расхождение приемлемо, а системное — уже нет. Например, пара дублей в почтах не повод будить дежурного ночью, а тысяча — повод.
store_failures
Когда тест падает на большом датасете, «упал unique на orders.id» — бесполезное сообщение: непонятно, какие именно строки виноваты. Флаг store_failures сохраняет сами нарушившие строки в отдельную аудиторскую таблицу, чтобы их можно было разобрать позже.
tests:
- unique:
store_failures: trueПровалившие строки складываются в схему dbt_test__audit (имя настраивается), и к ним можно обратиться обычным запросом:
SELECT * FROM analytics.dbt_test__audit.unique_orders_id;Это критично для отладки тестов на больших таблицах: вместо «что-то не так с 12 000 строк» вы сразу видите конкретные дубли и понимаете, откуда они взялись — из источника, из джойна или из неверной дедупликации.
Как это спрашивают на собесе
Формулировки, которые реально звучат на интервью, и что интервьюер хочет услышать:
- «Чем singular-тест отличается от generic?» Generic параметризуется и переиспользуется на разных моделях и колонках; singular — это разовый SQL под конкретную модель. Ответ проверяет, понимаете ли вы, когда выносить логику в макрос.
- «Тест упал в 3 ночи, но данные почти корректны — как сделать, чтобы пайплайн не падал?» Понизить severity до
warnили выставитьerror_ifс адекватным порогом, а не отключать тест совсем. - «Как понять, из-за чего именно упал тест на таблице в 50 млн строк?» Включить
store_failuresи посмотреть аудиторскую таблицу с конкретными нарушениями. - «Где вешать not_null и unique — на staging или на marts?» Обычно и там, и там: на staging ловим проблемы источника раньше, на marts гарантируем контракт для BI.
Интервьюер смотрит не на знание синтаксиса, а на инженерное мышление: понимаете ли вы, что тест — это способ поймать проблему до того, как её увидит бизнес.
Частые ошибки
- Тесты только на marts. Если проверять данные только в самом конце, отладка превращается в кошмар: непонятно, на каком слое сломалось. Тестируйте и staging тоже.
- Всё на
severity: error. Тогда любой мелкий дрейф роняет весь прогон, команда привыкает к красным прогонам и перестаёт на них реагировать. Разделяйте критичное и терпимое. uniqueиnot_nullтолько на PK. Ссылочную целостность (relationships) и допустимые значения (accepted_values) забывают, а именно они ловят разъехавшиеся джойны и мусорные статусы.- Падение без
store_failuresна больших таблицах. Тест краснеет, а что чинить — непонятно. Для тяжёлых моделей включайте сохранение нарушений заранее. - Копипаст singular-тестов вместо custom generic. Десять почти одинаковых SQL-файлов — сигнал, что пора вынести проверку в переиспользуемый макрос.
Связанные темы
- dbt на собесе DE
- dbt incremental models для DE
- Great Expectations для DE
- DQ dimensions для DE
- Подготовка к собесу Data Engineer
FAQ
Чем singular-тест отличается от generic?
Singular — это разовый SQL-запрос в папке tests/ под конкретную модель. Generic (встроенный или свой) параметризуется через model и column_name и переиспользуется на любых колонках. Если одну и ту же проверку хочется навесить на несколько моделей — это generic.
Что происходит при падении теста?
По умолчанию тест с severity: error роняет прогон, и с dbt build зависимые модели дальше не строятся — битые данные не едут в прод. Тест с severity: warn только пишет предупреждение в лог и не останавливает пайплайн.
Как посмотреть, какие именно строки не прошли тест?
Включить store_failures: true. Тогда нарушившие строки сохраняются в аудиторскую таблицу (схема dbt_test__audit по умолчанию), и их можно разобрать обычным SELECT — это спасает при отладке тестов на больших датасетах.
Тесты замедляют прогон, что делать?
Тест — это дополнительный запрос, так что на больших таблицах они стоят времени. Помогает: тестировать критичное на staging (раньше и на меньших объёмах), использовать where-конфиг для проверки только свежей партиции, разумно расставлять severity, чтобы не гонять тяжёлые проверки на каждый чих.
Это официальная информация?
Нет. Статья основана на документации dbt (1.7+) и опыте кандидатов. Конкретный набор тестов и соглашения зависят от команды и проекта.
Тренируйте Data Engineering — откройте тренажёр с 1500+ вопросами для собесов.