テストデータ

DB設計におけるテストデータの管理について、DBFluteとの関わりを交えながら話題にします。 ここでは、開発環境での単体テスト用(自動テストも含む)のテストデータに焦点を当てます。

テストデータ管理の重要性

まずいきなり二つ。

  • テストデータをしっかり管理することがプロジェクト成功の一番の近道
  • プログラムがDB変更に強くてもテストデータがDB変更に弱いと無意味

DBFluteは、テストデータに関してこの二つの考えを軸にアプローチしています。

極端な話、良いO/Rマッパを使うことよりも、良いテストデータが揃っていること の方が、プロジェクトを成功に近づくための歩数は大きいと考えています。(両方揃えば一番良い)

また、DBFluteはDB変更に強いをポリシーとしていますが、テストデータが散在していて、DB変更時にテストデータの修正コストが膨大だと その強みは半減します。逆に考えると、この部分へのアプローチなしでDB変更に強いというポリシーは前面に出せません。

そういうことからもDBFluteは ReplaceSchema というO/Rマッパの機能とは直接関係のない機能も提供し、かつ、テストデータの管理に対するアプローチ をこのページにまとめて提供しています。

テストデータの管理のパターン

細かく分類するとキリがないので、大きくよくあるパターンを分析してみたいと思います。

まず、マスタデータに関しては、あまり議論することはないかもしれません。ケースによって変わることが少ないので、自然と "A" 方式になることが多いでしょう。変える必要がある場合でも、そのときは テストプログラム上でちょこっと更新するだけで 対応できるでしょう(これは実質的に "C" 方式)。問題になるのはトランザクションデータの方 なので、以降の話題はそちらに焦点を当てます。

A. DBAチームがテストデータを作成・管理

DBAチームが全てのケースに対応するデータを作成します。

DBFluteに置き換えると、ReplaceSchemaのエクセルデータを全てDBAチームが準備します。 DB変更時はこのテストデータの変更もDB変更の一部に含めて、修正済みのものをディベロッパーに横展開するようにします。

この方式は、テストデータの作成もDB設計の作業の一部 という考え方に適用できます。つまり、DB変更作業もテストデータを直して初めて完了する と言えます。但し、DB設計者本人が全てのデータを作る必要はなく、経験の浅い人がDB設計者の下でレビューをしてもらいながらひたすらデータを作成する、 というやり方でも構いません。とにかく(DB構造に詳しい人が近くにいる状態で)誰か限られた人が作成する ということがこの方式の大事なところです。DB設計者はとても忙しいことが多いので、実際にはそういうパターンがほとんどかと思われます。(そういう意味でDBA チーム という風にここでは表現しています)

メリット ("A" 方式)

DB変更時のテストデータの修正コストの削減
テストデータが一元管理されているため、(散らばっている状態に比べれば遥かに)修正の影響範囲が少なく、かつ、特定しやすくなります。
間違ったテストデータでテストされるリスクの低減
DB構造をよくわかっている人がテストデータを管理することで、ディベロッパーが間違ったデータでテストしてしまうリスクが回避しやすくなります。 また、テストデータが一元管理されているため、人間の目から見て網羅性が高く、DB構造をよくわかっている人がデータのレビューをしやすいということもあります。
データ仕様の横展開がスムーズにいく
DB設計者の常の悩みは、DB構造に関する質問攻めにあうことでしょう。仕様策定者からもディベロッパーからも常に質問が来ます。 できる限りわかりやすく迷わないテーブル構造を心掛け、ERD(ER図)にもできるだけデータ仕様を書くようにするべきですが、 リソース不足でなかなか限界もあります。その状態でテストデータなんか作るコストを掛けていられないと思ってしまいがちですが、 それは逆で、テストデータがしっかり揃っていると、実は質問が大幅に減ります。皆、その(精度の高い)テストデータを見てDB構造を理解するからです。 どんなにデータ仕様が詳しく説明(文字)で書いてあるよりも、実際にテストデータを一目見ることの方が理解は早いのです。両方が充実しているのが一番ですが、 どちらか片方にリソースを寄せる必要があるのであれば、テストデータを揃える方に重点を置く方が効果が高いでしょう。
そのまま結合テストのテストデータに利用できる
結合テストしないシステムはほとんどないでしょう。そして、結局そこで精度の高いテストデータを作るのです。 その時期なら画面からデータが作れる、と思ってしまいがちですが、結合テストでの精密な検査をするにはやはりある程度は手動でまとまったデータを作る必要があります。 どうせ後でコスト掛けるくらいなら最初からデータ作って単体テストでも利用できる方が遥かに得です。
DB設計自体のレビューになる
テストデータの作成タイミングが早ければ早いほど、DB設計自体が早い段階で精度の高いものになっていき、 その精度の高い設計を実装前にディベロッパーに展開しやすくなります。 ドキュメント上(机上)だけで設計していざテストデータを作ってみると、"もっとこうやった方がいいな" とか "あ、これだと検索できない" など、色々出てくるものです。そのタイミングが早ければ早いほど、DB変更時の影響(ディベロッパーの不満など)を抑えることができます。
テスト実行が速くなり、デグレチェックがしやすい
他の方式でも実現方法次第で同じメリットを享受できますが、テストデータはDB環境構築時にReplaceSchemaで一括で登録されるため、 テスト実行時のテストデータの登録処理コストが掛かりません。テストケースが少ない時点では気になりませんが、多くなってきたとき意外に痛い問題に発展します。 他のプログラムに影響がありそうな(プログラム)修正をしたときに、ローカルで全テストケースを実行してデグレをチェックしたいと思っても、全部のテストケースに 30 分も時間が掛かってしまうとなると、なかなかフットワークが軽くなりません。それでもちゃんと実行してチェックすべきですが、省略してしまう人もいるでしょう。 CI環境に任せると言っても、影響範囲次第で修正内容を変えたいというような修正を行っている場合は、やはりすぐに実行したいものです。

