Перейти к содержанию

Тесты и документация

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


Зачем тестировать данные

Без тестов типичные проблемы обнаруживаются поздно:

Проблема Как проявляется
Дубликаты ключей Отчёт показывает завышенную выручку
NULL в обязательных полях Дашборд ломается или показывает пустоты
Неожиданные значения Статус 'test' попадает в продуктовые метрики
Битые связи (FK) JOIN теряет строки, аналитик не замечает

dbt-тесты — это SQL-запросы, которые должны вернуть 0 строк. Если тест возвращает строки — значит, найдены проблемные записи.


Встроенные тесты (generic tests)

Четыре теста «из коробки», которые покрывают большинство проверок:

unique

Проверяет, что в колонке нет дубликатов:

YAML
models:
  - name: stg_orders
    columns:
      - name: order_id
        tests:
          - unique

Под капотом dbt генерирует:

SQL
SELECT order_id
FROM stg_orders
GROUP BY order_id
HAVING COUNT(*) > 1

not_null

Проверяет, что в колонке нет NULL:

YAML
columns:
  - name: order_id
    tests:
      - not_null

accepted_values

Проверяет, что все значения — из заданного списка:

YAML
columns:
  - name: status
    tests:
      - accepted_values:
          values: ['paid', 'pending', 'cancelled', 'refunded']

relationships

Проверяет целостность внешнего ключа — все значения существуют в другой таблице:

YAML
columns:
  - name: user_id
    tests:
      - relationships:
          to: ref('stg_users')
          field: user_id

Под капотом:

SQL
SELECT user_id
FROM stg_orders
WHERE user_id NOT IN (
  SELECT user_id FROM stg_users
)

Комбинирование тестов

На одну колонку можно навесить несколько тестов:

YAML
# models/staging/schema.yml
models:
  - name: stg_orders
    columns:
      - name: order_id
        tests:
          - unique
          - not_null
      - name: user_id
        tests:
          - not_null
          - relationships:
              to: ref('stg_users')
              field: user_id
      - name: status
        tests:
          - not_null
          - accepted_values:
              values: ['paid', 'pending', 'cancelled', 'refunded']

Кастомные тесты (singular tests)

Когда встроенных тестов не хватает — пиши SQL-запрос в папку tests/. Тест считается пройденным, если запрос вернул 0 строк.

SQL
-- tests/assert_positive_revenue.sql
-- Выручка не может быть отрицательной
SELECT
  order_id,
  revenue
FROM {{ ref('fct_revenue') }}
WHERE revenue < 0
SQL
-- tests/assert_orders_have_items.sql
-- У каждого заказа должна быть хотя бы одна позиция
SELECT
  o.order_id
FROM {{ ref('stg_orders') }} o
LEFT JOIN {{ ref('stg_order_items') }} oi
  ON o.order_id = oi.order_id
WHERE oi.order_id IS NULL

Именование кастомных тестов

Называй тесты с префиксом assert_ — сразу понятно, что проверяется: assert_positive_revenue, assert_no_orphan_payments.


Конфигурация тестов

severity — жёсткость теста

По умолчанию провал теста = ошибка (ERROR). Можно снизить до предупреждения:

YAML
columns:
  - name: email
    tests:
      - not_null:
          severity: warn    # предупреждение, а не ошибка

where — фильтрация

Запускать тест только для части данных:

YAML
columns:
  - name: order_id
    tests:
      - unique:
          where: "status != 'draft'"

error_if и warn_if — пороги

Допустить определённое количество нарушений:

YAML
columns:
  - name: email
    tests:
      - not_null:
          error_if: ">100"    # ошибка, если больше 100 NULL
          warn_if: ">10"      # предупреждение, если больше 10

schema.yml — описание и документация

Файл schema.yml выполняет две задачи: конфигурирует тесты и описывает модели для документации.

YAML
# models/marts/schema.yml
version: 2

