mcpField
A
adding-dbt-unit-test

adding-dbt-unit-test

github.com/dbt-labsテスト・品質7471.1k 回インストール1 週間前に更新
リポジトリ

adding-dbt-unit-test について

dbt の SQL モデルに対し、入力をモックして期待出力を検証するユニットテストの定義と実行を支援するスキルです。データ開発者がモデルのマテリアライズ前に変換ロジックや境界ケースを確認できます。

主な機能

  1. 01モデル・入力・出力のテスト構成
  2. 02ソース、シード、モデル入力のモック
  3. 03dict・CSV・SQL のフィクスチャ形式
  4. 04インクリメンタルモデルのテスト案内
  5. 05dbt build または dbt test で実行

活用シーン

  1. 01SQL 変換のシナリオや境界ケースの確認
  2. 02報告済みの不具合修正の検証
  3. 03リファクタリング前のロジック検証
  4. 04重要度の高いモデルの検証

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/dbt-labs/dbt-agent-skills --skill adding-dbt-unit-test

インストール後、Claude Code で/adding-dbt-unit-testを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

references/
examples.md
spec.md
special-cases-ephemeral-dependency.md
special-cases-incremental-model.md
special-cases-special-case-overrides.md
special-cases-versioned-model.md
warehouse-bigquery-caveats.md
warehouse-bigquery-data-types.md
warehouse-postgres-data-types.md
warehouse-redshift-caveats.md
warehouse-redshift-data-types.md
warehouse-snowflake-data-types.md
warehouse-spark-data-types.md

詳細

dbt モデルのユニットテストを追加する

その他のリソース

dbt のユニットテストとは

dbt のユニットテストでは、本番環境でマテリアライズする前に、静的な入力を使って SQL のモデリングロジックを検証します。モデルのユニットテストに失敗すると、dbt はそのモデルをマテリアライズしません。

使用する場合

次のような場合にモデルのユニットテストを実施してください。

  • モデルの意図した機能やエッジケースについて、モデル・入力・出力のシナリオを追加し、後日モデルのロジックが変更された際のリグレッションを防止する。
  • バグ修正によって、既存の dbt モデルに関するバグ報告の問題が解決されることを確認する。

その他の例:

  • SQL に複雑なロジックが含まれている場合:
    • 正規表現
    • 日付計算
    • ウィンドウ関数
    • when が多数ある case when 文
    • 切り捨て
    • 複雑な結合(複数の結合、自己結合、または単純ではない条件での結合)
  • 関数の作成と同様に、入力データを処理するカスタムロジックを記述する場合。
  • 以前にバグが報告されたことがあるロジック。
  • 実データではまだ発生していないものの、適切に処理できることを確認しておきたいエッジケース。
  • 変換ロジックをリファクタリングする前(特に大規模なリファクタリングの場合)。
  • 「重要度」が高いモデル(公開モデル、契約対象のモデル、またはエクスポージャーの上流に直接位置するモデル)。

使用しない場合

ユニットテストの作成を推奨しないケース:

  • ウェアハウスのプロバイダーによって十分にテストされている組み込み関数。予期しない問題が起きた場合、関数そのものよりも基盤となるデータに問題がある可能性が高いため、ユニットテストのフィクスチャデータから有益な情報は得られません。
    • min() などの一般的な SQL 仕様関数

一般的な形式

dbt のユニットテストでは、モデル、与えられた入力、期待される出力の 3 つを使います(モデル・入力・出力)。

  1. model - このモデルをビルドする場合
  2. given の入力 - 前提条件として、ソース、シード、モデルのセットが与えられた場合
  3. expect の出力 - 事後条件として、モデルのこの行の内容を期待する場合
ワークフロー
1. テストするモデルを選ぶ

説明するまでもありません。タイトルのとおりです!

2. 入力をモックする
  • モデルが依存する各ノードの入力を作成します。
  • 使用するモックデータを指定します。
  • デフォルト(YAML dict)と異なる場合は、format を指定します。
    • 使用する format を判断するには、下記の「ユニットテスト用データの format」セクションを参照してください。
  • モックデータには、このテストケースで使用する列だけを含めれば十分です。

