今参加中ASP.NET MVCのプロジェクト
成果物としてユニットテストが必要で、
本格的にユニットテストを書く初めてのプロジェクトなので。
勉強会や参考書などで聞きかじった
「テスト駆動開発(TDD)」とか「テストファースト」、「保護してリファクタリング」などを試してみようと
と張り切っていたのだが、
既存のテストコードをみると
ControllerのテストでControllerから普通にWorkerService,Model,Repositoryと順に呼び出してDBにアクセスしていて
WorkerServiceのテストでもModelのテストでも同じ様にそれ以下のクラスを順番に呼び出してDBにアクセスし,同じようなテストを何度も繰り返していた
自分も最初はそれに合わせてユニットテストの様なものを書いていたのだが、
全然単体テストではないし、それに
何かの本で単体テストではDBやファイルシステムにアクセスしてはいけないと読んだ気がする。
このままでは駄目だと思い ちゃんとテストを書くことにした
しかし、初めてのユニットテストなのでどう書けばいいのかよくわからない
そこで、とりあえず テスト対象の関数以外全てをMockにして,
適切なMock関数を適切な引数で呼び出していることを保証するテストを書いてみることにした。
作るのは、ドロップダウンでテーブルを選択し、
そのテーブルとその他の関連テーブルからEntity Frameworkでデータを取得し、
それを元に計算、最終的にFileStreamResultとして返すという物なのだが、
ControllerからWorkerService,Modelまではあまり問題はなかったように思う
引数を元にテーブルに対応した計算関数のMockを呼び出し、Mockの返した値ををStream作成関数のMockに渡してしてその戻り値にファイル名を付けてFileStreamResultとして返すといった形だが
一度形がきまってしまえば修正する必要がほとんどない
しかし、Modelや、Repositoryに入ると問題が出てきた
リファクタリングをしようと思い、ソースコードを少しでも変更すると
Mock生成部分を修正する必要が出てくるのだ
引数を増やせば引数が足りないとコンパイルエラーがでるし
呼び出す関数を変えればMockが適切に呼ばれていないというエラーが出る
修正には新しく呼ぶ方の関数のMockを用意して、それを呼び出していることをチェックする必要がある。
「保護してリファクタリング」をするはずが、少しでも修正しようとするとテストコードを直す必要があるのではリファクタリングのしようがない。
これではDBにアクセスしていて実行に時間がかかるし、単体テストではないがきちんと保護されていてリファクタリングの出来る既存コードの方がまだましである。
次に、Mockを呼び出したこと自体のテストはせずに、Mockが想定通りの呼び出し方で呼び出された時に最終的に返されるであろう値でテストを行うようにしてみた、Mock関数の返すはずの値を別の関数が返してもテストは通るが、その別の関数のMockが必要なことには変わりはなく、あまり効果はなかった
どうすればいいのかわからずに悩んでいたのだが、
EnittyFrameworkの生成するDbContextの派生クラスの持つテーブルに対応したDBSetプロパティにRepositoryで直接アクセスせずに、
一度IEnumerableのプロパティに保存してAsQueryable()でIQueryableに変換してLinqにつなぐようにすれば、そのIEnumerableプロパティに普通の配列やListをわたすことで、
Repositoryの検索関数のテストをDBに接続せずに行うことができることに気付いた、
(※AsQueryableを忘れるとそのテーブルの全データを取得してからLinq to Objectで検索してしまうので注意)
この方法を利用すればDBにアクセスせずにDBにこういう値が入っているときにModelがこういう値を返すというテストが可能になり、途中で呼び出す関数などにも一切制限はかからない、
DBにアクセスせずに「保護してリファクタリング」が可能になる!
と思ったのだが、冷静に考えるとModelのテストでRepositoryにアクセスしてしまっている。
DBにアクセスしていないことを除いては最初の状態と同じな気がしてきた……
そうして、また苦悩している時に或る言葉をふと思い出した
「TDDのテストと品質保証のテストは違う」
もしかしてこれか?、これでいいのか?
この基礎中の基礎をわかっていなかったことがすべての苦悩の原因なのか?
いろいろ調べてみた結果、
多少の誤解はありそうなものの、基本的にはその考えは正しそうであるという結論に達した。
[動画で解説]和田卓人の“テスト駆動開発”講座
第8回 テスト駆動開発の「サイクル」――まず受け入れテストで土台を作る
まずDBやファイルシステムにアクセスしてしまっても、
複数のクラスや関数を横断してしまいそうでもいいので、
保護、リファクタリングのできるテストを書けばいいのだ
それから中で呼び出す処理や関数を実装し、
必要であればMockを使った単体テストも用意すればよい。
最初からあったテストコードはそのような物は存在していないのに、
一度 納品・受領されているらしいのでおそらく必須ではないだろう。
保護可能テストさえ通っていればリファクタリング中はMockによる単体テストはコメントアウトしても
大丈夫なはずだ。
とりあえずこれでテスト苦悩開発から解放されそうな気がする。