adding-dbt-unit-test について
dbt の SQL モデルに対し、入力をモックして期待出力を検証するユニットテストの定義と実行を支援するスキルです。データ開発者がモデルのマテリアライズ前に変換ロジックや境界ケースを確認できます。
主な機能
- 01モデル・入力・出力のテスト構成
- 02ソース、シード、モデル入力のモック
- 03dict・CSV・SQL のフィクスチャ形式
- 04インクリメンタルモデルのテスト案内
- 05dbt build または dbt test で実行
活用シーン
- 01SQL 変換のシナリオや境界ケースの確認
- 02報告済みの不具合修正の検証
- 03リファクタリング前のロジック検証
- 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.mdspec.mdspecial-cases-ephemeral-dependency.mdspecial-cases-incremental-model.mdspecial-cases-special-case-overrides.mdspecial-cases-versioned-model.mdwarehouse-bigquery-caveats.mdwarehouse-bigquery-data-types.mdwarehouse-postgres-data-types.mdwarehouse-redshift-caveats.mdwarehouse-redshift-data-types.mdwarehouse-snowflake-data-types.mdwarehouse-spark-data-types.md詳細
dbt モデルのユニットテストを追加する
その他のリソース
- 仕様リファレンス - ユニットテストで必須および任意の YAML キーすべて
- 例 - 形式(dict、csv、sql)ごとのユニットテストの例
- インクリメンタルモデル - インクリメンタルモデルのユニットテスト
- エフェメラルな依存関係 - エフェメラルモデルに依存するモデルのユニットテスト
- 特殊ケースのオーバーライド - イントロスペクティブマクロ、プロジェクト変数、環境変数
- バージョン管理されたモデル - バージョン管理された SQL モデルのユニットテスト
- BigQuery の注意点 - BigQuery 固有の注意点
- BigQuery のデータ型 - BigQuery のデータ型の処理
- Postgres のデータ型 - Postgres のデータ型の処理
- Redshift の注意点 - Redshift 固有の注意点
- Redshift のデータ型 - Redshift のデータ型の処理
- Snowflake のデータ型 - Snowflake のデータ型の処理
- Spark のデータ型 - Spark のデータ型の処理
dbt のユニットテストとは
dbt のユニットテストでは、本番環境でマテリアライズする前に、静的な入力を使って SQL のモデリングロジックを検証します。モデルのユニットテストに失敗すると、dbt はそのモデルをマテリアライズしません。
使用する場合
次のような場合にモデルのユニットテストを実施してください。
- モデルの意図した機能やエッジケースについて、モデル・入力・出力のシナリオを追加し、後日モデルのロジックが変更された際のリグレッションを防止する。
- バグ修正によって、既存の dbt モデルに関するバグ報告の問題が解決されることを確認する。
その他の例:
- SQL に複雑なロジックが含まれている場合:
- 正規表現
- 日付計算
- ウィンドウ関数
whenが多数あるcase when文- 切り捨て
- 複雑な結合(複数の結合、自己結合、または単純ではない条件での結合)
- 関数の作成と同様に、入力データを処理するカスタムロジックを記述する場合。
- 以前にバグが報告されたことがあるロジック。
- 実データではまだ発生していないものの、適切に処理できることを確認しておきたいエッジケース。
- 変換ロジックをリファクタリングする前(特に大規模なリファクタリングの場合)。
- 「重要度」が高いモデル(公開モデル、契約対象のモデル、またはエクスポージャーの上流に直接位置するモデル)。
使用しない場合
ユニットテストの作成を推奨しないケース:
- ウェアハウスのプロバイダーによって十分にテストされている組み込み関数。予期しない問題が起きた場合、関数そのものよりも基盤となるデータに問題がある可能性が高いため、ユニットテストのフィクスチャデータから有益な情報は得られません。
min()などの一般的な SQL 仕様関数
一般的な形式
dbt のユニットテストでは、モデル、与えられた入力、期待される出力の 3 つを使います(モデル・入力・出力)。
model- このモデルをビルドする場合givenの入力 - 前提条件として、ソース、シード、モデルのセットが与えられた場合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 つの形式をサポートしています。
dict(デフォルト):インライン YAML の辞書値。csv:インライン CSV 値または CSV ファイル。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 形式では、_すべての_列を定義する必要があります。
特殊なケース
- インクリメンタルモデルのユニットテスト。references/special-cases-incremental-model.mdを参照してください。
- エフェメラルモデルに依存するモデルのユニットテスト。references/special-cases-ephemeral-dependency.mdを参照してください。
- イントロスペクティブマクロ、プロジェクト変数、または環境変数に依存するモデルのユニットテスト。references/special-cases-special-case-overrides.mdを参照してください。
- バージョン管理された SQL モデルのユニットテスト。references/special-cases-versioned-model.mdを参照してください。
プラットフォーム/アダプター固有の注意事項
(Redshift、BigQuery などの)プラットフォームで実装する場合、プラットフォーム固有の詳細が必要です。ご利用のデータベースの注意事項ファイルが存在する場合は、そちらをお読みください。
プラットフォーム/アダプター固有のデータ型
ユニットテストは、データ型そのものではなく、期待される 値 をテストするように設計されています。dbt は指定された値を受け取り、入力モデルと出力モデルから推論したデータ型へのキャストを試みます。
ユニットテストの YAML 定義で入力値と期待値を指定する方法は、より複雑なデータ型に若干の違いはあるものの、データウェアハウス間でおおむね共通しています。
ご利用のデータベースのデータ型ファイルをお読みください。
- references/warehouse-bigquery-data-types.md
- references/warehouse-postgres-data-types.md
- references/warehouse-redshift-data-types.md
- references/warehouse-snowflake-data-types.md
- references/warehouse-spark-data-types.md
ユニットテストの無効化
デフォルトでは、指定されたすべてのユニットテストが有効になっており、--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つあります。
- ユニットテストの構築方法に誤りがある(偽陽性)
- モデルにバグがある(真陽性)
どちらに該当するか判断するには、専門家としての判断が必要です。
--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 モデルのみをサポートします |