Модульное тестирование в dbt для разработки, управляемой тестами
Вам когда-нибудь снились кошмары о "плохих данных"? Или я единственный, у кого такие повторяющиеся кошмары? 😱
Вот один из них, который мне приснился прошлой ночью:
Все началось с ночной охоты на баги. Угрожающее насекомое заперло моих коллег в подземелье, и они умоляют меня помочь им выбраться. Найти ключ оказывается сложно, и он всегда кажется чуть-чуть недосягаемым. Стресс ощутим, как физический груз на моей груди, пока я соревнуюсь со временем, чтобы освободить их.
Конечно, я просыпаюсь, так и не спасая их, но все равно чувствую облегчение. И у меня были похожие кошмары, связанные с героической переработкой кода или запуском новой модели или функции.
Хорошие новости: начиная с dbt v1.8, мы вводим полноценный фреймворк модульного тестирования, который может справиться с каждым из сценариев из моих кошмаров о данных.
Прежде чем углубиться в детали, давайте быстро посмотрим, как мы к этому пришли.
История качества данных в dbt
Основная причина моих плохих снов — это беспокойство о некачественных данных, которые влияют на общие результаты.
Одной из вещей, которая мне сразу понравилась, когда я начал использовать dbt, было то, что он имел полноценный механизм для утверждения качества данных на всех наших производственных данных в виде тестов данных.
Мне больше не нужно было беспокоиться о том, уникален ли мой первичный ключ, я мог просто добавить тест данных dbt, чтобы подтвердить это ожидание!
dbt test
быстро стал любимой командой, позволяющей мне ежедневно запускать полный набор тестов качества данных в производстве. И эти же тесты запускались в CI и разработке.
Но хотя этот механизм чрезвычайно полезен на целостном уровне, он не так хорошо подходит для более детального уровня. Он не был разработан для обработки минимальных тестовых случаев для модели с фиксированными входными данными и ожидаемым выходом из этих данных. Также он не был предназначен для обработки изолированных тестовых случаев, которые могут выполняться одновременно для одной и той же модели.
Таким образом, он не соответствует стандартному случаю использования в программной инженерии, который предполагает настройку и запуск отдельных тестовых случаев и других желательных свойств.
Введение модульного тестирования в dbt
Версия dbt 1.8 знаменует собой введение встроенного фреймворка модульного тестирования для расширения возможностей лучших практик программной инженерии для аналитических инженеров. Он позволяет создавать изолированные и повторяемые модульные тесты, которые хорошо подходят для выполнения во время разработки и CI. Они полезны в различных сценариях, таких как реагирование на сообщения об ошибках, уверенное рефакторинг кода и использование разработки, управляемой тестами при добавлении новых функций.
Давайте углубимся в детали...
Привет, мир модульного тестирования
Ключевой способ, которым я строю уверенность в себе, — это начинать с самого простого примера. Как только я добиваюсь, чтобы начальная вещь заработала, я могу настроить ее для более сложных случаев использования (прокрутите вниз до раздела "пример из реального мира" для чего-то более реалистичного!). Итак, вот супер простой пример, который вы можете использовать, чтобы начать. После этого я объясню больше о каждом из основных компонентов и о том, как вы можете применить их к своим собственным тестовым случаям.
Сначала создайте эту тривиальную модель:
-- models/hello_world.sql
select 'world' as hello
Затем добавьте простой модульный тест для этой модели:
# models/_properties.yml
unit_tests:
- name: test_hello_world
# Всегда только одна трансформация для тестирования
model: hello_world
# На этот раз входные данные не нужны!
# Большинство модульных тестов будут иметь входные данные — см. раздел "пример из реального мира" ниже
given: []
# Ожидаемый результат может содержать от нуля до нескольких строк
expect:
rows:
- {hello: world}
Наконец, выполните модель и все ее тесты одной командой, как это:
dbt build --select hello_world
Voilà! Мы видим, что один модульный тест был выполнен и он прошел.
Создание модульных тестов
После того, как вы выполнили свой первый модульный тест "hello, world", вы захотите начать писать свои собственные. Есть две вещи, которые помогут вам добиться успеха:
- Как концептуально думать о модульном тесте
- Как на самом деле создавать свои модульные тесты в YAML
Вот пошаговое руководство, которое вы можете следовать:
Организация ваших мыслей
- Определите свои сценарии: В каких сценариях вы хотите быть более уверенными? Для каждого сценария, какая модель является релевантной? Рассмотрите крайние случаи: какие входные данные могут быть сложными для правильной обработки этой модели? Это определит вашу модель и заданные входные данные.
- Определите критерии успеха: Каков ожидаемый результат для каждого сценария? Будьте конкретны. Это определит ваш ожидаемый результат.
Написание ваших модульных тестов
- Начните с структуры "модель-входные данные-выход": При запуске этой модели, с учетом этих тестовых входных данных, ожидается этот выход.
- Используйте значимые описания: Они должны четко объяснять, что делает тест, чтобы сотрудники и будущие разработчики могли понять его цель.
- Тестируйте одно поведение на тестовый случай: Это позволяет тестам быть сосредоточенными и легче отлаживаемыми.
Дополнительные советы:
- Думайте о поддерживаемости: Пишите тесты, которые легко понять и обновить.
- Рефакторьте тесты по мере необходимости: Держите их в актуальном состоянии с изменениями кода.
- Практикуйте разработку, управляемую тестами (TDD): Пишите тесты перед написанием кода, чтобы направлять процесс разработки.
- Помните, что модульное тестирование — это только часть обеспечения качества. Сочетайте его с другими методами тестирования, такими как тесты данных и контракты моделей, для комплексного подхода.
Далее я покажу вам краткий пример из "реального" мира.
Пример из реального мира
Когда мы пробовали опыт разработчика и эргономику модульного тестирования в dbt, мы обратились к нашему надежному репозиторию Jaffle Shop. Мы начали следовать вышеуказанному фреймворку, чтобы определить сценарии, а затем определить критерии успеха.
Первым сценарием, который мы рассмотрели, было подсчет количества продуктов питания и напитков в заказе. Одним из естественных крайних случаев является заказ без напитков. Наши критерии успеха в этом случае заключаются в том, чтобы count_drink_items
было равно 0 в модели order_items_summary
.
Чтобы реализовать модульный тест, мы начали с структуры "модель-входные данные-выход" (MIO), описанной выше. Релевантной моделью была orders
с заданными входными данными из order_items
и stg_orders
. В этом случае мы ожидаем, что наш выход для order_id 2 будет count_drink_items: 0
.
Вот как выглядел YAML модульного теста:
YAML модульного теста
unit_tests:
- name: test_order_items_count_drink_items_with_zero_drinks
description: >
Сценарий: Заказ без напитков
Когда таблица `order_items_summary` создается
Дано заказ с только 1 продуктом питания
Тогда количество напитков равно 0
# Модель
model: order_items_summary
# Входные данные
given:
- input: ref('order_items')
rows:
- {
order_id: 76,
order_item_id: 3,
is_drink_item: false,
}
- input: ref('stg_orders')
rows:
- { order_id: 76 }
# Выходные данные
expect:
rows:
- {
order_id: 76,
count_drink_items: 0,
}
Достаточно сказать, что когда мы впервые запустили модульный тест, он провалился! 💥
Но это произошло не потому, что мы неправильно определили модульный тест — это произошло потому, что мы обнаружили баг, о котором ранее не знали. Чтобы вернуть все на правильный путь, мы открыли PR, который добавил соответствующий модульный тест для подтверждения бага, а также исправление бага. Хорошая новость заключается в том, что, реализовав модульный тест, мы смогли обнаружить баг до того, как это сделал кто-то другой. 😎
Если вам интересно, как выглядела модель до и какие изменения в коде были внесены для исправления, вот они:
Оригинальный SQL код
with
order_items as (
select * from {{ ref('order_items') }}
)
select
order_id,
sum(supply_cost) as order_cost,
sum(product_price) as order_items_subtotal,
count(order_item_id) as count_order_items,
count(
case
when is_food_item then 1
else 0
end
) as count_food_items,
count(
case
when is_drink_item then 1
else 0
end
) as count_drink_items
from order_items
group by 1
Исправление SQL кода
17c17
< count(
---
> sum(
23c23
< count(
---
> sum(
Предостережения и советы
Смотрите документацию для полезной информации перед началом, включая модульное тестирование инкрементальных моделей, моделей, зависящих от эфемерных моделей, и платформенно-специфические соображения, такие как STRUCT
в BigQuery. Во многих случаях, sql
формат может помочь решить сложные крайние случаи, которые возникают.
Еще одна продвинутая тема — преодоление проблем, когда задействованы недетерминированные факторы, такие как текущая временная метка. Чтобы гарантировать, что выходные данные остаются неизменными независимо от времени выполнения теста, вы можете установить фиксированное, заранее определенное значение, используя конфигурацию overrides
.
Прежде чем мы завершим, давайте кратко сравним различные возможности обеспечения качества данных в dbt и определим ситуации, в которых каждая из них будет наиболее эффективной.
Модульные тесты vs. контракты моделей vs. тесты данных
dbt имеет несколько дополнительных функций, поддерживающих качество данных, включая модульные тесты, контракты моделей и тесты данных. Вот таблица, как они сравниваются и когда вы можете использовать каждую из них:
Модульные тесты | Контракты моделей | Тесты данных |
---|---|---|
Применяются до материализации узла ресурса | Применяются во время материализации узла ресурса | Применяются после материализации узла ресурса |
Блокируют попытку построить ресурс | Блокируют построение узла ресурса и нижестоящих узлов | Блокируют построение нижестоящих узлов |
Жесткие тесты точного ожидаемого результата для одной трансформации | Тестируют "форму" контейнера (названия столбцов и типы данных) для одного набора данных | Гибкие и могут тестировать утверждения по нескольким наборам данных, диапазонам значений и т.д. |
Хороши для тестирования точных значений, ожидаемых в выходных данных | Хороши для обеспечения названий столбцов и типов данных, описывающих "форму" данных, и указания ограничений, таких как первичные и внешние ключи | Хороши для тестирования утверждений, отличных от равенства (например, диапазонов допустимых значений) или исходных данных, чья трансформация является черным ящиком |
Резюме
Теперь вы готовы создать свои первые модульные тесты с этой новой функцией, которая появится в dbt v1.8! Мы с нетерпением ждем, когда вы попробуете это — дайте нам знать, как это работает для вас, оставив комментарий в этом обсуждении или открыв проблему.
Более подробную информацию о синтаксисе вы можете найти в нашей документации. Надеемся, это даст вам инструменты для повышения уверенности в ваших конвейерах данных и позволит спать спокойнее ночью 😴
Comments