Learning Platform
Глоссарий Troubleshooting
Урок 09.02 · 16 мин
Начальный
test strategystaging testsmarts testskeys vs measures

В прошлом уроке мы изучили четыре встроенных теста. Теперь — стратегия их применения: где какие тесты ставить, чтобы не утонуть в ложных срабатываниях и не пропустить реальные проблемы.

Главный принцип: тесты должны отражать смысловые гарантии модели. Не “повешу все тесты на все колонки”, а “что должно быть истинным для этой колонки в этой модели, и без чего downstream развалится”.

Стратегия по слоям

В стандартной dbt-структуре staging -> intermediate -> marts тесты распределяются по разному.

Тесты по слоям проекта

Staging — фундаментальные проверки на keys и NOT NULL критичных полей. Marts — детальные тесты на бизнес-логику (status enums, foreign keys). Intermediate обычно — без отдельных тестов, доверяем upstream и downstream.

Stagingraw -> clean
not_null на PK + unique на PKфундамент
Intermediateпромежуточная логика
часто без тестовтрастуем upstream
Martsfinal business model
all 4 types + business invariantsмаксимальное покрытие

Staging-слой: фундаментальные тесты

Staging — это первая модель над raw. Минимальные тесты:

version: 2

models:
  - name: stg_customers
    columns:
      - name: customer_id
        tests:
          - not_null
          - unique
      - name: email
        tests:
          - not_null   # если бизнес гарантирует email

Логика:

  • PK всегда: not_null + unique. Если customer_id NULL или дубль — у тебя проблема, которую обязательно нужно отловить максимально рано.
  • Критичные NOT NULL. Поля, без которых downstream не работает (email для рассылки, country для биллинга).
  • Без accepted_values и relationships. На уровне staging часто ещё нет данных для FK-проверок (другие staging могут идти параллельно). accepted_values имеет смысл, только если в raw гарантированы значения (редко на staging).

Зачем тестировать staging: ловить плохие данные до того, как они попадут в дорогие marts-вычисления. Сломанный loader или ошибка в источнике обнаружится сразу.

Marts-слой: максимальное покрытие

Marts — финальные модели для дашбордов. Тестов больше, проверяем business logic:

version: 2

models:
  - name: dim_customers
    columns:
      - name: customer_id
        tests:
          - not_null
          - unique
      - name: country
        tests:
          - accepted_values:
              values: ['RU', 'US', 'DE', 'FR', 'GB', 'JP']
      - name: status
        tests:
          - accepted_values:
              values: ['active', 'inactive', 'banned']
          - not_null

  - name: fact_orders
    columns:
      - name: order_id
        tests:
          - not_null
          - unique
      - name: customer_id
        tests:
          - not_null
          - relationships:
              to: ref('dim_customers')
              field: customer_id
      - name: status
        tests:
          - accepted_values:
              values: ['pending', 'shipped', 'delivered', 'cancelled']
      - name: amount
        tests:
          - not_null

Логика:

  • Все PK: not_null + unique. То же, что и в staging.
  • Foreign keys: not_null + relationships. Гарантируем целостность ссылок.
  • Enum-like поля: accepted_values. Status, country, type — везде, где set значений ограничен.
  • Measures (amount, count): not_null. Если измерение должно быть числом, NULL ломает агрегации.

Keys vs Measures

Это другая дихотомия — по природе колонки.

Keys — идентификаторы строк или связи между таблицами:

  • Primary key — уникален в модели. Тесты: not_null + unique.
  • Surrogate key — генерированный (хэш, uuid). Тесты: not_null + unique.
  • Foreign key — ссылается на родительскую модель. Тесты: not_null + relationships.

Measures — числовые или enum-значения, которые агрегируются или фильтруются:

  • Numeric (amount, count, duration) — обычно not_null. Иногда accepted_range (через dbt_utils).
  • Status (enum) — accepted_values.
  • Boolean — not_null + accepted_values: [true, false].
  • Date/time — not_null. Иногда accepted_range для разумных границ.

Dimensions — характеристики, не агрегируются:

  • Categorical (country, type) — accepted_values, если список ограничен.
  • Free-form (name, description) — обычно без accepted_values.
Тесты по типу колонки

Keys — обязательная пара not_null + unique (или not_null + relationships для FK). Measures — accepted_values для enum, not_null для numbers. Dimensions — accepted_values по обстоятельствам.

PK / surrogate keynot_null + unique
FKnot_null + relationships
Status / enumnot_null + accepted_values
Numeric measurenot_null
Country / categoryaccepted_values если ограничен
Free-form textобычно без тестов

Антипаттерны: какие тесты НЕ ставить

1. unique на foreign key. Customer_id в orders — у одного клиента много заказов, customer_id повторяется. unique будет провалится каждый раз.

2. not_null на optional поле. deleted_at — есть только у удалённых записей, у активных NULL. not_null будет ругаться на все активные строки.

3. accepted_values на free-form text. Имя клиента, описание товара, address — список не ограничен. accepted_values тут невозможен.

4. relationships между несвязанными таблицами. Если customer_id_alt — это альтернативный ID, который иногда есть, иногда нет — относится к другой таблице, проверка через relationships даст много ложных срабатываний.

