はじめに:テストは大事だが、書くのが面倒

データ変換のテストが大事だということに、反対する人はほとんどいないと思います。集計の途中でNULLが混ざっていたり、本来ユニークなはずのキーが重複していたりすると、その先のレポートやダッシュボードはすべて静かに狂っていきます。しかも、こういう不具合は数字がそれっぽく出てしまうぶん、気づくのが遅れがちです。

それでも、テストはなかなか書かれません。理由はシンプルで、面倒だからです。分析の本筋はSQLを書いて数字を出すことであって、「この列は本当にNULLでないか」を確認するコードを別に用意するのは、どうしても後回しになります。中小ECの現場で一人でデータ基盤を回していると、なおさらそうです。

この記事では、dbtの標準的なテスト機能を確認したうえで、Claude Codeを使ってモデルからテストコードを下書きする流れを紹介します。「面倒だから書かない」を「とりあえず下書きはある」に変えるのが狙いです。

なお、dbtでデータパイプラインを組む話はdbt × BigQueryで再現可能なデータパイプラインを構築する入門で、テストという考え方そのものはBigQueryでデータ品質をSQLテストで守るで扱っています。あわせて読むと、この記事の位置づけがわかりやすくなります。

dbtのテストはどんなものか

dbtのテストは、大きく分けて2種類あります。スキーマに紐づく汎用テスト(generic test)と、個別のSQLで書く単体テスト(singular test)です。

schema.ymlに書く汎用テスト

いちばんよく使うのが、schema.yml(モデルの定義ファイル)に書く汎用テストです。代表的なものが4つあります。

  • not_null:その列にNULLが含まれていないこと
  • unique:その列の値が重複していないこと
  • accepted_values:その列が決められた値の集合に収まっていること
  • relationships:別テーブルのキーと参照整合性が取れていること

たとえば、注文を1行1注文にまとめたstg_ordersというモデルがあるとします。これに対するテストは、次のように書きます。

version: 2

models:
  - name: stg_orders
    description: "注文を1行1注文に整形したステージングモデル"
    columns:
      - name: order_id
        description: "注文ID(主キー)"
        tests:
          - not_null
          - unique
      - name: order_status
        description: "注文ステータス"
        tests:
          - accepted_values:
              values: ["pending", "paid", "shipped", "cancelled"]
      - name: customer_id
        description: "顧客ID"
        tests:
          - not_null
          - relationships:
              to: ref('stg_customers')
              field: customer_id

このYAMLが意味するところはとても素直です。「order_idはNULLでなく重複もない主キー」「order_statusは4つの値のどれか」「customer_idはNULLでなく、stg_customersに存在する顧客を指している」。これだけで、注文データの素性をかなり守れます。

書いたら、ターミナルでテストを実行します。

dbt test --select stg_orders

accepted_valuesに想定外のステータスが混ざっていたり、order_idに重複があったりすると、その時点でテストが失敗して教えてくれます。

singular testで個別のロジックを検証する

汎用テストでカバーしきれない、業務固有のルールもあります。たとえば「割引後の金額は、元の金額を超えてはいけない」のような条件です。こういうときは、tests/ディレクトリにSQLファイルを置く単体テスト(singular test)を使います。

dbtの単体テストは、「失敗する行を返すSQL」を書くという発想です。1行も返ってこなければ合格、何行か返ってくれば、その行が違反しているということになります。

-- tests/assert_discount_not_exceed_gross.sql
-- 割引後金額が元金額を上回っている行を返す(0行なら合格)
select
    order_id,
    gross_amount,
    net_amount
from {{ ref('stg_orders') }}
where net_amount > gross_amount

この考え方に慣れると、「おかしい状態を一つのSQLで言い表す」だけでテストになるので、複雑なビジネスルールも素直に書けます。

Claude Codeでモデルからテストを自動生成する

ここからが本題です。テストの書き方そのものは難しくありませんが、すべてのモデルに対して列ごとに考えるのは、やはり骨が折れます。そこをClaude Codeに下書きさせます。

Claude Codeはターミナルで動くので、dbtプロジェクトのディレクトリでそのまま起動できます。モデルのSQLファイルを読ませて、テストの叩き台を出してもらう、という流れです。

