Решите задачу тестирования конвейеров данных

В последние годы мы наблюдаем огромный рост технологий и инструментов для создания стека данных. Теперь проще, чем когда-либо, построить современный конвейер данных без огромных инвестиций. Стандартный современный стек данных включает, но не ограничивается:

  • Облачное хранилище данных — масштабируемое и распределенное облачное хранилище данных, которое позволяет преобразовывать терабайты данных с помощью SQL. BigQuery, Snowflake и Amazon Redshift в настоящее время лидируют на рынке.
  • Оркестрация рабочих процессов — платформа для программного планирования и мониторинга преобразования данных. Хранилище данных — это всего лишь центральный репозиторий интегрированных данных из разрозненных источников. Нам нужна такая платформа, как Airflow и dbt, для преобразования необработанных данных в чистые данные, которые имеют смысл для бизнеса. В отличие от хранилища данных, многие инструменты оркестровки имеют открытый исходный код, а инфраструктура поддерживается группой данных, поэтому инженеры предложили множество передовых методов, таких как создание конвейера CICD, выполнение тестирования, мониторинга, оповещения и т. д. Эта статья является одним из них, который фокусируется на тестировании в dbt.
  • Бизнес-аналитика — инструмент, позволяющий каждому исследовать данные и самостоятельно обслуживать свои запросы данных, создавая информационные панели. Примеры инструментов: Tableau и Looker. Сотрудники должны иметь возможность использовать инструмент для принятия решений.

Как следует из названия, в этой статье мы ответим на один вопрос: как выполнить модульное тестирование в dbt? Прежде чем увидеть решение, давайте немного поговорим о dbt, его стратегии тестирования и о том, почему это проблема для dbt?

ДБТ

dbt — это инструмент преобразования данных на основе SQL, который позволяет аналитикам данных и инженерам преобразовывать, тестировать и документировать данные в облачном хранилище данных. Инженеры создают повторно используемые модели данных с опытом работы в предметной области. Любой сотрудник компании может использовать модели данных для принятия решений.

Проверка данных в dbt

Чтобы обеспечить корректность моделей данных, нам необходимо применять различные типы тестирования. Обязательной проверкой в ​​dbt является проверка данных. Это интегрированный шаг в конвейере для обеспечения качества данных. Инженеры часто делают предположения о конвейере: исходные данные достоверны, модель надежна, а преобразованные данные точны. Но обычно это не так. Должна быть проведена серия тестов для обеспечения качества данных. В связи с тем, что исходные данные меняются каждый день, эти тесты должны быть частью тестов среды выполнения в производственном конвейере. В зависимости от серьезности некоторые тесты могут блокировать конвейер, чтобы не загрязнять нисходящий поток. Здесь я перечисляю несколько общих тестов качества данных:

  • Нулевые значения — проверьте столбцы, в которых не должно быть пустых значений.
  • Проверка уникальности — комбинация нескольких столбцов должна быть уникальной, чтобы избежать дублирования.
  • Проверка свежести — содержит ли модель самые свежие записи?
  • Проверка связи — все ли необходимые данные в исходной таблице преобразуются в целевую таблицу?

Несколько пакетов dbt, таких как dbt-utils, предоставляют набор готовых универсальных тестов, которые могут удовлетворить большинство потребностей. Чтобы лучше понять роль проверки данных, давайте визуализируем ее:

Горизонтальный путь — это поток данных через конвейер данных. Данные в каждой среде могут быть разными, поэтому тесты качества данных должны выполняться в производственном потоке, чтобы гарантировать, что предположения об исходных данных всегда верны.

Это достаточно?

Модульный тест в dbt

Проверка данных может выявить проблемы с качеством данных. Но это не может гарантировать правильность кода SQL. Вот пример SQL, который объединяет revenueс vat таблицей для расчета чистой суммы бронирования и суммы НДС. Трудно понять, правильна ли формула или логика соединения, только из проверки данных.

SELECT
  date
  , city
  , SUM(amount_net_booking) as amount_net_booking
  , SUM(amount_net_booking * (1 - 1/(1 + vat_rate))) as amount_vat  
FROM revenue
LEFT JOIN vat USING (city)
GROUP BY 1,2

Чего нам здесь не хватает, так это функциональности для тестирования логики SQL (т. н. модульного тестирования), точно так же, как тестирование обычного приложения. Если я создаю приложение, я буду применять TDD (Test Driven Design), распространенную практику разработки программного обеспечения, которая поможет нам достичь хорошего уровня уверенности в правильности. Это гарантирует, что логика верна, прежде чем она будет запущена в производство.