ヒント: dbt show を使って、アップストリームモデルやソースの既存データを確認できます。現実的な入力構造を理解するのに役立ちます。ただし、ユニットテストのフィクスチャで使用する前に、必ずサンプルデータから機密情報や個人を特定できる情報(PII)を削除してください。

# Preview upstream model data
dbt show --select upstream_model --limit 5
3. 出力をモックする
  • これらの入力に基づいてモデルが作成すると期待するデータを指定します。
  • デフォルト(YAML dict)と異なる場合は、format を指定します。
    • 使用する format を判断するには、下記の「ユニットテスト用データの format」セクションを参照してください。
  • モックデータには、このテストケースで使用する列だけを含めれば十分です。
4. 実行前にアップストリームモデルが存在することを確認する

ユニットテストを実行するには、直接の親モデルがウェアハウスに存在している必要があります。 ユニットテストを単独で実行する(dbt test)前に、アップストリームモデルがすでに存在することを確認してください。

# Check if upstream models exist in the warehouse
dbt list --select +my_model --exclude my_model --resource-type model
# Then verify the tables/views actually exist in the warehouse via dbt show or your SQL client
dbt show --select upstream_model --limit 1

アップストリームモデルが存在しない場合、または存在するものの変更後にまだ更新されていない場合は、--empty を使ってスキーマのみのモデルを作成します。

# Build upstream models cheaply (schema only, no data read)
dbt run --select +my_model --exclude my_model --empty

警告: --empty は既存のモデルをスキーマのみ(0 行)のバージョンで上書きします。モデルがまだ存在しない場合、またはスキーマの変更を適用する必要がある場合にのみ使用してください。保持したいデータがアップストリームモデルに含まれている場合は使用しないでください。データが消去されます。

dbt build --select my_model を使用する場合(推奨)は、この手順を省略してください。ユニットテストを含むパイプライン全体が処理されます。

最小限のユニットテスト

次のモデルがあるとします。

-- models/hello_world.sql

select 'world' as hello

このモデルの最小限のユニットテスト:

# models/_properties.yml

unit_tests:
  - name: test_hello_world

    # Always only one transformation to test
    model: hello_world

    # No inputs needed this time!
    # Most unit tests will have inputs -- see the "real world example" section below
    given: []

    # Expected output can have zero to many rows
    expect:
      rows:
        - {hello: world}

ユニットテストの実行

hello_world モデルのユニットテストを実行し、モデルをビルドして、データテストを実行します。

dbt build --select hello_world

ユニットテストに合格した場合にのみモデルがマテリアライズされ、データテストに進むため、ウェアハウスの利用料金を節約できます。

または、モデルをビルドせず、データテストも実行せずに、ユニットテストのみを実行します。

dbt test --select "hello_world,test_type:unit"

または、名前を指定して特定のユニットテストを選択します。

dbt test --select test_is_valid_email_address
本番ビルドからユニットテストを除外する

dbt Labs は、ユニットテストを開発環境または CI 環境でのみ実行することを強く推奨しています。ユニットテストの入力は静的なため、本番環境で実行するために追加のコンピューティングリソースを使う必要はありません。テスト駆動の開発アプローチを採用する開発時や、変更によってテストが壊れないことを確認する CI で使用してください。

--resource-type フラグの --exclude-resource-type または DBT_EXCLUDE_RESOURCE_TYPES 環境変数を使用して、本番ビルドからユニットテストを除外し、コンピューティングリソースを節約します。

より現実的な例

unit_tests:

  - name: test_order_items_count_drink_items_with_zero_drinks
    description: >
      Scenario: Order without any drinks
        When the `order_items_summary` table is built
        Given an order with nothing but 1 food item
        Then the count of drink items is 0

    # Model
    model: order_items_summary

    # Inputs
    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 }

    # Output
    expect:
      rows:
        - {
            order_id: 76,
            count_drink_items: 0,
          }

ユニットテストの例については、references/examples.md を参照してください。

