問題
DBに登録したり、ファイルに書いたり、保存するような処理のテストについて考える。
インターフェースを抽象化すると、下記のような感じになる。
public interface ItemRepository { // itemを保存する void save(Item item); }
この場合、saveのユニットテストは「itemが保存されていること」になるが、どうやってテストしたらいいだろう?
考えられる選択肢
考えられる選択肢はいくつかある。
実体を見に行く 🤔
たとえば保存先がデータベースならデータベース、ファイルならファイルを直接参照しに行く形。
Spockであれば、Groovy.Sqlみたいなクラスを使って、テストDBにクエリを発行して結果を確認する。
ただこの手段だとどうしても実装量が増えてしまうし、テストのためのコードの品質が気になってくる。
また、実装のテストなのかインターフェースのテストなのかも曖昧になる。
実体を見に行く+ヘルパーを用意する 🤔
実体を参照するような処理をヘルパーなりライブラリなりに切り出して、ヘルパーの品質はヘルパーのテストなりで担保する方法。
実体を参照する処理は(ヘルパーに追い出したので)ある程度スッキリはする。
しかし実体を参照するために用意しているけど本当にやりたかったのは保存をするテストだったはずで、何をやりたかったのか分からなくなってくる。
取得のインターフェースを用意する 🤔
save(item)
した後に findById(id)
のように、取得のインターフェースを作成する。
しかしこうするとsaveのテストをしたかったはずなのにfindByIdを作成しなければならないし、findByIdのテストはどうするんだっけ?となってくる。
「ユニット」は「メソッド」ではない
結論から言うと、取得のインターフェースを提供するのがよさそうである。
粒度として対称性のある処理が必要になる(なりそう)なのであれば、それはモジュールが果たすべき役割ではないか。
createがあるならdeleteがあるだろうし、pushがあればpopがあるだろう、といったように。
「ユニット」を「メソッド」ではなく「モジュール」と考えると、「ユニットテスト」は「モジュールの整合性が取れていること(必要なインターフェースが揃っている)」を確認するものといえる。
実際の要求ではcreateメソッドだけが必要な場合もあるが、テストで結局同等の処理を書くなら、抽象度揃えたインターフェースを提供したほうが旨味もありそうである。
まとめ
- ユニットはメソッドではなく「モジュール」と考える
- ユニットテストはモジュールとしての整合性(必要なインターフェースが揃っているか)を確認する役割もある
- やりすぎはよくないが、対称性のある処理について考慮し、必要に応じてモジュールに提供させる