まずモデルを読ませて意図を伝える

たとえばmodels/staging/stg_orders.sqlがあるとして、次のように指示します。

models/staging/stg_orders.sql を読んでください。
このモデルに対する schema.yml のテストを下書きしてほしいです。
- 主キーらしき列には not_null と unique を付ける
- ステータス列など値が限られそうな列は accepted_values の候補を挙げる
- 他テーブルを参照していそうな列は relationships の候補を挙げる
確証が持てない箇所は、断定せずコメントで「要確認」と書いてください。

Claude CodeはSQLの中身(selectしている列、joinの相手、case式で出している区分など)を読み取って、それらしいschema.ymlを提案してくれます。先ほどのstg_ordersの例のようなYAMLが、ほぼそのまま出てくることもあります。

業務ルールはsingular testとして相談する

汎用テストだけでなく、業務固有のチェックも相談できます。

このモデルで成り立っているべき業務ルールを3つ挙げて、
それぞれを dbt の singular test(失敗する行を返すSQL)として
tests/ ディレクトリ用に書いてください。

「金額は負にならない」「キャンセル注文に出荷日が入っていない」といったルールを、SQLの形まで落として提案してくれます。自分では見落としていたチェック観点に気づけることもあり、レビューのきっかけとしても役立ちます。

生成されたテストは、必ず人がレビューする

ここはこの記事でいちばん強調したいところです。Claude Codeが出すテストは、あくまで下書きです。そのまま信じてdbt testを緑にすることが目的ではありません。

特に次の点は、人が必ず確認してください。

  • accepted_valuesの値が本当に正しいか(実データには想定外の区分が紛れていることがあります)
  • uniqueを付けた列が、本当にユニークであるべきか(実は重複が正常なケースもあります)
  • relationshipsの参照先が、業務的に正しい関係か
  • 生成されたsingular testが、「正しい状態」ではなく「自分のSQLの実装」をなぞっただけになっていないか

最後の点は要注意です。モデルのSQLをそのまま材料にしてテストを作ると、「実装が正しい前提で、実装どおりかを確認するだけ」のテストになりがちです。これだと、実装の間違いはすり抜けてしまいます。テストは「あるべき状態」を表すべきで、その判断は人の仕事です。

生成物をいったん疑い、実データに対して走らせて挙動を見て、おかしな失敗・おかしな合格がないかを確かめる。この一手間を省かないことが、自動生成と付き合ううえでの肝になります。

運用に乗せる

下書きを人がレビューして取り込んだら、あとは普段の開発フローに組み込みます。難しいことはありません。

  • dbt build(実行とテストをまとめて行うコマンド)を日々のパイプラインに入れる
  • モデルを直したら、関連するテストも見直す習慣をつける
  • 新しいモデルを足したときは、まずClaude Codeに下書きを作らせ、人がレビューしてから採用する

こうしておくと、テストが「特別な作業」ではなく、モデルとセットで増えていく当たり前のものになります。テストの初稿のハードルが下がるぶん、「とりあえず書いておくか」が現実的な選択肢になるのが、この組み合わせのいちばんの効きどころです。

まとめ

テストが大事なのはわかっていても、ゼロから書くのは面倒で、後回しになりがちです。dbtにはnot_nulluniquerelationshipsaccepted_valuesといった汎用テストと、SQLで自由に書けるsingular testがそろっていて、データ変換の素性を守る土台は十分にあります。

そこにClaude Codeを足すと、モデルからテストの下書きを起こす部分を肩代わりさせられます。ただし生成物は下書きであり、accepted_valuesの値やuniqueの前提、singular testが実装をなぞっただけになっていないかは、人が必ずレビューしてください。

「面倒だから書かない」を「下書きはあるからレビューして取り込む」に変える。地味ですが、データ品質を継続的に守るうえで効いてくる進め方です。dbtのパイプライン自体の組み方はdbt × BigQueryで再現可能なデータパイプラインを構築する入門を、テストという考え方の基礎はBigQueryでデータ品質をSQLテストで守るを参照してみてください。