---
title: "dbt testの書き方：generic・singular・unit testの使い分けとCIへの組み込み【1.12対応】"
url: "https://www.issoh.co.jp/tech/details/19611/"
published: 2026-11-11
updated: 2026-11-11
categories: ["データベース"]
publisher: "株式会社一創"
---

# dbt testの書き方：generic・singular・unit testの使い分けとCIへの組み込み【1.12対応】

dbt testは、モデルを作ったあとのデータを検査するデータテストと、作る前の変換ロジックを固定データで確かめるユニットテストを、1つのコマンドでまとめて実行します。この記事では、2026年11月時点の公式ドキュメントをもとに、YAMLで書くgeneric test、SQLで書くsingular test、given・expectで書くunit testの書き方を、コピーして動かせる形で整理しました。severityとwhereの設定、失敗行の保存、プルリクエストで変更分だけを検査するCIの組み方まで扱います。

## まとめ：dbt testはデータとロジックを分けて書き、CIで変更分だけを回す

主キーと結合キーには`unique`と`not_null`、参照先がある列には`relationships`をYAMLで付けます。ここまではどのプロジェクトでも書いておく最低線で、1列あたり数行で済みます。

業務ルールの検査は、SQLで失敗行を返すsingular testに書きます。同じ形の検査が3か所以上に出てきたら、`{% test %}`ブロックのgeneric testへ切り出す、という順番が無理のない進め方です。

CASE式や日付計算のように間違えやすい変換は、unit testで入力と期待値を固定します。unit testは開発とCIでだけ回し、本番のジョブからは除外してください。

プルリクエストでは`state:modified+`で変更したモデルと下流だけをテストし、警告で済む検査は`severity: warn`に落とします。止めるべき失敗と眺めるだけの警告を分けておくと、テストが鳴りっぱなしで誰も見なくなる事態を防げます。

## dbt testの4種類：generic・singular・unitとパッケージ

dbt testで書けるテストは、generic・singular・unit・パッケージ提供の4種類に分かれます。