models:
  - name: fct_revenue
    description: "Таблица выручки: одна строка = один оплаченный заказ"
    columns:
      - name: order_id
        description: "Уникальный идентификатор заказа"
        tests:
          - unique
          - not_null
      - name: user_id
        description: "ID покупателя"
        tests:
          - not_null
          - relationships:
              to: ref('dim_users')
              field: user_id
      - name: revenue
        description: "Сумма заказа в рублях"
        tests:
          - not_null

  - name: dim_users
    description: "Справочник пользователей"
    columns:
      - name: user_id
        description: "Уникальный идентификатор пользователя"
        tests:
          - unique
          - not_null

Описание источников (sources)

Источники описываются в отдельном YAML:

YAML
# models/staging/sources.yml
version: 2

sources:
  - name: raw
    description: "Сырые данные из продуктовой БД"
    schema: public
    tables:
      - name: orders
        description: "Заказы пользователей"
        columns:
          - name: id
            description: "PK заказа"
          - name: status
            description: "Статус: paid, pending, cancelled"
      - name: users
        description: "Пользователи системы"

Проверка свежести источников

dbt может проверять, что данные в источнике не устарели:

YAML
sources:
  - name: raw
    freshness:
      warn_after:
        count: 12
        period: hour
      error_after:
        count: 24
        period: hour
    loaded_at_field: _etl_loaded_at
    tables:
      - name: orders
      - name: users

Команда для проверки:

Bash
dbt source freshness

Если данные в таблице orders не обновлялись больше 12 часов — предупреждение, больше 24 — ошибка.


Генерация документации

dbt собирает описания из schema.yml, строит граф зависимостей и генерирует интерактивный сайт:

Bash
# Сгенерировать документацию
dbt docs generate

# Запустить локальный сервер
dbt docs serve --port 8080

На сайте документации:

  • Каталог моделей — описание каждой модели и её колонок
  • Граф зависимостей (Lineage Graph) — визуализация связей между моделями
  • SQL-код — скомпилированный и исходный SQL каждой модели

Документация рядом с кодом

Описания хранятся в schema.yml рядом с моделями. Когда меняется SQL — обновляешь описание в том же PR. Документация не устаревает.


Команды для тестирования

Bash
# Запустить все тесты
dbt test

# Тесты для одной модели
dbt test --select stg_orders

# Тесты для модели и всех зависимостей
dbt test --select +fct_revenue

# Сборка + тесты (рекомендуемый способ)
dbt build

# Только тесты определённого типа
dbt test --select test_type:singular    # только кастомные
dbt test --select test_type:generic     # только встроенные

dbt build — запускает модели и тесты в правильном порядке: сначала модель, потом её тесты, потом зависимые модели.


dbt в CI/CD

Типичный пайплайн в CI:

YAML
# .github/workflows/dbt.yml (пример для GitHub Actions)
name: dbt CI
on:
  pull_request:
    paths:
      - 'models/**'
      - 'tests/**'
      - 'macros/**'

jobs:
  dbt-test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Install dbt
        run: pip install dbt-postgres

      - name: Run dbt build
        run: dbt build --target ci
        env:
          DBT_PROFILES_DIR: ./ci

Что проверяет CI:

  1. dbt compile — SQL корректен, нет синтаксических ошибок
  2. dbt run — модели успешно создаются в тестовой БД
  3. dbt test — все тесты данных проходят

CI-среда — отдельная БД

Для CI создай отдельный target в profiles.yml с тестовой базой. Никогда не запускай CI-тесты на продуктовой БД.


Что запомнить

  • Четыре встроенных теста: unique, not_null, accepted_values, relationships
  • Кастомные тесты — SQL в tests/, возвращают проблемные строки (0 строк = тест пройден)
  • schema.yml = тесты + документация в одном файле
  • dbt build = модели + тесты в правильном порядке
  • severity: warn — тест предупреждает, но не останавливает сборку
  • Source freshness — проверяет, что данные не устарели
  • В CI: dbt build --target ci на отдельной базе

Проверь себя


Источники