dbt_expectations: продвинутые тесты
В уроках 7-8 модуля мы рассмотрели generic-тесты (not_null, unique, accepted_values, relationships). Они покрывают самое базовое — структурные проверки. Но что насчёт более тонких:
- «Значения колонки должны быть в диапазоне 0-100.»
- «Sum revenue не должен резко отличаться от вчерашнего ± 20%.»
- «Длина email — между 5 и 100 символов.»
- «Дата transaction’а — не в будущем.»
Generic тесты этого не покрывают. Можно писать singular tests (один SQL-файл на проверку), но это много кода. dbt_expectations — пакет от Calogica, port популярной Python-библиотеки Great Expectations на dbt. Десятки готовых параметризированных тестов.
В этом уроке — обзор, ключевые тесты, как организовать.
Установка
# packages.yml
packages:
- package: dbt-labs/dbt_utils
version: 1.3.0
- package: calogica/dbt_expectations
version: 0.10.4
dbt deps
После этого тесты dbt_expectations доступны через namespace dbt_expectations.*.
dbt_expectations зависит от dbt_utils. Если ставите dbt_expectations — dbt_utils тоже должен быть в packages.yml с совместимой версией. dbt deps вам это скажет если что-то не сходится.
Категории тестов
dbt_expectations организованы по категориям из Great Expectations:
Всего — 50+ тестов. Обозревать все нет смысла. Покажу 10 самых полезных для junior-проекта.
Топ-10 тестов dbt_expectations для junior
1. expect_column_values_to_be_between
models:
- name: fct_orders
columns:
- name: amount
data_tests:
- dbt_expectations.expect_column_values_to_be_between:
min_value: 0
max_value: 100000
strictly: false # >= 0 AND <= 100000, не >
Тест, что все значения колонки в range. Падает, если есть хоть одна строка вне диапазона.
Дополнительные параметры:
- dbt_expectations.expect_column_values_to_be_between:
min_value: 0
row_condition: "status = 'paid'" # только для paid строк
strictly: false
row_condition — SQL-фильтр, к которым применяется проверка. Очень удобно для conditional rules.
2. expect_column_values_to_match_regex
models:
- name: stg_jaffle__customers
columns:
- name: email
data_tests:
- dbt_expectations.expect_column_values_to_match_regex:
regex: "^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\\.[a-zA-Z0-9-.]+$"
Email-валидация, телефонные номера, ZIP codes — стандартный кейс.
Regex для email — это не валидация настоящего email. RFC 5322 регекс — десятки строк, и даже он не гарантирует, что email доставится. Юзайте как basic sanity check, не как полную валидацию.
3. expect_column_value_lengths_to_be_between
models:
- name: stg_jaffle__customers
columns:
- name: phone_number
data_tests:
- dbt_expectations.expect_column_value_lengths_to_be_between:
min_value: 10
max_value: 15
Длина строки в диапазоне. Удобно для phone numbers, ZIP codes, hash values.
4. expect_column_distinct_count_to_be_between
models:
- name: stg_jaffle__orders
columns:
- name: status
data_tests:
- dbt_expectations.expect_column_distinct_count_to_be_between:
min_value: 3
max_value: 7
Количество уникальных значений в колонке — в диапазоне. Если у вас 6 valid статусов заказа (placed, paid, shipped, delivered, cancelled, refunded), и тест говорит «не должно быть меньше 3 и больше 7» — это catches новые непредвиденные статусы.
5. expect_table_row_count_to_be_between
models:
- name: fct_orders
data_tests:
- dbt_expectations.expect_table_row_count_to_be_between:
min_value: 1000
max_value: 1000000
Проверка, что таблица в разумных размерах. Если внезапно 0 строк — значит pipeline сломался. Если 10М — что-то не так с дедупом.
6. expect_table_row_count_to_equal_other_table
models:
- name: stg_jaffle__orders
data_tests:
- dbt_expectations.expect_table_row_count_to_equal_other_table:
compare_model: source('jaffle', 'orders')
После staging-чистки rows должны совпадать с source. Если меньше — кто-то фильтрует случайно.
7. expect_column_values_to_be_in_type_list
models:
- name: fct_orders
columns:
- name: order_date
data_tests:
- dbt_expectations.expect_column_values_to_be_of_type:
column_type: date
Проверка типа колонки. Если кто-то случайно заменил DATE на VARCHAR — тест падает.
8. expect_row_values_to_have_data_for_every_n_datepart
models:
- name: revenue_daily
data_tests:
- dbt_expectations.expect_row_values_to_have_data_for_every_n_datepart:
date_col: date
date_part: day
interval: 1
Проверка, что нет gaps в датах. Должна быть строка на каждый день (или week, month и т.д.). Идеально для daily/weekly mart-таблиц.
Если за 2026-03-15 нет данных — тест падает с указанием, какие даты пропущены.
9. expect_column_values_to_be_increasing
models:
- name: fct_orders
columns:
- name: order_id
data_tests:
- dbt_expectations.expect_column_values_to_be_increasing
Проверка монотонности. Полезно для sequence ID, timestamps, version numbers.
10. expect_column_pair_values_to_be_in_set
models:
- name: fct_orders
data_tests:
- dbt_expectations.expect_column_pair_values_to_be_in_set:
column_A: status
column_B: is_paid
value_pairs_set:
- ['placed', false]
- ['paid', true]
- ['shipped', true]
- ['delivered', true]
- ['cancelled', false]
- ['refunded', true]
Проверка cross-column rules. Например: «если status=‘placed’, is_paid должен быть false». Это бизнес-правила на уровне строки.
Полный пример: качество staging-модели
# models/staging/jaffle/_jaffle__models.yml
version: 2
models:
- name: stg_jaffle__customers
description: "Чищенные customers"
data_tests:
# Размер таблицы — sanity check
- dbt_expectations.expect_table_row_count_to_be_between:
min_value: 100
max_value: 1000000
# Strictly == size of source (без потерь после чистки)
- dbt_expectations.expect_table_row_count_to_equal_other_table:
compare_model: source('jaffle', 'customers')
columns:
- name: customer_id
description: "Primary key"
data_tests:
- unique
- not_null
# Распределение значений
- dbt_expectations.expect_column_distinct_count_to_equal_other_table:
compare_model: source('jaffle', 'customers')
compare_column: id
- name: email
description: "Email клиента"
data_tests:
- not_null
# Email format
- dbt_expectations.expect_column_values_to_match_regex:
regex: "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$"
# Length sanity
- dbt_expectations.expect_column_value_lengths_to_be_between:
min_value: 5
max_value: 100
- name: created_date
description: "Дата регистрации"
data_tests:
- not_null
# Не в будущем
- dbt_expectations.expect_column_values_to_be_between:
max_value: "current_date"
min_value: "cast('2010-01-01' as date)"
Это 8 тестов на одной модели, покрывающие: размер, ключи, формат email, длина email, диапазон дат.
Запустите dbt test --select stg_jaffle__customers. Каждый тест — отдельная query, результат — pass / fail с details.
severity и store_failures
Тесты могут быть error (default — падает CI) или warn (warning без crash). Используется для soft checks:
- dbt_expectations.expect_table_row_count_to_be_between:
min_value: 1000
max_value: 1000000
config:
severity: warn # не падает CI, только warning
store_failures: true — сохранять failed rows в audit-таблицу для investigation:
- dbt_expectations.expect_column_values_to_be_between:
min_value: 0
max_value: 1000
config:
store_failures: true
После dbt test — в схеме появляется dbt_test__audit.{test_name} с failed rows. Можно поселектить и понять, какие конкретные строки failed.
Когда нужны expectations vs generic tests
| Проверка | Где |
|---|---|
customer_id IS NOT NULL | generic not_null |
customer_id UNIQUE | generic unique |
(order_id, line_id) UNIQUE | dbt_utils.unique_combination_of_columns |
customer_id REFERENCES customers | generic relationships |
status IN ('placed', 'paid', ...) | generic accepted_values |
amount BETWEEN 0 AND 100000 | dbt_expectations.expect_column_values_to_be_between |
email matches regex | dbt_expectations.expect_column_values_to_match_regex |
row count between 1000 and 1M | dbt_expectations.expect_table_row_count_to_be_between |
no gaps in dates | dbt_expectations.expect_row_values_to_have_data_for_every_n_datepart |
business rule on multiple columns | dbt_expectations.expect_column_pair_values_to_be_in_set |
Generic — структура. dbt_utils — composite uniqueness, custom SQL. dbt_expectations — values, ranges, distributions, cross-column.
Performance
Каждый тест — это отдельный SELECT в warehouse. На большой таблице это:
- Затратно: каждый тест запускает full scan.
- Параллелизуется через
threads:вprofiles.yml.
Best practices:
- Не тестируйте всё подряд. Тесты на staging, marts — да. Тесты на 50 intermediate-моделей — overkill.
- Limit scope.
row_condition: "status = 'paid'"ограничивает scan. - store_failures — только для дебага. В обычном CI выключайте.
- В larger projects тесты разделены:
dbt test --select tag:criticalв каждом merge,dbt test(полный) — ежедневно.
Антипаттерны
Попробуй сам
В вашем dbt-проекте:
- Установите dbt_expectations:
packages:
- package: dbt-labs/dbt_utils
version: 1.3.0
- package: calogica/dbt_expectations
version: 0.10.4
dbt deps
- Добавьте 3 теста в
_jaffle__models.yml:
models:
- name: stg_jaffle__orders
columns:
- name: amount
data_tests:
- not_null
- dbt_expectations.expect_column_values_to_be_between:
min_value: 0
max_value: 10000
- name: order_date
data_tests:
- not_null
- dbt_expectations.expect_column_values_to_be_between:
max_value: "current_date"
min_value: "cast('2020-01-01' as date)"
data_tests:
- dbt_expectations.expect_table_row_count_to_be_between:
min_value: 10
max_value: 100000
dbt test --select stg_jaffle__orders
- Specifically test что date не в будущем. Если у вас тестовые данные — добавьте строку с
order_date = '2030-01-01'в seed (если есть). Прогоните тест — он fail.
Бонус: добавьте expect_column_values_to_match_regex для email validation. Попробуйте найти tests for timeseries-data: expect_row_values_to_have_data_for_every_n_datepart для revenue_daily.
Ключевые выводы
- dbt_expectations — пакет от Calogica, port Great Expectations на dbt. Намного больше тестов чем generic.
- Категории: table shape, column values, column aggregates, distribution, string matching, dates.
- Топ тесты для junior:
expect_column_values_to_be_between,expect_column_values_to_match_regex,expect_column_value_lengths_to_be_between,expect_table_row_count_to_be_between,expect_row_values_to_have_data_for_every_n_datepart. - row_condition — фильтр для conditional tests.
- severity: warn — soft check. store_failures: true — audit-таблица с failed rows (для дебага).
- Когда expectations vs generic: generic для структуры (not_null, unique), expectations для values / ranges / distributions / cross-column rules.
- Performance: каждый тест — отдельный SELECT. Не тестируйте всё подряд; таргетьте critical paths.
- Антипаттерны: 100 тестов на модель, magic numbers без объяснения, store_failures всегда.