書き方に入る前に、それぞれが何を検査するのかを押さえておきます。手順やインストールがまだの場合は、先に[dbt Coreの使い方](https://www.issoh.co.jp/tech/details/19592/)で`dbt build`が通る状態を作ってください。

### データテストはモデル作成後、ユニットテストは作成前にロジックを検査する

公式の[データテストの解説](https://docs.getdbt.com/docs/build/data-tests)によれば、データテストはモデルやソースに対して`select`文を実行し、失敗した行を返すものです。返った行が0件なら合格になります。YAMLで宣言するgeneric testと、SQLファイルで書くsingular testの2つがこれに当たります。

一方の[ユニットテスト](https://docs.getdbt.com/docs/build/unit-tests)は1.8以降で使える機能で、静的な入力行をモデルのSQLに流し込み、出力が期待どおりかを比べます。本番テーブルを作る前に変換ロジックの誤りを見つけられる点が、データテストとの違いです。

| 種類             | 書く場所          | 検査の対象          |
| -------------- | ------------- | -------------- |
| generic test   | models配下のYAML | 作成済みの列の値       |
| singular test  | tests配下のSQL   | モデルをまたぐ業務ルール   |
| unit test      | models配下のYAML | モデルのSQLの変換ロジック |
| dbt\_utilsのテスト | models配下のYAML | 式・複合キー・値の範囲    |

### test\_typeセレクタで種類ごとに実行範囲を絞るコマンド

[dbt testコマンド](https://docs.getdbt.com/reference/commands/test)は、何も指定しなければデータテストとユニットテストを両方実行します。種類ごとに分けたいときは`test_type`セレクタを使います。

```
dbt test                                          # データテストとユニットテストを両方
dbt test --select "test_type:data"                # データテストだけ
dbt test --select "test_type:unit"                # ユニットテストだけ
dbt test --select "test_type:singular"            # SQLで書いたテストだけ
dbt test --select "test_type:generic"             # YAMLで宣言したテストだけ
dbt test --select "fct_orders,test_type:data"     # 1モデルのデータテストだけ
```

`test_type`セレクタはv1とv2の両方で動く書き方として案内されています。v1系だけで使える`--resource-type test`より、この形で手順書を書いておく方が移行時の書き換えが要りません。なお`dbt test`はテストだけを流すコマンドで、対象のモデルは先に作られている必要があります。

## generic testの書き方：組み込み4種とarguments・configの指定

generic testは、YAMLに数行足すだけで列の値を検査できる、最初に書くべきテストです。

### 組み込み4種のgeneric testを列ごとに指定するYAMLの書き方

組み込みのgeneric testは`unique`・`not_null`・`accepted_values`・`relationships`の4つです。テストに渡す値を`arguments:`の下に置く書き方は1.10.5以降で使え、v2では必須になりました。新しく書くなら最初からこの形にします。

```
# models/marts/schema.yml
version: 2
models:
  - name: fct_orders
    columns:
      - name: order_id
        data_tests:
          - unique
          - not_null
      - name: status
        data_tests:
          - accepted_values:
              arguments:
                values: ['placed', 'shipped', 'completed', 'returned']
      - name: customer_id
        data_tests:
          - relationships:
              arguments:
                to: ref('dim_customers')
                field: customer_id
```

キー名は`data_tests:`が正式で、`tests:`は互換のために残された別名です。1つのモデルに両方を書くとエラーになります。`arguments:`にはテスト本体へ渡す値、`config:`には後述のseverityやwhereのような動作の設定を書く、という分担です。

### severity・error\_if・warn\_ifで警告と失敗のしきい値を分ける

[severityの設定](https://docs.getdbt.com/reference/resource-configs/severity)は既定が`error`で、`error_if`と`warn_if`はどちらも既定で`!=0`、つまり1行でも失敗すればエラーです。外部から取り込むデータのように多少の欠損を許容する列では、件数でしきい値を分けます。

```
      - name: email
        data_tests:
          - not_null:
              config:
                severity: error
                error_if: ">1000"
                warn_if: ">10"
```

この例では、NULLが10件以下なら合格、11〜1000件なら警告、1000件を超えたらエラーになります。`severity: error`のときは先に`error_if`を評価し、満たさなければ`warn_if`を見る、という順番です。`severity: warn`にすると`error_if`は評価されず、`--warn-error`を付けない限りジョブは止まりません。

### whereで直近のデータだけを検査し、テストの実行時間を抑える

何億行もあるファクトテーブルに毎回`unique`を掛けると、テストだけで数十分かかることがあります。[whereの設定](https://docs.getdbt.com/reference/resource-configs/where)を使うと、検査する行を絞り込めます。

```
      - name: order_id
        data_tests:
          - unique:
              config:
                where: "order_date >= current_date - 7"
```

dbtはテストのSQL内の`{{ model }}`を`(select * from fct_orders where ...) dbt_subquery`という副問い合わせに置き換えて実行します。注意点として、whereはsingular testには効かず無視されます。SQLで書くテストでは、絞り込みの条件を自分でSQLに書いてください。

## singular testとカスタムgeneric testをSQLで書く手順

組み込みの4種で表せない業務ルールは、SQLで書くテストに移します。

### tests直下に書くSQLは失敗行を返し、結果が0行なら合格になる

singular testは、`tests/`フォルダに`select`文を1つだけ書いた`.sql`ファイルです。ファイル名がそのままテスト名になり、`dbt test`で自動的に実行されます。返金を含めても注文ごとの支払合計がマイナスにならない、というルールを検査する例です。スナップショットの現行行がキーごとに1件だけかを検査する例は[dbt snapshotの使い方：timestamp・check戦略でSCD Type2の履歴を作る手順](https://www.issoh.co.jp/tech/details/19613/)で紹介しています。

```
-- tests/assert_order_payment_total_is_not_negative.sql
select
    order_id,
    sum(amount) as total_amount
from {{ ref('fct_payments') }}
group by order_id
having sum(amount) < 0
```

書くときの決まりは3つあります。1つ目は、正しいデータなら0行になるように、失敗する行を返すSQLにすることです。2つ目は、末尾にセミコロンを付けないことで、付けるとテストが誤って失敗する場合があります。3つ目は、このファイルをモデルのYAMLの`data_tests:`から参照しないことです。参照するとエラーになるためです。説明文を付けたい場合は`tests/schema.yml`に`data_tests:`キーで書きます。

### test ブロックで再利用できるgeneric testを自作する

同じ形のsingular testが増えてきたら、[カスタムgeneric test](https://docs.getdbt.com/best-practices/writing-custom-generic-tests)へ切り出します。`tests/generic/`か`macros/`にSQLファイルを置き、`{% test %}`ブロックで定義します。金額列が0以上かを検査する、引数付きのテストの例です。

```
-- tests/generic/test_is_not_negative.sql
{% test is_not_negative(model, column_name, allow_null=false) %}
select *
from {{ model }}
where {{ column_name }} < 0
{% if not allow_null %}
   or {{ column_name }} is null
{% endif %}
{% endtest %}
```

`model`と`column_name`はdbtが自動で渡すので、YAMLに書くのは追加の引数だけです。

```
      - name: amount
        data_tests:
          - is_not_negative:
              arguments:
                allow_null: true
```

テストの既定の動作は、`{% test %}`ブロックの中で`{{ config(severity = 'warn') }}`のように指定できます。利用側のYAMLの`config:`で上書きできるため、普段は警告にしておき、重要な列でだけエラーにする、といった使い分けが可能です。

### dbt\_utils 1.4.1のexpression\_is\_trueと複合キーの一意性

自作する前に、[dbt-utils](https://github.com/dbt-labs/dbt-utils)に同じテストが無いかを確かめます。`expression_is_true`・`unique_combination_of_columns`・`accepted_range`・`recency`など16種のgeneric testが入っており、[dbt Hub](https://hub.getdbt.com/dbt-labs/dbt%5Futils/latest/)で確認した2026年11月時点の最新版は1.4.1、対応するdbtの範囲は1.3.0以上3.0.0未満です。

```
# packages.yml（追記後に dbt deps を実行する）
packages:
  - package: dbt-labs/dbt_utils
    version: 1.4.1

# models/marts/schema.yml
models:
  - name: fct_order_items
    data_tests:
      - dbt_utils.unique_combination_of_columns:
          arguments:
            combination_of_columns: ['order_id', 'line_no']
      - dbt_utils.expression_is_true:
          arguments:
            expression: "quantity * unit_price = line_amount"
```

明細テーブルのように主キーが複数列になるモデルでは、組み込みの`unique`は使えません。`unique_combination_of_columns`をモデル単位に付けるのが定番の書き方です。

## unit testの書き方：given・expectで変換ロジックを固定データで検証

unit testは、モデルのSQLに固定の入力行を与えて、出力が期待どおりかを確かめるテストです。

### unit\_testsのYAMLをmodels配下に置き、refをすべてinputで渡す

注文金額から会員ランクを判定する`dim_customers`のCASE式を検査する例です。境界値の9,999円と10,000円を入れておくと、不等号の向きの誤りをすぐに見つけられます。

```
# models/marts/unit_tests.yml
unit_tests:
  - name: test_customer_rank_boundary
    description: "累計購入額1万円以上をsilver、5万円以上をgoldにする境界を検査する"
    model: dim_customers
    given:
      - input: ref('stg_orders')
        format: dict
        rows:
          - {customer_id: 1, amount: 9999}
          - {customer_id: 2, amount: 10000}
          - {customer_id: 3, amount: 50000}
      - input: ref('stg_customers')
        format: dict
        rows:
          - {customer_id: 1}
          - {customer_id: 2}
          - {customer_id: 3}
    expect:
      format: dict
      rows:
        - {customer_id: 1, customer_rank: 'bronze'}
        - {customer_id: 2, customer_rank: 'silver'}
        - {customer_id: 3, customer_rank: 'gold'}
```

`format`は`dict`・`csv`・`sql`から選べ、`dict`と`csv`では検査に関係する列だけを書けば足ります。YAMLは`tests/`ではなく`models/`の配下に置いてください。`tests/`はデータテスト専用です。

つまずきやすいのは、モデルが参照する`ref`と`source`をすべて`input`に並べる点です。1つでも抜けるとコンパイル時に「node not found」のエラーになります。SQLモデルだけが対象で、materialized view・再帰SQL・イントロスペクティブなクエリを使うモデルには書けません。

### 本番ジョブではunit testを除外：exclude-resource-typeの指定

unit testの入力は固定なので、本番で何度流しても結果は変わらず、DWHの計算資源を使うだけです。公式ドキュメントも、開発環境とCIでだけ実行するよう強く勧めています。本番ジョブでは[resource typeの指定](https://docs.getdbt.com/reference/global-configs/resource-type)で除外します。

```
# 本番ジョブ（1.12系）：unit testを除いてビルドする
dbt build --exclude-resource-type unit_test --target prod

# 環境変数で指定する場合（1.11以降）
export DBT_ENGINE_EXCLUDE_RESOURCE_TYPES=unit_test
dbt build --target prod
```

CI側では逆に`dbt test --select "test_type:unit"`を先に流すと、ロジックの誤りをデータテストより前に検出できます。unit testの終了コードは、失敗した行数にかかわらずテストケース単位で0か1です。

## 失敗の調査とCIへの組み込み：store\_failuresとstate:modified+

テストを書いたあとに必要になるのは、落ちた理由を調べる手段と、毎回流す仕組みです。

### store\_failuresで失敗行をdbt\_test\_\_auditスキーマに残す

テストが落ちたとき、ログに出るのは失敗件数だけです。[store\_failures](https://docs.getdbt.com/reference/resource-configs/store%5Ffailures)を有効にすると、失敗した行がテストと同じ名前のテーブルに保存されます。保存先は既定で`<接続先のスキーマ名>_dbt_test__audit`というスキーマです。

```
# dbt_project.yml：プロジェクト全体で失敗行を残す
data_tests:
  +store_failures: true

# 一度だけ残したいときはフラグで指定する
dbt test --select fct_orders --store-failures
```

保存されたテーブルは、同じテストを次に実行したときに置き換わります。失敗が0件になった場合も置き換わるので、履歴として残したいなら別のテーブルへ写す処理を自分で用意します。開発者ごとのスキーマでは`dbt_test__audit`スキーマの作成権限が無く、「permission denied for schema」で落ちることがあるので、権限の付与も合わせて確認してください。

### GitHub Actionsで変更したモデルのテストだけを回す設定

[stateメソッド](https://docs.getdbt.com/reference/node-selection/methods)を使うと、本番で最後に作った`manifest.json`と比べて、変更のあったモデルとその下流だけを選べます。プルリクエストごとに全モデルをテストする必要はありません。

```
# .github/workflows/dbt-test.yml
name: dbt-test
on: pull_request
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: "3.12"
      - run: pip install "dbt-core~=1.12.0" "dbt-duckdb~=1.11.0"
      - run: dbt deps
      # 本番ジョブが保存した manifest.json を prod-artifacts/ に取得しておく
      - run: dbt test --select "state:modified+,test_type:unit" --state prod-artifacts
      - run: dbt build --select "state:modified+" --exclude "test_type:unit" --state prod-artifacts --target ci
```

1段目でunit testだけを流し、2段目で変更分のモデルを作りながらデータテストを掛けます。`--state`に渡すフォルダは`target/`と分けてください。dbtが解析時に`manifest.json`を上書きし、比較が成り立たなくなります。本番の成果物を保管するジョブの組み方は[データパイプラインの設計判断](https://www.issoh.co.jp/tech/details/15848/)で扱っています。

## テストをどこまで書くかの判断：dbt testで足りる範囲と他の道具

テストは増やすほど安心に見えますが、実行時間と鳴りっぱなしの警告が増えると、誰も結果を見なくなります。

### テストの対象は主キー・結合キー・計算の境界値に絞り、全列には書かない

必ず書くのは、主キーの`unique`と`not_null`、結合に使う外部キーの`relationships`、金額や日付の計算を含むモデルのunit testです。これらが崩れると、下流の集計が気づかれないまま倍になったり欠けたりします。

見送ってよいのは、表示用の名称列や自由記述の列に`not_null`を一律に付けることです。業務上NULLがありうる列に付けると警告が常態化し、本当に止めるべき失敗が埋もれます。stagingとmartのどちらに付けるかは、生データの不備はstagingで、集計ロジックの誤りはmartで捕まえる、と層で分けるのが目安です。層の分け方は[dbtのmarts層と3層の分け方](https://www.issoh.co.jp/tech/details/12323/)を参照してください。

### 行単位でテストを書く範囲と、分布の受け入れ検査をdbtの外に出す場面

dbt testで足りるのは、行単位・件数単位で判定できる検査です。列の型や必須項目を契約として固定し、上流の変更で壊れたらビルドを止めたい場合は、[データ契約とdbtの実装](https://www.issoh.co.jp/tech/details/16109/)で扱うモデル契約の方が向いています。値の分布や外れ値の比率まで見たい場合は、[Great Expectationsの実装手順](https://www.issoh.co.jp/tech/details/16126/)のような専用の道具を検討します。

dbtを入れたもののテストの設計が手つかずで、どの列に何を書くかを決めきれない段階であれば、取り込みからBIまでを通した品質設計として整理する方が早く進みます。一創の[データ分析基盤構築・MLOps構築支援](https://www.issoh.co.jp/service/ai/data-platform/)では、dbtのモデル設計とテスト方針を含めたご相談を受けています。

## よくある質問

dbt testを書き始めるときによく聞かれる質問に答えます。

### dbt testとdbt buildはどちらを使えばよいですか？

本番のジョブには`dbt build`を使ってください。モデルとテストをDAGの順に処理し、テストが落ちたモデルの下流を作らないため、誤った集計が公開される前に止まります。`dbt test`が向くのは、モデルを作り直さずにテストだけを流したい開発中の場面や、CIでunit testだけを先に確かめる場面です。

### tests:とdata\_tests:はどちらで書けばよいですか？

新しく書くなら`data_tests:`です。ユニットテストと区別するためにキー名が変わり、`tests:`は互換のための別名として残っています。1つのモデルに両方を書くとエラーになるので、既存のプロジェクトはファイル単位でまとめて置き換えてください。あわせて引数を`arguments:`の下に移しておくと、v2への移行時にそのまま使えます。

### テストが落ちた行を確認するにはどうすればよいですか？

手早く見るなら、`dbt test --select <テスト名> --store-failures`で失敗行を`dbt_test__audit`スキーマに保存し、そのテーブルをクエリします。`target/compiled/`配下にはテストのSQLが出力されているので、それをDWHで直接実行して確かめる方法もあります。

### dbt testの実行が遅いときはどうすればよいですか？

大きなファクトテーブルのテストに`where`を付けて直近の期間だけを検査し、CIでは`state:modified+`で変更分に絞ります。unit testを本番から除外するだけでも、本番ジョブの実行時間を短くすることが可能です。v2ではバッチテストを有効にすると、同じモデルの`unique`と`not_null`を種類ごとに1本のクエリへまとめられます。v2そのものの違いは[dbt Fusionとは](https://www.issoh.co.jp/tech/details/19593/)で解説しました。

### テストの内容をドキュメントとして共有できますか？

ドキュメントとして共有可能です。YAMLに書いたテストは、`dbt docs generate`で作るドキュメントのモデルのページに一覧で表示されます。singular testも`tests/schema.yml`に説明を書いておけば表示の対象です。社内への公開手順は[dbt docsの使い方](https://www.issoh.co.jp/tech/details/19599/)にまとめています。

## 関連記事

- [dbtとは？ELTのTを担う仕組みとdbt Core・Cloudの選び方【2026年版】](https://www.issoh.co.jp/column/details/16548/)：dbtの仕組みと採用判断
- [dbt Coreの使い方：インストールからprofiles.yml・dbt build・CIまでの手順【1.12対応】](https://www.issoh.co.jp/tech/details/19592/)：テストを書く前の環境構築
- [データ契約（Data Contract）とは？ODCSとdbtでスキーマ破壊を止める実装を解説【2026年版】](https://www.issoh.co.jp/tech/details/16109/)：スキーマを契約で固定する方法
- [dbtのmarts層とは？3層の分け方とMetricFlowで指標を定義する手順【dbt 1.12対応】](https://www.issoh.co.jp/tech/details/12323/)：テストを付ける層の設計
- [Great Expectationsとは？GX Core 1.xの部品構成・実装手順と採用判断を解説【2026年版】](https://www.issoh.co.jp/tech/details/16126/)：分布まで見る品質検査

---

出典: [dbt testの書き方：generic・singular・unit testの使い分けとCIへの組み込み【1.12対応】](<https://www.issoh.co.jp/tech/details/19611/>)（株式会社一創）
