generic test は、YAML から呼び出せる再利用可能な「検証マクロ」です。1つ作っておけば列やモデルに横展開でき、品質ルールの標準化に直結します。
本稿は、最小のカスタム test から multi-relation の書き方、実行フローと保管、ベストプラクティスまでを一気通貫で解説します。試験でも狙われがちな用語の違いと設定の勘所を押さえましょう。
dbt の generic test は、Jinja マクロとして定義され、SELECT で「失敗行のみ」を返す SQL を生成します。YAML(schema.yml)から引数付きで呼び出す点が特徴で、not_null や unique と同じ仕組みで自作できます。
一方、singular test は tests/ 配下に置く単発の SQL ファイルで、使い回しよりも個別検証に向きます。試験では、この2つの違いと generic test の引数・config の扱いが頻出です。
| 項目 | generic test | singular test |
|---|---|---|
| 定義場所の一般例 | tests/generic/ または macros/ 配下のマクロ | tests/ 配下の SQL ファイル |
| 呼び出し方法 | schema.yml の tests セクションで引数付き | dbt test でファイル単位 |
| 再利用性 | 高い(引数で汎用化) | 低い(ケース固有になりがち) |
| 標準搭載例 | unique, not_null, relationships など | なし(自作) |
| 適用単位 | モデル単位・列単位どちらも可 | テストファイル単位 |
既存 not_null / unique テストの呼び出し例(schema.yml)
models:\n - name: fct_orders\n columns:\n - name: order_id\n tests:\n - not_null\n - uniqueまずは「値が最小値〜最大値の範囲内か」を検証する accepted_range を作ってみます。generic test マクロは、失敗行のみを返す SELECT を生成すればよい点がポイントです。
ファイル配置はプロジェクトの慣習に合わせます。tests/generic/accepted_range.sql のように置くと識別しやすく、パッケージ化の際も分かりやすくなります。
tests/generic/accepted_range.sql と schema.yml の最小例
{% test accepted_range(model, column_name, min_value, max_value, inclusive=True) %}\nselect\n *\nfrom {{ model }}\nwhere\n {% if inclusive %}\n ({{ column_name }} < {{ min_value }} or {{ column_name }} > {{ max_value }})\n {% else %}\n ({{ column_name }} <= {{ min_value }} or {{ column_name }} >= {{ max_value }})\n {% endif %}\n{% endtest %}\n\n# schema.yml 側の呼び出し例\nmodels:\n - name: fct_orders\n columns:\n - name: amount\n tests:\n - accepted_range:\n min_value: 0\n max_value: 100000\n inclusive: trueテストの「可変ロジック」は引数で渡し、「実行時の挙動(警告化・保管・絞り込みなど)」は config で制御します。config は YAML 側で個別のテストインスタンスごとに上書きできます。
よく使う設定は severity(warn/error)、where(対象行の事前絞り込み)、limit(保管やログの出力量を抑制)、store_failures(失敗行をテーブルに保存)です。where はテスト SQL に条件を埋めるのではなく、config で指定すると再利用性が高まります。
YAML での test config 例(警告化・対象絞り込み・失敗行の保管)
models:\n - name: fct_orders\n columns:\n - name: amount\n tests:\n - accepted_range:\n min_value: 0\n max_value: 100000\n config:\n severity: warn\n where: "order_status != 'CANCELLED'"\n limit: 100\n store_failures: truerelationships のように、参照先テーブルを追加で受け取る generic test も実装できます。YAML から to: に ref() または source() を渡し、test マクロ側で結合して孤児キーを検出します。
試験では、model は Relation として渡され、{{ model }} でそのまま参照できる点、to も Relation として受け取れる点が問われがちです。文字列ではないため、マクロ内で再度 ref() を呼ぶ必要はありません。
2 リレーションを受け取る exists_in テスト例と YAML
{% test exists_in(model, column_name, to, field) %}\nselect\n src.{{ column_name }} as fk_value\nfrom {{ model }} as src\nleft join {{ to }} as tgt\n on src.{{ column_name }} = tgt.{{ field }}\nwhere src.{{ column_name }} is not null\n and tgt.{{ field }} is null\n{% endtest %}\n\n# schema.yml での呼び出し(列テスト)\nmodels:\n - name: fct_orders\n columns:\n - name: customer_id\n tests:\n - exists_in:\n to: ref('dim_customers')\n field: id\n config:\n severity: errordbt test は generic test マクロをコンパイルし、失敗行を返す SELECT を各ターゲットに送ります。0 行ならパス、1 行以上なら失敗。severity=warn なら警告、error ならジョブを失敗にします。
store_failures: true を指定すると、失敗行が専用スキーマ(デフォルトはターゲットスキーマ配下のテスト用スキーマ)にテーブルとして保存されます。where と limit を併用すると、対象縮小や保管量の抑制に効果的です。
generic test 実行フロー
実行と保管のコマンド例
# モデル配下を選択してテスト\ndbt test -s fct_orders\n\n# テスト名で選択(パッケージ名を含められる)\ndbt test -s test_type:accepted_range\n\n# 失敗行を保管して実行\ndbt test -s fct_orders --store-failures命名は、動詞ではなく「検証の意味」が伝わる名詞句にし、引数は最小限に。テスト内で方言依存の関数を使う場合は adapter.dispatch を使って吸収します。
列名のクオートや大小文字は方言で差が出やすい部分です。必要に応じて adapter.quote を使うか、識別子に半角英数・アンダースコアを徹底しましょう。NULL の扱いは RDBMS ごとに差がないものの、外部キー検証では意図的に NULL を除外するのが一般的です。
adapter.dispatch で方言差を吸収する雛形(テスト内で利用)
{% macro cast_to_numeric(expr) %}\n {{ adapter.dispatch('cast_to_numeric', 'my_project')(expr) }}\n{% endmacro %}\n\n{% macro my_project__cast_to_numeric(expr) %}\n cast({{ expr }} as numeric)\n{% endmacro %}\n\n{% macro snowflake__cast_to_numeric(expr) %}\n try_to_number({{ expr }})\n{% endmacro %}\n\n# テスト内利用例\n# where {{ cast_to_numeric(column_name) }} >= 0Analytics Engineer
問題 1
dbt のカスタム generic test マクロに渡される引数 model は何を表し、SQL 内ではどのように参照しますか?
正解: A
generic test の第一引数 model は Relation です。テスト SQL では {{ model }} で直接リレーションを展開できます。ref() を再度呼ぶ必要はありません。
generic test マクロはどこに置けば認識されますか?
macros/ 配下に置けば確実に認識されます。慣習として tests/generic/ 配下に置くプロジェクトもありますが、要はマクロ検索パスにあれば動作します。パッケージとして配布する場合は、ディレクトリ構成を一貫させてください。
外部パッケージ(例: dbt-utils)の generic test をどう呼べばいいですか?
パッケージを packages.yml で導入後、YAML の tests に通常どおり記述します。名前空間は自動で解決されるため、basic カスタムと同様に使えます(例: tests: - dbt_utils.expression_is_true: {expression: "amount > 0"})。
失敗行の保存先やスキーマを制御できますか?
store_failures: true で失敗行を保存できます。保存先スキーマはプロジェクト設定(dbt_project.yml の tests スキーマ設定など)で制御可能です。併せて where・limit を指定し、保存量を適切に抑制してください。
NicheeLab編集部
データエンジニアリング・クラウド資格の専門家。Databricks・Snowflake等の認定資格を保有し、実務経験に基づいた問題作成・解説を行っています。NicheeLab運営。
dbt Model の基礎: SQL で定義する変換の最小単位
Analytics Engineer 向けに、dbt Model の定義、マテリアライゼーション、依存関係、インクリメン...
dbt Analytics Engineer 試験ガイド: 出題範囲・配点・申込の実務視点
dbt Analytics Engineer 認定の出題範囲、配点の考え方、申込から受験までの流れを、公式ドキュメントの...
dbt Cloud と dbt Core の違いと選び方:Analytics Engineer 試験に効く要点
dbt Cloud と dbt Core の機能差を、実務と資格対策の両面から整理。スケジューリング、IDE、RBAC、...
dbt プロジェクト構造ガイド: models / seeds / macros の実務レイアウト
Analytics Engineer 向けに、dbt プロジェクトのディレクトリ構造と命名規約、dbt_project....
dbt_project.yml の読み方:主要設定と命名を最短で掴む
dbt_project.yml の必須キー、命名解決(database.schema.identifier)、設定優先度...