デメリット

DBAチームの管理コストが高い
要は、ディベロッパーに任せてた部分を全てDBAチームが引き受けることになります。なので、DBAチームに対するリソースの配分に関して、 しっかりとマネジメントで調整する必要があります。但し、全体のコストという面では、それぞれのディベロッパーに任せるよりも、 DBAチームで一手に引き受ける方が遥かに効率が良いでしょう。
テストデータのバグのフィードバックコスト
ディベロッパーが作るよりも遥かに精度の高いテストデータが作成できるでしょうが、バグがないわけではないです。 そのとき、大抵そのバグは開発中(実装中)に挙ってきます。そのとき、そのバグを誰が直すでしょうか? それの役割を明確にしておく必要がありますし、もし、DBAチームが修正するのであれば、DBAチームは開発中に予期せぬ工数が発生することになりますので、 そこで別の仕事が入っているとパンクしてしまう可能性があります。
独立した事象のケースの扱い
あるケースを実現するためにはあるケースが存在してはならない、というようなテストケースの横展開方法に悩みます。 ReplaceSchemaの dataLoadingType の切り替え機能を使って、登録するデータを切り替えることもできますが、 テストケースによってReplaceSchemaの実行を変えなければならないので、それなりにマネジメント(仕組み作り)が必要になります。

考察

大規模であればあるほど、効果が高い方式です。その分、デメリットも大きくなるのですが、 増加するデメリットよりも、増加するメリットの方が遥かに大きい と考えています。

B. ディベロッパーがテストデータを作成・管理

ディベロッパーがそのとき自分がテストするために欲しいテストデータを自分で作成します。

この方式の実現方法として、それぞれのテストケースごとに専用のテストデータを作成・配置(管理)する というのと、DBFluteのReplaceSchemaのエクセルデータをそれぞれのディベロッパーが作成・追加する というで大きく二つに分けられます。前者の実現方法は、DBFluteとしては特に支援する機能は存在していません。

メリット

"A" 方式と真逆ということで、"A" のデメリットの逆がそのままこの方式のメリットとなります。

デメリット

同じく "A" のメリットの逆がそのままこの方式のデメリットとなります。

  • DB変更時のテストデータのコスト膨大
  • 間違ったテストデータでテストされるリスク
  • データ仕様に関する大量の質問がDBAチームへ
  • 結合テストでのテストデータに利用しづらい(散在した不整合なデータのため)
  • テストデータを作ることによって発覚するDB設計の不備が実装中に発生
  • テスト実行が重くなる(ディベロッパーがテスト実行するのがおっくうになる)

テストケースごとに専用のデータを作成・配置(管理)

それぞれのテストケースごとに専用のデータを作成・配置(管理)する実現方法だと、もろにデメリットを食らいます。 最悪なパターンとしては、テストデータが直し切れなくて単体テスト(自動テスト)が形骸化 してしまうことです(珍しくない話です)。また、テストデータが直し切れないのでDB変更を躊躇する というのも最悪です。DBはとても長生きです。そのような理由で長期的な利益を押し込めてしまうのはあまりにもったいないと言えるでしょう。 いずれにせよ、DBFluteの特徴である "DB変更に強い" が完全に死んでしまいます。

ReplaceSchemaのエクセルデータを作成・追加