サポート対象と対象外のシナリオ

  • dbt がサポートするのは、SQL モデルのユニットテストのみです。
    • Python モデルのユニットテストはサポートされていません。
    • スナップショット、シード、ソース、分析など、モデル以外のノードのユニットテストはサポートされていません。
  • dbt がユニットテストの追加をサポートするのは、_現在の_プロジェクトのモデルのみです。
    • プロジェクトをまたぐモデルや、パッケージからインポートしたモデルのユニットテストはサポートされていません。
  • dbt は、materialized view マテリアライゼーションを使用するモデルのユニットテストをサポートして_いません_。
  • dbt は、再帰 SQL を使用するモデルのユニットテストをサポートして_いません_。
  • dbt は、内省クエリを使用するモデルのユニットテストをサポートして_いません_。
  • dbt は、インクリメンタルモデルで挿入またはマージした後のデータベーステーブルの最終状態に対する expect 出力をサポートして_いません_。
  • dbt は、インクリメンタルモデルでマージまたは挿入される内容に対する expect 出力をサポートして_います_。

知っておくと便利なこと

  • 単体テストは、model-paths ディレクトリ(デフォルトでは models/)内の YAML ファイルで定義する必要があります
  • 単体テスト用のフィクスチャファイルは、test-paths ディレクトリ(デフォルトでは tests/fixtures)内の SQL または CSV ファイルで定義する必要があります
  • コンパイル時の「node not found」エラーを回避するため、すべての ref または source モデル参照を、単体テストの設定で input として含めてください。
  • モデルに複数のバージョンがある場合、デフォルトでは単体テストはモデルのすべてのバージョンで実行されます。
  • エフェメラルモデルに依存するモデルを単体テストする場合は、エフェメラルモデルの入力に format: sql を使用する必要があります。
  • join のロジックを単体テストするには、モデル内のテーブル名にエイリアスを付ける必要があります

単体テストを指定する YAML

  • 単体テストの YAML 定義で必須および任意のキーをすべて確認するには、references/spec.md を参照してください

単体テストの入力

単体テストで特定のモデルまたはソースを参照するには、input を使用します。

  • input: には、ref または source 呼び出しを表す文字列を使用します。
    • ref('my_model') または ref('my_model', v='2') または ref('dougs_project', 'users')
    • source('source_schema', 'source_name')
  • シード入力の場合:
    • シードの input を指定しない場合は、シードの CSV ファイルを入力として使用します。
    • シードの input を指定した場合は、その入力を代わりに使用します。
  • 行を空のリスト rows: [] に設定すると、「空」の入力を使用できます。
    • モデルに ref または source 依存関係があっても、この単体テストではその値が関係ない場合に便利です。ただし、その入力に対してモデルが結合を行い、行が除外される可能性がある場合は注意してください。

models/schema.yml

unit_tests:
  - name: test_is_valid_email_address  # this is the unique name of the test
    model: dim_customers  # name of the model I'm unit testing
    given:  # the mock data for your inputs
      - input: ref('stg_customers')
        rows:
         - {email: [email protected],     email_top_level_domain: example.com}
         - {email: [email protected],     email_top_level_domain: unknown.com}
         - {email: badgmail.com,         email_top_level_domain: gmail.com}
         - {email: missingdot@gmailcom,  email_top_level_domain: gmail.com}
      - input: ref('top_level_email_domains')
        rows:
         - {tld: example.com}
         - {tld: gmail.com}
      - input: ref('irrelevant_dependency')  # dependency that we need to acknowlege, but does not need any data
        rows: []
...

単体テストのデータ format

デフォルトの形式は dict です — まずはこれを使ってください

別の形式を使う明確な理由がない限り、dict(インライン YAML)を使用してください。format: を省略した場合のデフォルトです。

given:
  - input: ref('orders')
    # no format: key needed — dict is the default
    rows:
      - {order_id: 1, status: completed, amount: 100}
      - {order_id: 2, status: pending, amount: 50}

format の選び方

dict(デフォルト)を使用sql を使用csv を使用
その他すべて — まずはこちらから始めてくださいモデルがエフェメラルモデルに依存している外部フィクスチャファイルを使用する
列のデータ型が dict/csv でサポートされていないdict でサポートされていない列のデータ型
サポートされていない型を含む外部フィクスチャファイル

重要な sql に関する注意:sql 形式では、モックデータ内のすべての列を指定する必要があります。dict と csv では、テストに関連する列だけを指定すればよいため、より簡潔です。