Хотя dbt — мощная платформа, мы по-прежнему пишем кучу SQL-кода с нуля для построения моделей. Пока это написано людьми, оно может пойти не так. Это ничем не отличается от написания программы на Python или Java. Мы можем думать о модели dbt как о функции, где входными данными являются исходные таблицы, а выходными данными является результат ее SQL. Модульное тестирование в dbt — это тест, в котором мы предоставляем фиктивные входные данные, а затем сравниваем результаты с ожидаемой моделью. Большим преимуществом модульного тестирования является то, что мы можем предоставлять фиктивные входные данные для всех типов тестовых случаев. включая крайние случаи. Это особенно полезно для кода SQL, включающего сложную бизнес-логику. Если это цепочка моделей, мы можем предоставить фиктивные входные данные для первой модели и утвердить результаты для окончательной модели и промежуточных моделей по пути. Эта цепочка тестирования называется интеграционным тестом.

В отличие от теста данных, нет необходимости интегрировать модульный тест в конвейер данных, потому что код не будет изменен после перехода в среду более высокого уровня. Нам не нужно делать никаких предположений о логике, потому что мы полностью ее контролируем. Пока логика проверяется в нижней среде, мы должны доверять ей и выпускать код.

Проверка данных в конвейере данных по сравнению с Модульный тест в приложении

Хотя тестирование данных в конвейере данных и модульное тестирование в обычных приложениях — это две разные концепции, у них есть общие области. Например, при построении интерфейса REST API приложение получает данные извне, которые могут быть ненадежными. Он должен проверять данные (т. е. выполнять проверку качества данных) перед их обработкой. Хотя большинство инженеров-программистов не считают это стандартным тестом, этот тип проверки во время выполнения очень похож на тест данных в конвейере данных.

В другом случае тест может рассматриваться как тест данных и модульный тест, например, проверка взаимосвязи (например, сумма полей a и b равна c ). Инженеру важно понимать цель вашего теста, независимо от того, тестируются ли динамические данные или статическая логика. Если речь идет о логике, то это должен быть модульный тест и выполняться только один раз в конвейере CICD. Если речь идет о данных, то они должны быть частью производственного конвейера из-за различий в данных. Несколько примеров проверки данных:

  • Есть ли значение NULL в исходных данных?
  • Содержат ли данные последнюю дату?
  • Каждая запись из источника преобразуется в место назначения?
  • Есть ли какое-либо значение за пределами допустимого диапазона?
  • Есть ли дублирование в коде?

Несколько примеров юнит-тестов:

  • Модель рассчитывает выручку компании, правильный ли расчет?
  • Модель содержит несколько условий IF для расчета поля, не пропустил ли я здесь какой-либо пограничный случай?
  • Модель выполняет агрегацию по нескольким столбцам, я пропустил какой-либо столбец?

Зрелая группа данных использует как автоматизированные проверки качества данных, так и автоматические логические тесты, гарантируя, что данные имеют максимально возможную ценность для компании.

Реализация модульного теста в dbt

Чтобы продемонстрировать, как модульное тестирование работает в dbt, я создал следующий конвейер данных, который считывает 2 исходные таблицы transaction и vat, а затем преобразует данные в окончательную модель revenue. Как инженер данных, я не обязательно знаю каждую деталь логики на начальном этапе, поэтому я создал эту модель, основываясь на каком-то предположении.

Поскольку это критическая и сложная модель, я хочу протестировать ее с парой предопределенных сценариев и создать для нее фиктивные данные. Макетные данные — это CSV-файлы в моем проекте dbt, где каждый CSV-файл представляет одну исходную таблицу. Мы можем хранить файлы CSV в папке data и использовать команду dbt seed для загрузки данных в хранилище данных.

Несколько слов о том, как/кому создавать mock data. Чтобы обеспечить полноту тестовых случаев, люди, которые пишут код, не должны быть теми, кто также предоставляет тестовые данные. Во многих компаниях аналитики или заинтересованные лица предоставляют тестовые примеры или тестовые данные.

Как правило, фиктивные данные никогда не следует смешивать с реальной исходной таблицей. Предпочтительно хранить его в отдельной схеме (например, unit_testing). Мы можем определить это в файле dbt_project.yml. Каждый файл в папке unit_testing/revenue будет загружен в схему unit_testing. tags также играет решающую роль при выборе моделей. При запуске dbt build -s +tag:unit_testing будут запущены все семена/модели/тесты/моментальные снимки с тегом unit_testing и их восходящие потоки.

seeds:
  unit_testing:
    revenue:
      schema: unit_testing
      +tags:
        - unit_testing

Для демонстрационных целей моя модель dbt относительно проще, чем реальность.

Вы видите здесь проблему?

