dbt

dbt カスタム generic test 実装ガイド: 再利用可能な検証の作り方

2026-04-19
NicheeLab編集部

generic test は、YAML から呼び出せる再利用可能な「検証マクロ」です。1つ作っておけば列やモデルに横展開でき、品質ルールの標準化に直結します。

本稿は、最小のカスタム test から multi-relation の書き方、実行フローと保管、ベストプラクティスまでを一気通貫で解説します。試験でも狙われがちな用語の違いと設定の勘所を押さえましょう。

カスタム generic test の基礎と既存テストとの違い

dbt の generic test は、Jinja マクロとして定義され、SELECT で「失敗行のみ」を返す SQL を生成します。YAML(schema.yml)から引数付きで呼び出す点が特徴で、not_null や unique と同じ仕組みで自作できます。

一方、singular test は tests/ 配下に置く単発の SQL ファイルで、使い回しよりも個別検証に向きます。試験では、この2つの違いと generic test の引数・config の扱いが頻出です。

  • 定義はマクロ(Jinja)だが、実行は dbt test の test マテリアライゼーションに乗る
  • generic test は YAML からモデル/列に付与、singular test は SQL 単体で実行
  • 戻り値は常に「失敗行のみ」。0 行ならパス、1 行以上で失敗(severity により warn/error)
項目generic testsingular 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 を作る

まずは「値が最小値〜最大値の範囲内か」を検証する accepted_range を作ってみます。generic test マクロは、失敗行のみを返す SELECT を生成すればよい点がポイントです。

ファイル配置はプロジェクトの慣習に合わせます。tests/generic/accepted_range.sql のように置くと識別しやすく、パッケージ化の際も分かりやすくなります。

  • 戻り値は失敗行のみ(パス時は 0 行)
  • 引数は model(Relation)、column_name(文字列)、任意のパラメータを受けられる
  • 列名は {{ column_name }} で参照。model は {{ model }} でリレーションとして参照

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 で制御します。config は YAML 側で個別のテストインスタンスごとに上書きできます。

よく使う設定は severity(warn/error)、where(対象行の事前絞り込み)、limit(保管やログの出力量を抑制)、store_failures(失敗行をテーブルに保存)です。where はテスト SQL に条件を埋めるのではなく、config で指定すると再利用性が高まります。

  • 引数はマクロのロジックを変えるもの(例: min_value、max_value)
  • config はテストの評価や実行の周辺挙動(例: severity、where、limit、store_failures)
  • config はテスト呼び出しごとに独立して指定可能。共通化は dbt_project.yml で上書きも可

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: true

複数リレーションを受け取るテストの書き方

relationships のように、参照先テーブルを追加で受け取る generic test も実装できます。YAML から to: に ref() または source() を渡し、test マクロ側で結合して孤児キーを検出します。

試験では、model は Relation として渡され、{{ model }} でそのまま参照できる点、to も Relation として受け取れる点が問われがちです。文字列ではないため、マクロ内で再度 ref() を呼ぶ必要はありません。

  • model と to はいずれも Relation。SQL では {{ model }}、{{ to }} のように直接展開
  • 外部キー検証では NULL 扱いの差異に注意(NULL は通常スキップする)
  • 方言差(大小文字・クオート)は列名/識別子の扱いで吸収する

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: error

実行フロー、パフォーマンス、失敗行の保管

dbt test は generic test マクロをコンパイルし、失敗行を返す SELECT を各ターゲットに送ります。0 行ならパス、1 行以上なら失敗。severity=warn なら警告、error ならジョブを失敗にします。

store_failures: true を指定すると、失敗行が専用スキーマ(デフォルトはターゲットスキーマ配下のテスト用スキーマ)にテーブルとして保存されます。where と limit を併用すると、対象縮小や保管量の抑制に効果的です。

  • 大量データの列テストはコスト増。where でスコープを絞る、スケジュールで段階実行
  • 選択的に dbt test -s model_name: で対象を限定し、開発中の反復を高速化
  • concurrency はターゲットのクォータとロック状況を考慮。依存関係で自動制御される

generic test 実行フロー

schema.ymlgeneric test マクロ失敗行のみ返す SELECT に変換test マテリアライゼーションパス: 0 行で完了 / 失敗: 1+ 行で warn/error(store_failures: true で失敗行テーブル作成)

実行と保管のコマンド例

# モデル配下を選択してテスト\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 を除外するのが一般的です。

  • テストは決定的に(非決定関数・現在時刻への依存を避ける)
  • YAML の where で対象を絞り、マクロは汎用に保つ
  • 外部配布を想定するならパッケージ名空間とセマンティックバージョニングを運用

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) }} >= 0

問題で確認

Analytics Engineer

問題 1

dbt のカスタム generic test マクロに渡される引数 model は何を表し、SQL 内ではどのように参照しますか?

  1. A. 実行時の Relation オブジェクトで、SQL 内では {{ model }} としてそのまま参照する
  2. B. モデル名の文字列で、SQL 内では {{ ref(model) }} を呼び出す必要がある
  3. C. 列名の配列を含む辞書で、SQL からは直接参照できない
  4. D. メモリ上の DataFrame で、マテリアライズ不要で動作する

正解: 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 を指定し、保存量を適切に抑制してください。

この記事で学んだ内容を問題で確認しましょう

16,000問以上の問題で実力チェック

無料で問題を解いてみる
この記事の著者

NicheeLab編集部

データエンジニアリング・クラウド資格の専門家。Databricks・Snowflake等の認定資格を保有し、実務経験に基づいた問題作成・解説を行っています。NicheeLab運営。


関連記事
dbt

dbt Model の基礎: SQL で定義する変換の最小単位

Analytics Engineer 向けに、dbt Model の定義、マテリアライゼーション、依存関係、インクリメン...

dbt

dbt Analytics Engineer 試験ガイド: 出題範囲・配点・申込の実務視点

dbt Analytics Engineer 認定の出題範囲、配点の考え方、申込から受験までの流れを、公式ドキュメントの...

dbt

dbt Cloud と dbt Core の違いと選び方:Analytics Engineer 試験に効く要点

dbt Cloud と dbt Core の機能差を、実務と資格対策の両面から整理。スケジューリング、IDE、RBAC、...

dbt

dbt プロジェクト構造ガイド: models / seeds / macros の実務レイアウト

Analytics Engineer 向けに、dbt プロジェクトのディレクトリ構造と命名規約、dbt_project....

dbt

dbt_project.yml の読み方:主要設定と命名を最短で掴む

dbt_project.yml の必須キー、命名解決(database.schema.identifier)、設定優先度...

dbtの記事一覧 (100件)
© 2026 NicheeLab All rights reserved.