シードデータ
空のスキーマは動きはしますが、何も見せてくれません。マイグレーション直後の データベースに対してアプリケーションを開けば、一覧はどれも空、詳細ページはどれも 404 です。そして誰もが最初にやるのは、手でデータを入れることです —— マシンごとに 違う内容で。
シードデータとは、その行を書き下したものです。データセットごとに1ファイル、 リポジトリの中に置き、コマンド1つで適用します。
| 用語 | 意味 |
|---|---|
| シードデータ | スキーマとは別に、データベースを使える状態にするために投入する行 |
| データセット | テーブル名を行のリストに対応させた YAML ファイル1つ |
| フィクスチャ | テストが既知の状態として使うときの、同じファイルの呼び名 —— フィクスチャを参照 |
シードとマイグレーションの線引きは、正確に引いて おく価値があります。どちらもデータベースに何かを入れるからです。マイグレーションは 構造を変え、バージョンを持ち、適用済みとして記録され、本番を含むすべての環境へ 渡っていきます。シードは行を挿入し、バージョンを持たず、どこにも記録されず、 開発とテストのデータベースに向けたものです。2回実行したときの意味も同じではありません。 マイグレーションは未適用のものを見つけませんが、シードはもう一度挿入します。
データセット
Section titled “データセット”member:- { id: 1, name: Frank }- { id: 2, name: Grace }- { id: 3, name: Heidi }ファイルは testdata/seed/ に置きます。1つのファイルに複数のテーブルを書けます。
行は書いた順に挿入されるので、行どうしが参照し合う場合は、ファイルの中の順序も、
コマンドラインに並べたファイルの順序も効いてきます。
pw seed # ディレクトリ内のすべてのデータセットpw seed users orders # この2つを、この順で名前はシードディレクトリからの相対パスで、.yaml は省略できます。pw seed users
と pw seed users.yaml は同じ要求です。pw seed を参照して
ください。
データセットの書式
Section titled “データセットの書式”データセットは YAML だけです。モデルは DBUnit のもので、キーの名前もそこから来ていますが、 XML や CSV や Excel のデータセットは読みません。Popcorn Web にも、下層の dbtestify にも、それらを解析するものがない からです。
テーブル、行、そして NULL
Section titled “テーブル、行、そして NULL”トップレベルのキーはテーブル名です。ただしアンダースコアで始まるものはディレクティブです。 各テーブルは行のリストを持ち、行は列名から値への対応です。
NULL は null と書きます。列を省いたときの意味は、周りの行がその列に触れているかどうかで
変わります。行はバッチで挿入され、1 つの文の列リストは、そのバッチの行が使ったキーの
和集合になるからです。
member:- { id: 1, name: Frank, nickname: Frankie }- { id: 2, name: Grace } # nickname は NULL として挿入されるそのテーブルのどの行も触れていない列は、挿入文そのものから外れます。だからスキーマの
既定値が効きます。既定値付きの created_at や連番の id をデータセットに書かずに
済むのはこれです。一方、どれか 1 行が値を与えた列は、隣の行にとっても同じ文の一部です。
省いた行に入るのは既定値ではなく NULL で、NOT NULL 制約があれば違反になり、無ければ
黙って空の列になります。
行には _tag のリストも書けます。Popcorn Web はこれを解析しますが、何にも使いません。
行のタグを参照して
ください。
_operation: テーブルに最初に何をするか
Section titled “_operation: テーブルに最初に何をするか”既定では各テーブルを truncate してから詰め直すので、同じデータセットを 2 回適用しても 行が倍になるのではなく同じ状態になります。
_operation: member: insert access_log: truncate
member:- { id: 3, name: Heidi }| 操作 | 効果 |
|---|---|
clear-insert(既定) |
テーブルを truncate してから、並んだ行を挿入する |
insert |
すでにあるものを残したまま、並んだ行を挿入する |
upsert |
並んだ行を挿入し、主キーが既にあれば更新する |
truncate |
テーブルを空にして、何も挿入しない |
delete |
ファイルが主キーを挙げた行を削除する |
ファイルが名前を挙げていないテーブルは、どの操作でも触られません。
主キーを必要とするのは upsert と delete の 2 つで、その参照にはコネクションが
もう 1 本かかります。選ぶ前に読む価値のある制約です。
キーは単数形で、_tag も同じです。複数形はテーブル名として読まれるので、_operations:
はディレクティブの綴り間違いではなく「そんなテーブルは無い」というエラーになります。
期待値としてしか意味を持たない値
Section titled “期待値としてしか意味を持たない値”同じファイルは、読み込む代わりにデータベースと突き合わせることもできます。書式のうち 2 つは、その向きのためだけにあります。
角括弧で囲んだ値は、値ではなくマッチャです。
| 値 | 一致するもの |
|---|---|
[null] |
NULL。null と書くのと同じ |
[notnull] |
NULL 以外のすべて |
[any] |
任意の値 |
[currentdate, 2m] |
現在時刻から指定した時間内のタイムスタンプ。既定は 1m |
[regexp, ^User .+ logged in$] |
文字列表現がパターンに一致する値 |
audit_log:- { id: 1, created_at: [currentdate, 2m], message: [regexp, "^User .+ logged in$"] }事前に書き下せない列——生成されるタイムスタンプ、識別子が埋め込まれたメッセージ——を これで覆えます。ただし、seed に使うファイルには書かないでください。マッチャはドライバに バインドできない値として届き、seed はそこで失敗します。
もう 1 つの期待値専用のキーが _match です。ファイルに並んでいない行を許すかどうかを
テーブルごとに決めます。どちらを選ぶかは
アサーション側と一緒に扱っています。
1つのファイル、2つの利用者
Section titled “1つのファイル、2つの利用者”テストヘルパーはまさにこのファイルを読みます。
server := testutil.TestRun(t, Handlers(), nil, testutil.WithMigrations("../migrations"), testutil.WithSeed("initial"),)WithSeed はスキーマの設置後、サーバの起動前にデータセットを読み込みます。
ディレクトリを変えるのが WithSeedDir です。ファイルを共有することは便利さの話では
なく、これが目的です。シードとは別に管理されたフィクスチャはずれていきますし、その
ずれ方は静かです。開発データベースにはもう存在しない形に対して、テストだけが通り
続けます。
ブラウザスイートは同じファイルの 3 人目の読み手です。pwdev ビルドのアプリケーション
はシードエンドポイントを提供し、Playwright のテストはテストの合間に HTTP リクエスト
1 本でデータベースを入れ直します ——
E2E テストが示すとおりです。形式は 1 つ、読み手は
3 人、ずれはありません。
テストはこのファイルを逆向きにも読みます。server.AssertDB(t, "after_archive") は
データベースをデータセットと突き合わせ、テーブルごとの差分を報告します。リクエスト後の
期待状態が、SELECT のアサーションの列ではなく、このファイルの隣に置かれたもう 1 つの
ファイルになるということです。その往復と、一致戦略、そしてテーブルを置き換えるのでは
なく足すためのテーブルごとの操作については、
フィクスチャを参照してください。
どのデータベースに入るか
Section titled “どのデータベースに入るか”シードの経路はマイグレーションと同じです。middleware.rdb.write_group、設定されて
いれば middleware.rdb.migration_group、そして readonly の接続が選ばれることは
ありません。
pw seed も DSN をアプリケーション自身の設定から解決するので、他と同じく APP_ENV
に従います。その一貫性がこのコマンドを信頼できるものにしていますが、同時に危険にも
しています。開発用でないデータベースへ流し込む前に、環境を確認してください。
