Кастомные и singular-тесты

Когда встроенных тестов мало — пишут свои: разовые singular или переиспользуемые generic.

Singular-тест — отдельный SQL-запрос-проверка для конкретного случая. Generic-тест — параметризуемый тест-макрос, который можно навешивать на любые колонки, как встроенные.

Singular: проверка «на один раз»

Иногда нужно проверить специфичное бизнес-правило, которого нет среди встроенных: «сумма заказа не бывает отрицательной», «дата доставки не раньше даты заказа». Для этого пишут singular-тест — просто .sql-файл в папке tests/, возвращающий строки-нарушители.

-- tests/assert_amount_non_negative.sql
select order_id, amount
from {{ ref('fct_orders') }}
where amount < 0

Правило то же, что у встроенных: если запрос вернул строки — тест провален. Здесь нашлись бы заказы с отрицательной суммой.

Проверим логику singular-теста

CREATE TABLE fct_orders (order_id INTEGER, amount NUMERIC);
INSERT INTO fct_orders VALUES (1, 100), (2, -5), (3, 250);

-- тело singular-теста: ищем нарушителей
select order_id, amount
from fct_orders
where amount < 0;

Вывод:

order_id = 2, amount = -5  — нарушитель, тест провален

Generic: свой переиспользуемый тест

Если правило нужно на многих колонках (например «значение положительное»), его делают generic — макросом, который принимает модель и колонку:

-- tests/generic/positive_value.sql
{% test positive_value(model, column_name) %}
select {{ column_name }}
from {{ model }}
where {{ column_name }} <= 0
{% endtest %}

Теперь его навешивают в YAML, как встроенный:

models:
  - name: fct_orders
    columns:
      - name: amount
        tests:
          - positive_value

Singular против generic

SingularGeneric
Что этоSQL-файл в tests/Тест-макрос с параметрами
ПереиспользованиеНет, под один случайДа, на любые колонки
Когда братьУникальная разовая проверкаПовторяющееся правило

Как работает под капотом

Оба вида сводятся к одному принципу: запрос, возвращающий нарушителей. Singular — это готовый запрос; generic — шаблон запроса, в который dbt подставит конкретную модель и колонку при компиляции. dbt test прогоняет и встроенные, и кастомные тесты одинаково и считает тест пройденным при нуле строк. Так качество данных становится таким же кодом, как и сами модели, — лежит в git, проходит ревью.

Частые ошибки

  • Писать singular там, где правило повторяется. Если проверка нужна на многих колонках — делайте generic.
  • Возвращать из теста «хорошие» строки. Тест должен возвращать именно нарушителей; ноль строк = успех.
  • Не покрывать бизнес-правила. Встроенные тесты не знают вашу логику — специфику закрывают кастомные тесты.

Итоги

  • Singular-тест — разовый SQL-запрос в tests/, возвращающий нарушителей.
  • Generic-тест — параметризуемый макрос, навешиваемый на колонки как встроенный.
  • Оба следуют правилу «ноль строк = пройден» и хранятся как код в git.
Проверьте себя
1. Что должен возвращать SQL-запрос singular-теста?
AВсе строки таблицы
BТолько строки-нарушители правила (ноль строк = тест пройден)
CКоличество строк
DКорректные строки
2. Когда вместо singular-теста стоит написать generic-тест?
AНикогда
BКогда одно и то же правило нужно применять к разным колонкам/моделям
CКогда тест должен выполняться быстрее
DКогда нет доступа к YAML