継続的12章
TRANSCRIPT
継続的12章データを管理する
12.1 導入• アプリケーションのデータは長く保持しなければならない
• システムの中で一番重要なのはデータだ• データは移行しなければならない• データの構造や内容を変更する仕組みが必要• データベースの移行プロセスを自動化すること• 本番のダンプをテストデータとして用いるのは問題なので代替案をあとで提示する
12.2 データベースのスクリプト処理
• DB のあらゆる変更は自動化されなければならない
• アプリケーションの特定のバージョンに合わせた DBスキーマに設定できるよう管理しなければならない
• ユニットテストは DBなしでも動作しなければならない
• 受け入れテストの準備プロセスで DBスキーマとデータを設定できなければならない
12.2.1 データベースを初期化する
• データベースをデプロイする最もシンプルな手順:1. 以前にあったものを削除する2. データベースの構造、インスタンス、スキーマなどを作る
3. データを読み込む
• ほとんどのプロジェクトでは、デプロイメントプロセスで既存データの移行が必要となる
12.3 インクリメンタルな変更• 変更を加えた後もアプリケーションが動作し続けなければならない
• 既存のデータを保持し続けなければならない
• ロールバックの手段も用意しなければならない
12.3.1 データベースのバージョンを管理する
• テーブルにバージョン番号を含める• 変更を加えるたびに2つのスクリプトを作る–ロールフォワード(バージョンを上げる)–ロールバック(バージョンを下げる)
• アプリケーションに動作する DBのバージョン情報を与える
• Ruby on Rails がこの方式– Java, .Netの場合、 DbDeploy, Tarantino, DbDiff,
Dbmigrateがある
12.3.1 続き• 構造を追加する変更は簡単–ロールバックスクリプトは削除するだけ
• 構造を変更する場合、データを削除することがある–ロールフォワードで一時テーブルを作り、削除するデータをコピーして、ロールバックできるようにする
12.3.1 続き2• データベーススキーマの変更は難しくなりがち–追加は概ねうまくいく• 追加する制約に既存のデータが違反していたり、デフォルト値なしで追加する場合は難しくなる
–削除する場合は難しい
12.3.2 続き3• DBの変更を管理するテクニックにより、–アプリケーションのデプロイで、デプロイ先の環境の DBの状態を心配する必要がなくなる
– DB変更によるアプリケーションへの影響を吸収する• アプリケーションが要求する DBのバージョンを更新するまで DBの変更が適用されない
12.3.2 オーケストレイトされた変更を管理する
• すべてのアプリケーションを一つの DBに統合するのはおすすめしないが、しようがない場合もある
• DB変更のテストは DBを共有するアプリケーションもそろえた環境で行うこと– この環境をシステムインテグレーション環境あるいはステージング環境と呼ぶことがある
• 他のアプリケーションを保守するチームと話し合って変更すること
• アプリケーションを複数のバージョンで動くようにすると良い
12.4 データベースのロールバックとゼロダウンタイムリリース
• 本番環境へのデプロイ次の制約–アップグレードの取り消し時にアップグレード後のトランザクションを失わないこと
–アプリケーションを利用可能なままにしておくこと• ホットデプロイ、あるいはゼロダウンタイムリリース
12.4.1 データを失わずにロールバックする
• 単にロールバックするわけには行かない場合–一時テーブルからデータを書き戻す場合、アップグレード以降に追加されたレコードが一貫性制約に違反することがある
–ロールバック時に新たなトランザクションからのデータを削除するが、システム上はそのデータを失うことが許されない
12.4.1 続き• 解決策–アップグレード後のトランザクションをキャッシュする• しっかりした設計とテストが必要
–ブルーグリーンデプロイメント• 片方の DBにロールバック前のデータがあるのでそれを元に復元
• 上記の方法では、データが大量にあると、ダウンタイムを避けられないこともある
12.4.2 アプリケーションのデプロイをデータベースのマイグレーションから分離
する• DBのマイグレーションとアプリケーションのデプロイを切り離す–ブルーグリーンデプロイメントやカナリアリリースにも使える
12.4.2 続き• 後方互換–新しいバージョンのアプリケーションを古いバージョンの DBでも動くようにする
–新しいアプリケーションを古いバージョンのDBで動かす
– 様子を見てロールバックの必要がないと判断したら DBのバージョンを上げる
12.4.2 続き2• 前方互換– 古いバージョンのアプリケーションを新しいバージョンの DBでも動くようにする
–新しく追加されたフィールドは無視する– 万能ではないが通常の変更の場合には有用
12.4.2 続き3• 抽象化レイヤ–ストアドプロシージャやビュー–データベースオブジェクトを変更しても、アプリケーション向けのインターフェースであるビューなどはそのままに保てる
12.5 テストデータを管理する• パフォーマンス–ユニットテスト• DBを使わない、あるいはインメモリの DB
–その他のテスト• 一部の例外を除き本番 DBのデータを使わない
• テストの分離–テストの実行に関するデータが永続的に保持されうる
12.5.1 仮のデータベースでのユニットテスト
• DBとやり取りするサービスの代わりにテストダブルを使う
• DBにアクセスするコードをテストダブルで置き換える– リポジトリパターン
• DBアクセスの抽象化レイヤ– DBアクセス用コードがひとまとめになり保守しやすい
• DBのフェイクを使う– インメモリ DB
• H2, SQLite, JavaDB
12.5.2 テストとデータのつながりを管理する
• テストの状態を管理する方法– テストの分離
• 各テスト用のデータはそのテストからしか見えない– 順応型テスト
• 各テストがデータ環境を調べ、データに合わせて振る舞いを変える
– テストの順序付け• 実行順序を決め、各テストが一つ前のテストの出力を使う
• 通常はテストの分離を使うべき– 並列化も可能になる
12.5.3 テストの分離• 個々のテストがアトミック–他のテストに影響を与えず、影響を受けない
• 方法–テスト終了時に DBをテスト実行前の状態に戻す• トランザクションを利用してロールバックする
–データの機能分割• 受け入れテストでも有効• 命名規則を定めてそれに従い、各テストがそのテスト用のデータだけを見る
12.5.4 準備と後始末• 開始位置を確立し、テスト終了時に状態を復元する
• 順応型テストでは、準備段階で環境を評価して、既知の開始位置を確立する
12.5.5 一貫したテストシナリオ• 一貫したストーリーを作ってテストしたくなることがある–データの準備と後始末が簡単になる– 個々のテストがシンプルになる– 実行速度が早くなる(データの生成と破棄が
減るため)
12.5.5 続き• 個人的には良くないと考えている– 各テストが密結合する• テストの設計が難しくなる• 1つのテストの失敗により、依存するテストすべてが失敗する• 業務のシナリオや技術的実装の変化によりテストスイート全体の見直しが発生する• 各ステップでのテストを網羅できない
12.6 データの管理とデプロイメントパイプライン
• 我々がテストしたかったのは一体何か?– 我々が望む振る舞いと一致しているか
• ユニットテスト– 不注意による破壊を防げる
• 受け入れテスト– 価値をお届けできることを確かめる
• キャパシティテスト– キャパシティ要件を満たすことを確かめる
• インテグレーションテスト– 他のサービスと正しくやり取りすることを確かめる
• これらのテストで必要になるテストデータはどのようなものか、どのように管理するべきか
12.6.1 コミットステージでのテストにおけるデータ
• 実行速度が最重要• 実装の詳細とテストを密結合させない–リファクタリングの妨げとなる–本来はリファクタリングを安全に行う助けとなるべき
– 密結合はテストデータに凝り過ぎた結果であることが多い
12.6.1 続き• よくできたコミットステージは、手の込んだデータを用意せずに済む
• 最小限のテストデータを使う–それほどデータ駆動ではない–テストヘルパーやフィクスチャを使う• データ構造の変化の影響を軽減できる
12.6.2 受け入れテストにおけるデータ
• システムをテストする–テストデータは複雑となる
• テストケースの作成を再利用できるようにすること
• 各テストのテストデータに対する依存を減らす
12.6.2 続き• データの区分を意識すると良い–テスト固有のデータ• テスト対象を動かす• テストするケースの詳細を表す
–テストが参照するデータ• テストに関係する• テスト対象の振る舞いにはあまり影響しない
–アプリケーションが参照するデータ• テスト対象に無関係• アプリケーション立ち上げのために必要
12.6.2 続き2• テスト固有のデータ–一意でなければならない–テスト分離のための手段
• テストが参照するデータ– 事前に用意した初期データで管理可能– 再利用する
• アプリケーションが参照するデータ–どんな値でも、 null でも良い–テストの結果に影響しなければ良い
12.6.2 続き3• アプリケーションが参照するデータ、うまくすればテストが参照するデータも、データベースからのダンプの形式で保持しておける–それをバージョン管理し、マイグレーションを組み込む
– DBの自動マイグレーション自体のテストとしても有効
12.6.2 続き4• アプリケーションの内部コードや DBダンプを使って初期状態に持っていくのはおすすめしない
• アプリケーションの APIを使うべき–受け入れテストの間に矛盾した状態に陥らない– DBやアプリケーションのリファクタリングが、受け入れテストに影響しなくなる
–受け入れテストがアプリケーションの APIテストとしても機能する
テストデータの形式:その一例• 金融取引:ユーザーのポジションが正しく更新されたかのテスト
• テスト固有のデータ– アカウント、ポジション
• テストの準備としてアカウントの登録と資金を提供• テストが参照するデータ
– 金融商品• 他のテストが再利用しても、「ポジションのテスト」には影響しない
• アプリケーションが参照するデータ– アカウント作成のオプション
• ポジションの算出に無関係ならばテストに無関係• 任意のデフォルト値を使う
12.6.3 キャパシティテストにおけるデータ
• 大量のデータの生成を自動化することを勧める• 入力データもマスタデータも、インタラクションテンプレートのような仕組みを使う (p.299)
• 受け入れテストをシステムとのインタラクションを記録したものとして捉える– 記録した内容はそれ以降のステージでの開始地点として利用
– 受け入れテストを選択して関連するデータをツールを用いて膨らませ、様々なインタラクションを行えるようにする?
– 受け入れテストとキャパシティテスト両方で使えるでしょ?
12.6.4 その他のテストステージにおけるデータ
• 自動化した受け入れテストを以降のすべてのテストの開始地点として使う
• WEB アプリケーションでは互換性テストも用意する–受け入れテストを全ての主要ブラウザで行う
12.6.4 続き• 手動テストでのデータの扱い–最小限のデータで、空っぽの初期状態で開始する
–大きめのデータ群を使い、実環境を想定した操作シナリオを実行する
–本番データベースのダンプを使う• おすすめしない• 巨大で扱いにくい• 本番 DBのマイグレーションのテストなどでは有効
12.6.4 続き2• これらのデータ群は開発者が開発環境でも使えなければならない
• 開発者の開発環境では、本番データベースのデータを使ってはならない
12.7 まとめ• データ管理の基本原則– 完全に自動化したプロセスで DB作成やマイグレーションを行えるようにすること
–なんどでも繰り返せて信頼できること–どんな場合も同じ手順に従うこと
12.7 続き• 本番 DBのダンプをなるべく使わない–テスト自身に状態を作らせる– 各テストが独立して実行できる–テスターは目的に合わせてデータを用意し管理する
12.7 続き2• DBをバージョン管理し、ツールでマイグレーションを自動化する
• スキーマ変更の際に前方互換性と後方互換性を維持し、データのデプロイメントとマイグレーションの問題をアプリケーションのデプロイメントの問題と分離する
• テストデータの作成はテストの準備段階に含める• テストで共有するデータの準備処理に、アプリケーション立ち上げのための必要最小限のデータを含め、それ以外に含めるのは一般的に使われるマスターデータだけにする
12.7 続き3• テストに必要な状態を準備するときは、可能な限りアプリケーションの公開 APIを用いる
• 例外を除いて本番データベースのダンプをテスト用に使わない–本番データから注意して取り出した小さめのデータか、受け入れテストやキャパシティテストを実行した結果のデータを使う