5. unique на дублирующихся denormalized моделях. Wide-tables, где одна сущность представлена в нескольких строках (для разных дат снимков), не уникальны по сущности — только по композиции.

Когда тесты на intermediate

Intermediate-слой обычно тестируется минимально:

  • Если intermediate — ephemeral, тесты не работают вообще (см. модуль 6).
  • Если view/table — можно повесить 1-2 теста на ключевые трансформации, но обычно достаточно тестов на staging (вход) и marts (выход).

Исключение: если intermediate содержит критичную business-логику (например, дедупликация late events), тогда тесты имеют смысл — гарантия, что логика работает.

Тесты на staging для sources

Тесты можно вешать прямо на sources, не дожидаясь staging:

sources:
  - name: jaffle_shop
    tables:
      - name: raw_customers
        columns:
          - name: id
            tests:
              - not_null
              - unique

dbt test --select source:jaffle_shop запустит. Это полезно, когда хочешь раннее обнаружение проблем в источнике, до построения staging.

Минусы:

  • Тесты на raw часто шумные — loader может временно дать неконсистентные данные.
  • Логически правильнее тестировать после “очистки” в staging.

Практика: 1-2 критичных теста на source (например, not_null на id), остальное — на staging.

Severity и WARN

Чуть подробнее на это в следующем уроке, но для контекста: каждый тест может быть error (default — блокирует pipeline) или warn (предупреждение, не блокирует).

- name: status
  tests:
    - accepted_values:
        values: ['active', 'inactive']
        config:
          severity: warn

Используется, когда тест не критичен — например, новое значение status может быть OK, но хочется получить уведомление.

Полный пример: staging + mart

Реалистичный YAML для проекта Jaffle Shop:

version: 2

models:
  - name: stg_customers
    columns:
      - name: customer_id
        description: "Unique customer ID from source"
        tests:
          - not_null
          - unique
      - name: email
        description: "Email address"
        tests:
          - not_null

  - name: stg_orders
    columns:
      - name: order_id
        tests:
          - not_null
          - unique
      - name: customer_id
        tests:
          - not_null
          # без relationships здесь — stg_customers может быть не построен

  - name: dim_customers
    columns:
      - name: customer_id
        tests:
          - not_null
          - unique

  - name: fact_orders
    columns:
      - name: order_id
        tests:
          - not_null
          - unique
      - name: customer_id
        tests:
          - not_null
          - relationships:
              to: ref('dim_customers')
              field: customer_id
      - name: status
        tests:
          - not_null
          - accepted_values:
              values: ['pending', 'shipped', 'delivered', 'cancelled']
      - name: amount
        tests:
          - not_null

Структура понятна: staging — минимальный фундамент, marts — полное покрытие.

TIP

Эмпирическое правило: каждая модель должна иметь как минимум один тест на PK (not_null + unique). Это базовый sanity check. Дальше — по обстоятельствам: критичные NOT NULL, foreign keys через relationships, status enums через accepted_values.

Команды для отладки

# Тесты только для staging
dbt test --select staging

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

# Тесты по тэгу (если расставил tags на тесты)
dbt test --select tag:critical

Если тест провалился — открой target/compiled/<project>/...<test_name>.sql, выполни в DuckDB, посмотри что вернулось — это нарушители.

Попробуй сам

Создай _models.yml для своего проекта с приоритетом покрытия:

  1. PK на каждой модели — not_null + unique.
  2. FK на каждой fact-модели — not_null + relationships.
  3. Status на каждой fact — accepted_values.
  4. Critical NOT NULL — email, amount, дата.

Запусти dbt test. Посмотри время выполнения. Если упадёт — открой compiled-файл теста, разберись, что нарушает.

Что мы поняли

Стратегия применения тестов: staging — фундамент (not_null + unique на PK), marts — максимальное покрытие (все 4 типа). По типу колонки: keys — not_null + unique (PK) или not_null + relationships (FK); measures — accepted_values для enum, not_null для numbers; free-form — обычно без тестов. Главное — тесты должны отражать смысловые гарантии, не be everywhere. Антипаттерны: unique на FK, not_null на optional, accepted_values на free-form.

В следующем уроке разберём severity — как блокировать или предупреждать через тесты.

Шесть измерений data quality
Проверка знанийKnowledge check
У тебя dim_customers с колонкой email_alt (опциональный второй email, NULL у большинства клиентов). Кто-то повесил not_null на эту колонку, dbt test постоянно FAIL. Что делать?
ОтветAnswer
Удалить not_null. Если поле опциональное (NULL — валидное значение), тест not_null будет всегда падать. Это типичный антипаттерн. Подумай, какая семантика у колонки: если NULL означает "у клиента нет второго email" — это валидно, тест убрать. Если NULL означает "loader потерял данные" — тогда тест полезен, но нужно понимать, что он будет ловить именно эту ошибку, и быть готовым его поддерживать.

Проверьте понимание

Результат: 0 из 0
Прикладной
Вопрос 1 из 6. В fact_orders есть колонка customer_id (FK). Какие тесты повесить?

Закончили урок?

Отметьте его как пройденный, чтобы отслеживать свой прогресс

Войдите чтобы оценить урок

Прогресс модуля
0 из 4