つかびーの技術日記

情報系修士卒のWeb系技術日記です。現在のフォーカス分野はアドテクです。

specs2のmock機能(stub機能)とtrait+overrideを使ってテスタビリティを上げる

      2015/01/24

今回のネタはScala + specs2 + mockitoを使ったmockによるユニットテストです。

なるべくテストしやすい形でテストコードを書いて行きます!

前提

build.sbtに以下を追加するだけです。

mockを使う場合はmockのdependencyの追加を忘れずに。

今回は最新版を使いますが、Play frameworkなどと一緒に利用する場合はspecs2の依存ライブラリとの競合にご注意ください。

コード

まずは普通に書いてみます。(ダメな例)

こういうControllerクラス、Serviceクラスがあるという想定で、

テストを書いてみます。

テスト対象の関数はnow : という文字列とともに整形された日付を返します。

ダメな点

これは非常に悪いテストコードです。

なぜならControllerがServiceに依存しているにも関わらず、そこをstubとして書いていません。Controllerのテストを書きたいのに、実質Serviceのテストまで書いてしまっているような状況です。

さらに根本的な問題として、ServiceのdateStringは外部リソースに依存しています。この場合は現在日時です。つまり、今回のテストケースは日付が変わると失敗し始めます・・・・。外部リソースはDBなどのケースが多いですね。その場合もまた、テストレコードの用意とかで結構苦労します。

trait + overrideを使ってテスタビリティを上げつつ、mockを使ってテストの安定性を上げる

さて、ここからは上記のダメな例を改善して行きます。

まず初めにControllerとServiceはtraitにしてしまいます。さらに、Serviceのobjectを呼び出すのではなく、それをController内部に変数として保持しておく形式にします。

これだけ見ると、意味あんの・・・?という感じですが、これで大分テスタビリティが上がります。理由はテストコードを書くときにbarServiceを適宜設定することで、stubを使える為です。

では、テストコードを書いて行きます。

ポイントはmockという部分でモックオブジェクトを作成している点です。returnsで返却値を固定しています。

ここで作った物をoverride valしつつ、new FooControllerとすることで、Serviceの挙動を書き換えたControllerができます。これをテストに使うことで単純にFooContollerのロジックだけをテストすることができます。

どうでしょうか、意外と簡単だったかと思います。

FooController内部で利用するモジュールが増えれば増えるほど、mockを用意する手間が増えるのが難点ですが、これで外部リソース依存のテストなどを避けることができます。

ちなみに自分は今やっているプロジェクトで、初めはメモリDB + テスト用SQLという形でテストコードを書いていたんですが、テストデータがこんがらがって意味不明になってしまったので、今は上記の方式を取っています。

みなさんもぜひspecs2でmockitoを使ってみてください。

 - Scala, ライブラリ , ,