Open6

【dbt】dbt_unittest

YuichiYuichi
packages.yml
packages:
  - package: yu-iskw/dbt_unittest
    version: 0.4.0
dbt deps
YuichiYuichi

https://techblog.cartaholdings.co.jp/entry/snowflake-dbt-data-platform-vision

dbt-unittestという便利なライブラリがあるので、こちらを使ってマクロをユニットテストできるようにしています。全てのマクロにテスト追加させるようなルールはなく、適宜追加するスタイルでやっています。仮にテストしないと不安になるような場合、マクロが複雑すぎる可能性があるのでシンプルな方向に落とすように促しています。

https://docs.getdbt.com/blog/unit-testing-dbt-packages

YuichiYuichi

Jest で __tests__ フォルダにテスト用ファイルを入れるのを真似て、 __tests__ フォルダを使う

dbt_project
├─ macros
│  └─ __tests__
│    └─ test_macros.sql
└─ generate_name
   ├─ __tests__
   │  ├─ test__generate_alias_name.sql
   │  └─ test__generate_schema_name.sql
   ├─ generate_alias_name.sql
   └─ generate_schema_name.sql

実行したいテストを下記にまとめる

test_macros.sql
{% macro test_macros() %}
    {% do test__generate_alias_name() %}
    {% do test__generate_schema_name() %}
{% endmacro %}
generate_schema_name.sql
{% macro generate_schema_name(custom_schema_name, node, test_target=none) -%}
    {%- set effective_target = test_target if test_target is not none else target -%}

    {%- if custom_schema_name is none -%} {{ effective_target.schema }}
    {%- else -%} {{ custom_schema_name | trim }}
    {%- endif -%}

{%- endmacro %}
test__generate_schema_name.sql
{% macro test__generate_schema_name() %}

    {# カスタムスキーマが指定されている場合、それがそのまま使用される #}
    {% set custom_schema_name = "custom_schema" %}
    {% set node = none %}
    {% set test_target = {"schema": "test_target_schema"} %}
    {{
        dbt_unittest.assert_equals(
            dbt_project.generate_schema_name(custom_schema_name, node, test_target),
            "custom_schema",
        )
    }}

    {# カスタムスキーマが指定されていない場合、target.schema を使用する #}
    {% set custom_schema_name = none %}
    {% set node = none %}
    {% set test_target = {"schema": "test_target_schema"} %}
    {{
        dbt_unittest.assert_equals(
            dbt_project.generate_schema_name(custom_schema_name, node, test_target),
            "test_target_schema",
        )
    }}

{% endmacro %}
全テスト実行
dbt run-operation test_macros
個別実行
dbt run-operation generate_schema_name
テスト失敗
15:47:45  Encountered an error while running operation: Compilation Error in macro test_macros (macros/__tests__/test_macros.sql)
  FAILED: test_target_schema does not equal test_target_schema2.
  
  > in macro assert_equals (macros/assert_equals.sql)
  > called by macro test__generate_schema_name (macros/generate_name/__tests__/test__generate_schema_name.sql)
  > called by macro test_macros (macros/__tests__/test_macros.sql)
  > called by macro test_macros (macros/__tests__/test_macros.sql)
YuichiYuichi

WIP

dbtでマクロのユニットテストをJest風に書く構成

概要

dbt のマクロの挙動をユニットテストで担保したい場合、dbt-unittest のようなライブラリと run-operation を使って検証できます。本記事では、Jest風に __tests__ フォルダを用いてテストコードを整理し、各マクロのテストをモジュールごとに管理する構成を紹介します。


ディレクトリ構成

以下のように、各マクロディレクトリに __tests__ フォルダを設けてテストを分離します:

dbt_project/
├─ macros/
│  └─ __tests__/
│    └─ test_macros.sql             # 全体のエントリーポイント
├─ generate_name/
│  ├─ __tests__/
│  │  ├─ test__generate_alias_name.sql
│  │  └─ test__generate_schema_name.sql
│  ├─ generate_alias_name.sql
│  └─ generate_schema_name.sql

Jest では __tests__ フォルダを用いてテストコードを分離管理する文化がありますが、dbt でも似たような思想で管理するとスッキリします。


テストコードの書き方

例として generate_schema_name マクロのテストを書いてみます。

マクロ本体(generate_name/generate_schema_name.sql

{% macro generate_schema_name(custom_schema_name, node, test_target=none) -%}
    {%- set effective_target = test_target if test_target is not none else target -%}

    {%- if custom_schema_name is none -%} {{ effective_target.schema }}
    {%- else -%} {{ custom_schema_name | trim }}
    {%- endif -%}
{%- endmacro %}

テストコード(generate_name/__tests__/test__generate_schema_name.sql

{% macro test__generate_schema_name() %}

    {# カスタムスキーマが指定されている場合、それがそのまま使用される #}
    {% set custom_schema_name = "custom_schema" %}
    {% set node = none %}
    {% set test_target = {"schema": "test_target_schema"} %}
    {{
        dbt_unittest.assert_equals(
            dbt_project.generate_schema_name(custom_schema_name, node, test_target),
            "custom_schema",
        )
    }}

    {# カスタムスキーマが指定されていない場合、target.schema を使用する #}
    {% set custom_schema_name = none %}
    {% set node = none %}
    {% set test_target = {"schema": "test_target_schema"} %}
    {{
        dbt_unittest.assert_equals(
            dbt_project.generate_schema_name(custom_schema_name, node, test_target),
            "test_target_schema",
        )
    }}

{% endmacro %}

テストマクロのエントリーポイント

すべてのテストをまとめて実行するためのエントリーポイントを作成します(macros/__tests__/test_macros.sql):

{% macro test_macros() %}
    {% do test__generate_alias_name() %}
    {% do test__generate_schema_name() %}
{% endmacro %}

実行方法

全テストを実行

dbt run-operation test_macros

個別のマクロを手動で試す

dbt run-operation generate_schema_name

テスト失敗時の出力例

失敗時は下記のように明確にエラー箇所がログに表示されます:

Encountered an error while running operation: Compilation Error in macro test_macros (macros/__tests__/test_macros.sql)
  FAILED: test_target_schema does not equal test_target_schema2.

  > in macro assert_equals (macros/assert_equals.sql)
  > called by macro test__generate_schema_name (macros/generate_name/__tests__/test__generate_schema_name.sql)
  > called by macro test_macros (macros/__tests__/test_macros.sql)

まとめ

  • Jestライクに __tests__ フォルダでマクロごとのテストを管理すると見通しが良くなる。
  • dbt run-operation を使うことでユニットテストが簡単に実行できる。
  • dbt_unittest を使えば、assert_equalsassert_in などの便利な検証ができる。

マクロを共通化していくプロジェクトでは、このようなテスト戦略を早めに取り入れておくと、将来的な変更に強くなります。