Проблема в исходной таблице. Как всегда, я использую {{ source() }} для ссылки на реальную исходную таблицу. Но мои фиктивные данные находятся в отдельной схеме, и на них следует ссылаться с помощью функции ref, потому что они являются семенами. Как я собираюсь переключить источник? Чтобы решить эту проблему, я создаю специальный маркер, способный переключать исходную таблицу в зависимости от среды или типа триггера.

Этот марк имеет 2 входных параметра: source_table -> таблица содержит реальные данные и test_table -> таблица содержит фиктивные данные. Марко возвращает таблицу на основе переменной в команде dbt. Если команда не предоставляет переменную unit_testing или значение равно false, то она возвращает source_table, иначе возвращает test_table. Вы можете изменить этот маркер в зависимости от вашей ситуации, например, переключить таблицу на основе target вместо переменной.

dbt команда для запуска модели и тестирования с использованием фиктивных данных

dbt build -s +tag:unit_testing --vars 'unit_testing: true'

dbt команда для запуска модели с использованием реальных данных. (позже объясню, зачем нам здесь нужен флаг --exclude)

dbt build -s +tag:revenue --exclude tag:unit_testing

Обновленная модель теперь выглядит так. Мы используем переменную {{ import_* }} для представления правильной исходной таблицы.

Итак, мы увидели, как выбрать правильную исходную таблицу и запустить модель поверх фиктивных входных данных. Далее мы собираемся сравнить фактический вывод с ожидаемым фиктивным выводом. Мы можем использовать тест equality, предлагаемый dbt-utils, для сравнения двух моделей. В compare_model мы ссылаемся на ожидаемую модель вывода.

Стоит отметить, что мы не должны забывать tags: ['unit_testing'] в тесте. Как мы уже говорили ранее, модульное тестирование не должно выполняться как часть производственного конвейера. Запуск этого equality теста в производственной среде на самом деле означает, что мы сравниваем производственную модель с фиктивным выводом, который никогда не будет работать. Добавление тега unit_testing и команды запуска dbt build -s +tag:revenue --exclude tag:unit_testing гарантирует, что тест будет пропущен в рабочей среде.

Вау, надеюсь, вы все еще следите за мной :) Давайте проверим разные способы сравнения моделей. Тест dbt_utils.equality выполняет точное сопоставление моделей, но при сравнении числовых значений это усложняется из-за проблем с точностью. Способ обойти это — создать пользовательскую маркировку, которая сначала округляет числовые столбцы, а затем выполняет сравнение.

Вы должны подойти к этой части творчески, если это поможет вам увидеть разницу.

Мы в полной безопасности?

К сожалению, нет. Модульное тестирование дает нам возможность тестировать логику с помощью пользовательских сценариев. Но проблема в том, все ли охватывают эти сценарии? Возможно, нет. Есть 2 проблемы:

Трудно предоставить идеальный набор данных, охватывающий все перестановки — неизвестные известные

Исходные данные часто разнообразны, невероятно много времени уходит на то, чтобы разобраться во всех возможных комбинациях и создать тестовые данные для каждой из них. Одна из стратегий заключается в том, чтобы заранее создать отдельный слой, который отфильтровывает недопустимые данные и выполняет несколько проверок работоспособности. Это как бы ограничивает количество перестановок, поэтому мы более уверены в том, что произойдет, и можем создать относительно ограниченный набор данных.

Проблема Oracle — (не)известные неизвестные

На мой взгляд, это философская проблема. Каждый тест представляет собой сравнение фактического результата и ожидаемого результата.

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

Как упоминалось ранее, источник данных сложен. Есть много сценариев, когда мы не знаем, как с этим справиться. Простая стратегия заключается в использовании одной логики для обработки всех неизвестных случаев и принятии ее в качестве ответа. Подумайте о роли else в условии if.

Если этого все еще недостаточно, то нам остается только жить с этим. Люди приобретают новые знания каждый день, как и программа. По мере того, как мы узнаем больше проблем, больше неизвестного будет преобразовано в известное, поэтому мы сможем захватывать все больше и больше и понимать мир немного лучше с каждым днем.

Заключение

Я надеюсь, что вы найдете эту статью полезной. Если вы убеждены и начинаете применять модульное тестирование в своем конвейере данных, это прекрасно! Если вы не сразу убедились, но все же хотите извлечь уроки из этой статьи, то это будет означать понимание роли модульного тестирования и тестирования данных в конвейере данных, потому что оно вам когда-нибудь понадобится. Если у вас есть какие-либо мысли о тестировании в конвейере данных, сообщите нам об этом в комментариях. Ваше здоровье!

Ссылка