ソフトウェアエンジニアリングの世界では、自動化されたテストを使ってコードの振る舞いを検証するのが当たり前になっている。 同じように、データエンジニアリングの世界でも、自動化されたテストを使ってデータの振る舞いを検証するのが望ましい。
データをテストするのに使える OSS のフレームワークも、いくつか存在する。 今回は、その中でも dbt (data build tool) を使ってデータをテストする方法について見ていく。 dbt 自体はデータのテストを主目的としたツールではないものの、テストに関する機能も備えている。
また、dbt には WebUI を備えたマネージドサービスとしての dbt Cloud と、CLI で操作するスタンドアロン版の dbt Core がある。 今回扱うのは後者の dbt Core になる。
使った環境は次のとおり。
$ sw_vers ProductName: macOS ProductVersion: 12.2 BuildVersion: 21D49 $ uname -srm Darwin 21.3.0 arm64 $ python -V Python 3.9.10 $ pip list | grep dbt dbt-core 1.0.1 dbt-extractor 0.4.0 dbt-postgres 1.0.1
もくじ
- もくじ
- 下準備
- dbt からデータベースに接続する
- source freshness をテストする
- generic test を使ってテストを書く
- テスト用マクロの入ったパッケージを利用する
- custom generic test を定義する (引数なし)
- custom generic test を定義する (引数あり)
- singular test を使ってテストを書く
- まとめ
- 参考
下準備
今回は、公開されているデータセットをローカルの PostgreSQL に取り込んで、それを dbt でテストしていく。
PostgreSQL をセットアップする
まずは Homebrew を使って PostgreSQL をインストールしよう。 また、データセットをダウンロードするために wget も入れておく。
$ brew install postgresql wget
PostgreSQL のサービスを起動する。
$ brew services start postgresql
brew services list
Name Status User File
postgresql started amedama ~/Library/LaunchAgents/homebrew.mxcl.postgresql.plist
データセットとしては seaborn が利用している taxis データセットを使う。
ダウンロードして /tmp
に保存する。
$ wget https://raw.githubusercontent.com/mwaskom/seaborn-data/master/taxis.csv -P /tmp
上記のデータセットに合う形でテーブルの定義を作る。
$ cat << 'EOF' | psql -d postgres CREATE TABLE IF NOT EXISTS public.taxis ( id SERIAL NOT NULL, pickup TIMESTAMP NOT NULL, dropoff TIMESTAMP NOT NULL, passengers INT NOT NULL, distance FLOAT NOT NULL, fare FLOAT NOT NULL, tip FLOAT NOT NULL, tolls FLOAT NOT NULL, total FLOAT NOT NULL, color TEXT NOT NULL, payment TEXT, pickup_zone TEXT, dropoff_zone TEXT, pickup_borough TEXT, dropoff_borough TEXT ); EOF
先ほどダウンロードした CSV ファイルの内容を上記のテーブルに取り込む。
$ cat << 'EOF' | psql -d postgres COPY public.taxis ( pickup, dropoff, passengers, distance, fare, tip, tolls, total, color, payment, pickup_zone, dropoff_zone, pickup_borough, dropoff_borough ) FROM '/tmp/taxis.csv' WITH ( FORMAT csv, HEADER true ) EOF
また、データベースを操作するためのユーザ (ROLE) を alice
という名前で追加しておく。
$ cat << 'EOF' | psql -d postgres CREATE ROLE alice WITH LOGIN PASSWORD 'wonderland' EOF
ちなみに、ユーザにパスワードは設定してるけど、実は無くても問題はない。
Homebrew でインストールした場合、デフォルトでローカルからの接続が trust
になっているため。
パスワードなしでも接続できる。
$ cat /opt/homebrew/var/postgres/pg_hba.conf | sed -e "/^#/d" -e "/^$/d" local all all trust host all all 127.0.0.1/32 trust host all all ::1/128 trust local replication all trust host replication all 127.0.0.1/32 trust host replication all ::1/128 trust
追加したユーザにテーブルを操作する権限をつけておく。
$ cat << 'EOF' | psql -d postgres GRANT ALL PRIVILEGES ON TABLE public.taxis TO alice EOF
次のように、ユーザとパスワードを指定してデータを見られることを確認しておく。
$ echo "SELECT * FROM public.taxis LIMIT 5" | psql -d postgres --user alice --password Password: id | pickup | dropoff | passengers | distance | fare | tip | tolls | total | color | payment | pickup_zone | dropoff_zone | pickup_borough | dropoff_borough ----+---------------------+---------------------+------------+----------+------+------+-------+-------+--------+-------------+-----------------------+-----------------------+----------------+----------------- 1 | 2019-03-23 20:21:09 | 2019-03-23 20:27:24 | 1 | 1.6 | 7 | 2.15 | 0 | 12.95 | yellow | credit card | Lenox Hill West | UN/Turtle Bay South | Manhattan | Manhattan 2 | 2019-03-04 16:11:55 | 2019-03-04 16:19:00 | 1 | 0.79 | 5 | 0 | 0 | 9.3 | yellow | cash | Upper West Side South | Upper West Side South | Manhattan | Manhattan 3 | 2019-03-27 17:53:01 | 2019-03-27 18:00:25 | 1 | 1.37 | 7.5 | 2.36 | 0 | 14.16 | yellow | credit card | Alphabet City | West Village | Manhattan | Manhattan 4 | 2019-03-10 01:23:59 | 2019-03-10 01:49:51 | 1 | 7.7 | 27 | 6.15 | 0 | 36.95 | yellow | credit card | Hudson Sq | Yorkville West | Manhattan | Manhattan 5 | 2019-03-30 13:27:42 | 2019-03-30 13:37:14 | 3 | 2.16 | 9 | 1.1 | 0 | 13.4 | yellow | credit card | Midtown East | Yorkville West | Manhattan | Manhattan (5 rows)
これでデータベースの準備は整った。
dbt をインストールする
続いては肝心の dbt をインストールする。 dbt は Python で開発されているので、pip を使ってインストールできる。
dbt では、接続するデータベースごとにアダプタと呼ばれるパッケージを切り替えて対応する。
たとえば PostgreSQL なら dbt-postgres
というアダプタを使えば良い。
これが、もしバックエンドに BigQuery を使うなら dbt-bigquery
を使うことになる。
いずれも、依存関係に本体の dbt-core
が入っているので一緒にインストールできる。
$ pip install dbt-postgres
インストールすると dbt
コマンドが使えるようになる。
$ dbt --version installed version: 1.0.1 latest version: 1.0.1 Up to date! Plugins: - postgres: 1.0.1
これで必要な下準備がすべて整った。
dbt からデータベースに接続する
まずは dbt からデータベースに接続する部分を確認していく。
dbt では dbt_project.yml
という設定ファイルが必要になるので、まずは作る。
name
はプロジェクト名、version
はプロジェクトのバージョン番号、config-version
は YAML のコンフィグ形式のバージョン番号を表している。
profile
というのは、dbt がデータベースに接続するやり方のことをプロファイルと呼んでいて、それにつけた名前のこと。
$ cat << 'EOF' >> dbt_project.yml name: taxis version: 0.0.1 config-version: 2 profile: postgres_taxis EOF
ちなみに dbt init
コマンドを使えば一通りの設定ファイルとディレクトリ構造の入ったボイラープレートを展開することもできる。
ここらへんは好みで。
$ dbt init
次は、上記の設定ファイルで指定した postgres_taxis
という名前のプロファイルを用意しよう。
データベースへの接続方法は、パスワードなど秘匿しておきたい情報も多い。
そのため、デフォルトではプロジェクトのディレクトリとは分離して、ホームディレクトリ以下を読むことになっている 1。
~/.dbt
というディレクトリに profiles.yml
という名前で YAML ファイルを作る。
そして、postgres_taxis
というプロファイルを定義する。
その下にある target
では、デフォルトで使う接続先の環境を指定している。
これは、同じ用途のデータベースであっても、一般的には役割によって複数の環境を用意することになるため。
たとえば、本番 (prod)、ステージング (stg)、開発 (dev) といったように。
その中で、デフォルトで使用するものを指定している。
なお、接続先はコマンドラインオプションの --target
で切り替えることができる。
target
に指定した名前は、outputs
以下にある設定と対応する。
ここでは local
という名前で設定した。
type
に postgres
を指定することで、アダプタとして dbt-postgres
の実装が使われることになる。
$ mkdir -p ~/.dbt $ cat << 'EOF' > ~/.dbt/profiles.yml postgres_taxis: target: local outputs: local: type: postgres threads: 1 host: localhost port: 5432 user: alice pass: wonderland dbname: postgres schema: dbt_alice EOF
ちなみに、PostgreSQL ではテーブルの階層構造が <database>.<schema>.<table>
という 3 層構造になっている。
上記で dbname
や schema
を指定しているのは、このため。
ただし、上記で指定している schema
は、このプロファイルで使う作業用のスキーマの名前になっている。
つまり、アクセスできるスキーマがこれに限られるわけではない。
さて、プロファイルができたら、まずはデータベースへの接続が上手くいくことを確認しよう。
これには dbt debug
コマンドを使う。
$ dbt debug 15:14:54 Running with dbt=1.0.1 dbt version: 1.0.1 python version: 3.9.10 python path: /Users/amedama/.virtualenvs/py39/bin/python os info: macOS-12.2-arm64-arm-64bit Using profiles.yml file at /Users/amedama/.dbt/profiles.yml Using dbt_project.yml file at /Users/amedama/Documents/temporary/dbt-example/dbt_project.yml Configuration: profiles.yml file [OK found and valid] dbt_project.yml file [OK found and valid] Required dependencies: - git [OK found] Connection: host: localhost port: 5432 user: alice database: postgres schema: dbt_alice search_path: None keepalives_idle: 0 sslmode: None Connection test: [OK connection ok] All checks passed!
どうやら、無事に接続できたようだ。 データベースに接続できることを確認するのも、ある意味で健全性のテストと言えるかもしれない。
source freshness をテストする
さて、データベースに接続できることが分かった。 次の一手としては、すでにデータベースに取り込まれているテーブルをソース (source) として定義する。 ソースを定義しておくと、別の場所から参照できたり、それに対してテストが書けたりする。
テストの観点は色々とあるけど、まずは source freshness を確認してみよう。 これは、ソースの特定のカラムに含まれる最新のタイムスタンプが、現在時刻からどれくらい離れているかを検証するもの。 たとえば DWH へのデータの取り込みが何らかの理由で遅延したり、あるいは停止しているのを見つけるのに利用できる。
ソースを定義するには models
というディレクトリを作って、そこに YAML の設定ファイルを追加する。
さっきも似たような作業があったと思うけど、基本的に dbt はユーザから見える部分のほとんどが YAML と SQL で成り立っている。
sources
以下に name
でスキーマを指定して、その下に tables
でテーブルを指定する。
以下であれば postgres.public.taxis
という階層構造のテーブルを定義していることになる。
そして、その下にある freshness
と loaded_at_field
という項目で source freshness の設定をする。
$ mkdir -p models $ cat << 'EOF' > models/taxis.yml version: 2 sources: - name: public tables: - name: taxis freshness: warn_after: count: 1 period: hour error_after: count: 1 period: day loaded_at_field: pickup::timestamp EOF
上記の設定では、pickup
というカラムの最新の時刻が現在時刻から 1h 以上離れると警告、1d 以上離れるとエラーになる。
カラムの時刻は UTC を基準にする点に注意が必要。
つまり、JST を使っている場合には、UTC に変換する必要がある 2。
設定できたら dbt source freshness
コマンドを実行しよう。
これで、ソースに含まれる最新のタイムスタンプと現在時刻が比較される。
$ dbt source freshness 15:16:57 Running with dbt=1.0.1 15:16:57 Partial parse save file not found. Starting full parse. 15:16:57 Found 0 models, 0 tests, 0 snapshots, 0 analyses, 165 macros, 0 operations, 0 seed files, 1 source, 0 exposures, 0 metrics 15:16:57 15:16:57 Concurrency: 1 threads (target='local') 15:16:57 15:16:57 1 of 1 START freshness of public.taxis.......................................... [RUN] 15:16:57 1 of 1 ERROR STALE freshness of public.taxis.................................... [ERROR STALE in 0.02s] 15:16:57 15:16:57 Done.
当たり前だけど、実行は失敗する。 サンプルデータのタイムスタンプは、最新のレコードでも 2019 年になっている。 1h とか 1d なんて単位ではない離れ方をしている。
$ echo "SELECT MAX(pickup), NOW() FROM public.taxis" | psql -d postgres max | now ---------------------+------------------------------- 2019-03-31 23:43:45 | 2022-02-04 00:26:35.521179+09 (1 row)
試しに現在時刻との乖離が小さなデータを追加してみよう。
時刻の計算に GNU date を使いたいので Homebrew を使って coreutils
をインストールする。
$ brew install coreutils
pickup
が現在時刻から 2h 前のレコードを追加する。
これなら実行したときエラーではなく警告になるはずだ。
他のカラムについては適当な値で埋めた。
$ PICKUP=$(TZ=UTC gdate "+%Y-%m-%d %H:%M:%S" --date '2 hour ago') $ DROPOFF=$(TZ=UTC gdate "+%Y-%m-%d %H:%M:%S" --date '1 hour ago') $ cat << EOF | psql -d postgres INSERT INTO taxis ( pickup, dropoff, passengers, distance, fare, tip, tolls, total, color, payment, pickup_zone, dropoff_zone, pickup_borough, dropoff_borough ) VALUES ( '${PICKUP}', '${DROPOFF}', 1, 1.5, 7, 2.0, 0, 12.5, 'yellow', 'credit card', 'Lenox Hill West', 'UN/Turtle Bay South', 'Manhattan', 'Manhattan' ); EOF
先ほどと同じコマンドを実行すると、今度はたしかに警告 (WARN) に変わっている。
$ dbt source freshness 15:31:35 Running with dbt=1.0.1 15:31:35 Found 0 models, 0 tests, 0 snapshots, 0 analyses, 165 macros, 0 operations, 0 seed files, 1 source, 0 exposures, 0 metrics 15:31:35 15:31:35 Concurrency: 1 threads (target='local') 15:31:35 15:31:35 1 of 1 START freshness of public.taxis.......................................... [RUN] 15:31:35 1 of 1 WARN freshness of public.taxis........................................... [WARN in 0.01s] 15:31:35 Done.
同じ要領で pickup
と現在時刻の差が 1h 未満のデータを入れてみよう。
$ PICKUP=$(TZ=UTC gdate "+%Y-%m-%d %H:%M:%S" --date '15 min ago') $ DROPOFF=$(TZ=UTC gdate "+%Y-%m-%d %H:%M:%S" --date '10 min ago') $ cat << EOF | psql -d postgres INSERT INTO taxis ( pickup, dropoff, passengers, distance, fare, tip, tolls, total, color, payment, pickup_zone, dropoff_zone, pickup_borough, dropoff_borough ) VALUES ( '${PICKUP}', '${DROPOFF}', 3, 2.16, 9, 1.1, 0, 13.4, 'yellow', 'cash', 'Midtown East', 'Yorkville West', 'Manhattan', 'Manhattan' ); EOF
今度は実行が成功 (PASS) した。
$ dbt source freshness 15:33:36 Running with dbt=1.0.1 15:33:36 Found 0 models, 0 tests, 0 snapshots, 0 analyses, 165 macros, 0 operations, 0 seed files, 1 source, 0 exposures, 0 metrics 15:33:36 15:33:36 Concurrency: 1 threads (target='local') 15:33:36 15:33:36 1 of 1 START freshness of public.taxis.......................................... [RUN] 15:33:36 1 of 1 PASS freshness of public.taxis........................................... [PASS in 0.01s] 15:33:36 Done.
これで source freshness の確認ができるようになった。
generic test を使ってテストを書く
さて、続いてはデータの中身を見るテストを書いていこう。 dbt を使ってテストを書くやり方には generic test と singular test の 2 つがある。 まずは、より汎用性の高い generic test から見ていこう。
generic test を使うには YAML の設定を追加するだけで良い。
次の設定では、ソースの taxis
テーブルに含まれるいくつかのカラムに対してテストを用意している。
それぞれの名前から内容はなんとなく分かるはずだけど、念の為に書いておくと次のようなルールになっている。
id
カラムは一意で NULL の値がないことpickup
カラムは NULL の値がないことcolor
カラムはyellow
とgreen
の値だけあること
$ cat << 'EOF' > models/taxis.yml version: 2 sources: - name: public tables: - name: taxis columns: - name: id tests: - unique - not_null - name: pickup tests: - not_null - name: color tests: - accepted_values: values: ['yellow', 'green'] EOF
なお、先ほど確認した source freshness の設定は、簡単のために上記の設定ファイルからは省いた。
設定ファイルを作ったら dbt test
コマンドでテストを実行する。
$ dbt test 09:38:01 Running with dbt=1.0.1 09:38:01 Found 0 models, 4 tests, 0 snapshots, 0 analyses, 165 macros, 0 operations, 0 seed files, 1 source, 0 exposures, 0 metrics 09:38:01 09:38:01 Concurrency: 1 threads (target='local') 09:38:01 09:38:01 1 of 4 START test source_accepted_values_public_taxis_color__yellow__green...... [RUN] 09:38:01 1 of 4 PASS source_accepted_values_public_taxis_color__yellow__green............ [PASS in 0.03s] 09:38:01 2 of 4 START test source_not_null_public_taxis_id............................... [RUN] 09:38:01 2 of 4 PASS source_not_null_public_taxis_id..................................... [PASS in 0.01s] 09:38:01 3 of 4 START test source_not_null_public_taxis_pickup........................... [RUN] 09:38:01 3 of 4 PASS source_not_null_public_taxis_pickup................................. [PASS in 0.03s] 09:38:01 4 of 4 START test source_unique_public_taxis_id................................. [RUN] 09:38:01 4 of 4 PASS source_unique_public_taxis_id....................................... [PASS in 0.02s] 09:38:01 09:38:01 Finished running 4 tests in 0.19s. 09:38:01 09:38:01 Completed successfully 09:38:01 09:38:01 Done. PASS=4 WARN=0 ERROR=0 SKIP=0 TOTAL=4
テストが実行されて成功 (PASS) した。
ちなみに、それぞれのテストケースは、いずれも SQL を使って実現されている。
先ほどの実行で、どのような SQL が発行されているかは、デフォルトで logs
ディレクトリに生成されるログを読むと分かる。
テストは原則として「失敗するときに一致するレコードが出る SQL」になっている。
つまり、先ほどテストが成功したということは「発行した SQL に一致するレコードがなかった」ことを意味する。
また、上記ではソースに対してテストを書いたけど、モデルなど dbt に登場するその他のオブジェクトに対しても同じ要領でテストが書ける。 ただし、今回は簡単のためにソースに対してだけテストを書いていく。
テスト用マクロの入ったパッケージを利用する
先ほどはカラムの値が一意か NULL がないかといったテストを書いた。 ただ、dbt Core が組み込みで用意しているテスト用のマクロは多くない。 早々に「こういうテストが書きたいのに!」という場面が出てくる。 そんなときは、お目当てのテストに使えるマクロが入ったパッケージがないか探してみるのが良い。 dbt には dbt Hub というリポジトリに登録されているパッケージをインストールする仕組みがある。
たとえば dbt-utils というパッケージをインストールしてみよう。
このパッケージはテスト専用ではないものの、テストに使えるマクロがいくつか用意されている。
インストールしたいパッケージは packages.yml
という名前の設定ファイルに書く。
$ cat << 'EOF' > packages.yml packages: - package: dbt-labs/dbt_utils version: 0.8.0 EOF
バージョンの指定は必須なので dbt Hub を見て記述する。
設定ファイルの用意ができたら dbt deps
コマンドを実行する。
$ dbt deps
すると、デフォルトで dbt_packages
というディレクトリにパッケージがインストールされる。
$ ls dbt_packages
dbt_utils
このディレクトリには、パッケージに対応するリポジトリの内容がそのままダウンロードされている。 実にシンプルな仕組み。
$ ls dbt_packages/dbt_utils
CHANGELOG.md README.md dbt_project.yml etc macros
LICENSE RELEASE.md docker-compose.yml integration_tests run_test.sh
パッケージをダウンロードできたら、実際に dbt-utils に含まれるテスト用のマクロを使ってみよう。
たとえば accepted_range
というマクロを使うとカラムが取りうる値の範囲を指定できる。
以下では tip
カラムの値が 0.0 ~ 33.2
になることを確認している。
$ cat << 'EOF' > models/taxis.yml version: 2 sources: - name: public tables: - name: taxis columns: - name: tip tests: - dbt_utils.accepted_range: min_value: 0.0 max_value: 33.2 inclusive: true EOF
次のとおり tip
カラムの値は 0.0 ~ 33.2
になっている。
$ echo "SELECT MAX(tip) AS max_tip, MIN(tip) AS min_tip FROM public.taxis" | psql -d postgres max_tip | min_tip ---------+--------- 33.2 | 0 (1 row)
テストを実行してみよう。
$ dbt test 14:56:08 Running with dbt=1.0.1 14:56:08 Unable to do partial parsing because a project dependency has been added 14:56:09 Found 0 models, 1 test, 0 snapshots, 0 analyses, 352 macros, 0 operations, 0 seed files, 1 source, 0 exposures, 0 metrics 14:56:09 14:56:09 Concurrency: 1 threads (target='local') 14:56:09 14:56:09 1 of 1 START test dbt_utils_source_accepted_range_public_taxis_tip__True__33_2__0_0 [RUN] 14:56:09 1 of 1 PASS dbt_utils_source_accepted_range_public_taxis_tip__True__33_2__0_0... [PASS in 0.03s] 14:56:09 14:56:09 Finished running 1 test in 0.08s. 14:56:09 14:56:09 Completed successfully 14:56:09 14:56:09 Done. PASS=1 WARN=0 ERROR=0 SKIP=0 TOTAL=1
ちゃんと成功した。
ちなみに、テスト用のマクロが色々と入っているパッケージとしては dbt-expectations というのがある。
custom generic test を定義する (引数なし)
探しても使えそうなマクロが見つからないときは、独自の generic test を自分で書くこともできる。 公式のドキュメントでは custom generic test と呼んでいる。
試しに、カラムの値が正の値かをテストするマクロを定義してみよう。 前述したとおり、書くのは「失敗するときに一致するレコードが出る SQL」となる。 ただし、純粋な SQL ではなくて Jinja2 というテンプレートエンジンの構文を使って書いていく。
custom generic test を定義するには、tests/generic
ディレクトリ以下に SQL を記述する。
以下では is_positive
という名前で定義しており、カラムの値が 0
未満のレコードを抽出する。
$ mkdir -p tests/generic $ cat << 'EOF' > tests/generic/is_positive.sql {% test is_positive(model, column_name) %} select * from {{ model }} where {{ column_name }} < 0 {% endtest %} EOF
上記のテストを使ってみよう。
passengers
カラムの内容をチェックする。
$ cat << 'EOF' > models/taxis.yml version: 2 sources: - name: public tables: - name: taxis columns: - name: passengers tests: - is_positive EOF
$ dbt test
custom generic test を定義する (引数あり)
先ほどの custom generic test には、追加の引数がなかった。 次は追加の引数があるものを定義してみよう。
以下では、特定の値よりも大きな値がカラムに含まれないことを確認する custom generic test を定義している。
引数にはしきい値を表す value
と、境界値を含むかを表したフラグの inclusive
をつけている。
$ cat << 'EOF' > tests/generic/max.sql {% test max(model, column_name, value, inclusive=true) %} select * from {{ model }} where {{ column_name }} > {{- "=" if inclusive }} {{ value }} {% endtest %} EOF
上記を使ってみよう。
以下では passengers
の最大値が 6
であることを確認している。
なお、inclusive
のデフォルト値は false
で上書きしている。
$ cat << 'EOF' > models/taxis.yml version: 2 sources: - name: public tables: - name: taxis columns: - name: passengers tests: - max: value: 6 inclusive: false EOF
テストを実行すると成功する。
$ dbt test 15:27:22 Running with dbt=1.0.1 15:27:22 Found 0 models, 1 test, 0 snapshots, 0 analyses, 354 macros, 0 operations, 0 seed files, 1 source, 0 exposures, 0 metrics 15:27:22 15:27:22 Concurrency: 1 threads (target='local') 15:27:22 15:27:22 1 of 1 START test source_max_public_taxis_passengers__False__6.................. [RUN] 15:27:22 1 of 1 PASS source_max_public_taxis_passengers__False__6........................ [PASS in 0.02s] 15:27:22 15:27:22 Finished running 1 test in 0.06s. 15:27:22 15:27:22 Completed successfully 15:27:22 15:27:22 Done. PASS=1 WARN=0 ERROR=0 SKIP=0 TOTAL=1
ちなみに inclusive
オプションを true
にしたり、あるいは削ってデフォルト値にするとテストは失敗する。
これは passengers
カラムの最大値が 6
のため。
$ echo "SELECT MAX(passengers) FROM public.taxis" | psql -d postgres max ----- 6 (1 row)
singular test を使ってテストを書く
generic test は汎用的なテストをマクロとして定義した上で、それを色々な場所から YAML の設定で使うものだった。 一方で、特定の用途に特化したテストを書きたいときもあるはず。 そんなときは単発の SQL を実行するだけの singular test を使うと良い。
singular test を書くときは tests
ディレクトリ以下に、直接 SQL のファイルを記述する。
以下では postgres.public.taxis
の passengers
カラムに正の値しかないことを確認するテストを書いている。
ようするに、先ほど引数なしの custom generic test で検証した内容をベタ書きしているだけ。
$ cat << 'EOF' > tests/taxis_passengers_positive.sql select * from {# sources に定義してあるテーブルの名前を取得できる #} {{ source('public', 'taxis') }} where passengers < 0 EOF
純粋に singular test の結果だけ見たいので、先ほどの custom generic test は設定ファイルから削る。
$ cat << 'EOF' > models/taxis.yml version: 2 sources: - name: public tables: - name: taxis EOF
テストを実行してみよう。
$ dbt test 15:36:50 Running with dbt=1.0.1 15:36:50 Found 0 models, 1 test, 0 snapshots, 0 analyses, 354 macros, 0 operations, 0 seed files, 1 source, 0 exposures, 0 metrics 15:36:50 15:36:50 Concurrency: 1 threads (target='local') 15:36:50 15:36:50 1 of 1 START test taxis_passengers_positive..................................... [RUN] 15:36:50 1 of 1 PASS taxis_passengers_positive........................................... [PASS in 0.02s] 15:36:50 15:36:50 Finished running 1 test in 0.06s. 15:36:50 15:36:50 Completed successfully 15:36:50 15:36:50 Done. PASS=1 WARN=0 ERROR=0 SKIP=0 TOTAL=1
ちゃんと実行されて成功したことがわかる。
まとめ
今回は dbt を使ってデータをテストする方法について書いた。