ReplaceSchemaのエクセルデータを作成・追加する方式であれば、ある程度のデメリットは解消されます。 但し、大人数でのエクセルデータの修正はそれなりに(逆に)マネジメントが必要 になります。同じようなケースのテストデータを至るところで作成されてしまって、ReplaceSchemaのテストデータが(無駄に)膨張して、 ReplaceSchemaの実行にやたら時間が掛かってしまうという新たな問題も発生してしまいます。

考察

大規模であればあるほど、デメリットが膨張する方式です。DBAチームからすると楽なのでこの方が良いように感じてしまいがちですが、 プロジェクト全体で考えると明らかにコストとリスクが大きくなっています。一方で、大規模になってもメリットがそんなに大きくならないのが特徴です。

C. ある程度のデータを "A" 足りなければ "B"

DBAチームが全てのテストデータを作るのではなく、ある程度のケースを網羅したデータをあらかじめ作り、 ディベロッパーがそのデータを元にして、自分のテストケースに都合の良いデータに変えてテストするようにします。

8 割 2 割のバランス

デメリットの存在しない方式は(どの分野においても同じで)存在しません。大事なのは いかにデメリット(やリスク)を許容できる範囲に収めるか であり、また、メリットも100%享受する必要はなく、ある程度享受できれば良いのです。

この方式は、"A" と "B" をマージしたようなやり方で、メリット・デメリットもマージされます。 およそ 8 割くらいをDBAチームが頑張って、実装中に 2 割の不足をディベロッパー自身が補完する という(開発)運用でかなりバランスの良い開発ができるようになると考えます。

8 割ならDBAチームも快適

全てのケースを厳密に網羅するのはやはり大変なことです。しかし、DBAチーム自身がテストデータを作ることには多くのメリット があるので、それらメリットを大きく享受しデメリットは最小限に、というのがバランスが良いと考えます。

2 割ならディベロッパーも快適

8 割のデータがあることで、ディベロッパーの 2 割はかなり楽なものになります。2 割であれば、ディベロッパー自身がReplaceSchemaのエクセルデータの作成・追加する のも比較的スムーズに運用でき、そして、8 割のちゃんとしたデータがあるのであれば、テストプログラム上でちょこっと更新するだけで 自分(ディベロッパー)が欲しいデータは簡単に手に入るでしょう。

テストデータを散在させない

この方式なら ディベロッパーが作成するテストデータをテストケースごとに作成・配置(管理)することがない(する必要がない) ため、DB変更にも強い(テストデータが一元管理されている)状態を保つことができます。

似てる!?

それにしてもDBFluteのバランス重視のDBアクセスI/Fとどこか似てます。

パターンの総括

プロジェクトの組織は多種多様ですので、一概にどの方式を採用するかという話にはなりませんが、 これらアプローチを参考にそのプロジェクトに合ったやり方を見つける(生み出す)のが大事です。

それを踏まえた上で、DBFluteとの相性で考えると、少なくとも "B" 方式に近いやり方はお奨めできません。"A" もしくは "C" 方式の方向性がフィットしやすい でしょう。

ちょこっと更新でテストデータ作成

そのテストケースで欲しいテストデータを自分が一から作るのではなく、 既に存在するデータをテスト実行前にちょこっとだけ更新して最後にもとに戻す というやり方をすることで、テストデータの作成コストが削減できます。但し、ある程度の(精度の高い)データが存在することが前提です。

自動テストのとき

自動テストであれば、テスト内での更新はロールバックされますので、幾ら更新しても構いません。 しかも、タイプセーフなAPIを使って更新できるので、DB変更時にも強くなります。

更新処理のコストがテスト実行時に含まれてしまいますが、大量のデータを登録するわけではないので、大きな問題になる可能性は低いでしょう。

もし、生年月日が null の退会会員のデータが無かったら @Java
// ## Arrange ##
Member pre = new Member();
pre.setMemberId(3);
pre.setBirthdate(null);
pre.setMemberStatusCode_Equal_Withdrawal();
memberBhv.update(pre); // create data for this test case

// ## Act ##
// ...execute and use the member

// ## Assert ##
// ...assert result

手動テストのとき

手動テストであっても、ツールからちょこっとデータを修正してテストを行い、また元の状態に戻せるような仕組みになっていればOKです。 要は、ReplaceSchemaし直せば元通りと。もちろん、バグ発生時の再現データとしてその瞬間のSNAPSHOTは保存する必要はあるので、 それはDBMSの機能を使うなりして対応します。その場合は、それにプラスして再現するパターンをそのままReplaceSchemaのデータに追加するのも良いかと思います。

とにかく一件以上データが入っていれば

このやり方の 最大の敵は一件もデータが入っていない という状態です。更新のしようがないので、プログラム上だろうがエクセルだろうが一からデータを作るしかありません。 せめて一件でも二件でもデータがあれば、このやり方で様々なケースをテストすることができるでしょう。