重要な sql の要件:モデルの ref() または source() 入力のいずれかがエフェメラルモデルの場合、その入力には必ず sql 形式を使用してください。dict と csv は失敗します。

dbt は、単体テスト内のモックデータに次の 3 つの形式をサポートしています。

  1. dict(デフォルト):インライン YAML の辞書値。
  2. csv:インライン CSV 値または CSV ファイル。
  3. sql:インライン SQL クエリまたは SQL ファイル。

各形式の例については、references/examples.md を参照してください

注意:

  • sql 形式ではモックデータの_すべての列_を指定する必要がありますが、dict と csv では一部の列のみを指定できます。
  • エフェメラルモデルに依存するモデルを単体テストできるのは sql 形式のみです。この場合、dict と csv は使用できません。
  • Jinja をサポートする形式はありません。
フィクスチャファイル

dict 形式ではインライン YAML のモックデータのみを使用できますが、csv または sql はインラインで使用することも、個別のフィクスチャファイルで使用することもできます。フィクスチャファイルは、いずれかの test-paths 内の fixtures サブディレクトリに保存してください。例:tests/fixtures/my_unit_test_fixture.sql。

dict または csv 形式では、関連する列のモックデータだけを定義すればよいため、簡潔で_具体的な_単体テストを記述できます。sql 形式では、_すべての_列を定義する必要があります。

特殊なケース

プラットフォーム/アダプター固有の注意事項

(Redshift、BigQuery などの)プラットフォームで実装する場合、プラットフォーム固有の詳細が必要です。ご利用のデータベースの注意事項ファイルが存在する場合は、そちらをお読みください。

プラットフォーム/アダプター固有のデータ型

ユニットテストは、データ型そのものではなく、期待される 値 をテストするように設計されています。dbt は指定された値を受け取り、入力モデルと出力モデルから推論したデータ型へのキャストを試みます。

ユニットテストの YAML 定義で入力値と期待値を指定する方法は、より複雑なデータ型に若干の違いはあるものの、データウェアハウス間でおおむね共通しています。

ご利用のデータベースのデータ型ファイルをお読みください。

ユニットテストの無効化

デフォルトでは、指定されたすべてのユニットテストが有効になっており、--select フラグに従って実行対象に含まれます。

ユニットテストを実行対象から外すには、次のように設定します。

    config: 
      enabled: false

ユニットテストが誤って失敗しており、修正されるまで無効にしておく必要がある場合に便利です。

ユニットテストが失敗した場合

ユニットテストが失敗すると、「actual differs from expected」というログメッセージが表示され、両者の「data diff」が示されます。

actual differs from expected:

@@ ,email           ,is_valid_email_address
→  ,[email protected],True→False
   ,[email protected],False

ユニットテストが失敗する主な可能性は2つあります。

  1. ユニットテストの構築方法に誤りがある(偽陽性)
  2. モデルにバグがある(真陽性)

どちらに該当するか判断するには、専門家としての判断が必要です。

--empty フラグ

ユニットテストの対象モデルの直接の親モデルは、ユニットテストを実行する前にデータウェアハウスに存在している必要があります。run コマンドと build コマンドでは、スキーマのみを作成するドライランに --empty フラグを使用できます。--empty フラグを指定すると、ref と sources の行数がゼロになります。dbt はターゲットのデータウェアハウスに対してモデルの SQL を引き続き実行しますが、入力データのコストのかかる読み取りは回避します。これにより、依存関係を検証し、モデルが正しくビルドされることを確認できます。

データウェアハウスのコストを抑えるために、--empty フラグを使用してモデルの空のバージョンをビルドしてください。


dbt run --select "stg_customers top_level_email_domains" --empty

よくある間違い

間違い修正方法
組み込み関数を使った単純な SQL のテスト複雑なロジックのみをユニットテストします。例:正規表現、日付計算、ウィンドウ関数、複数条件の CASE 文
入力データのすべての列をモックするテストケースに関連する列のみを含めます
dict が使えるのに sql 形式を使うdict を優先し(最も読みやすいため)、必要な場合にのみ csv または sql を使います
ref または source の input がない「node not found」エラーを避けるため、モデルの依存関係をすべて含めます
Python モデルやスナップショットをテストするユニットテストは SQL モデルのみをサポートします

コメント