BDDツール spock の Mock
Groovy の BDDツール spock における Mock の使い方を簡単にご紹介します。spock の Mock は定義が簡単なので個人的にはかなり有用だと考えています。
例えば、以下のような記述でモックの処理内容が定義できます。(実行回数と戻り値の組み合わせも可)
戻り値の箇所ではクロージャを使って例外の発生などを行う事も可能です。
モックの定義例
モックオブジェクト名.メソッド名(引数の制約, ・・・) >> 戻り値 モックオブジェクト名.メソッド名(引数の制約, ・・・) >>> [戻り値1回目, 戻り値2回目, ・・・] 実行回数 * モックオブジェクト名.メソッド名(引数の制約, ・・・)
なお、引数の制約では以下のような記述が可能です。
引数の制約例
モックオブジェクト名.メソッド名() //引数なし モックオブジェクト名.メソッド名(_) //何でもよい モックオブジェクト名.メソッド名(!null) //null以外 モックオブジェクト名.メソッド名(値) //値と等しい モックオブジェクト名.メソッド名(!値) //値と等しくない モックオブジェクト名.メソッド名(_ as クラス) //指定クラスのインスタンス モックオブジェクト名.メソッド名({・・・}) //クロージャで制約を実装
詳細は http://code.google.com/p/spock/wiki/Interactions をご覧ください。
それでは、実際に spock で Mock を使ったサンプルを作成していきます。(ソースは http://github.com/fits/try_samples/tree/master/blog/20110814/)
今回は、以下のようなレガシーな感じの Java コードのスペック(テスト)を作成する事にします。(最近は @Inject とかのアノテーションを使って dao 部分を DI するのが普通だと思います)
テスト対象 Java コード
package fits.sample.service; import fits.sample.model.Task; import fits.sample.dao.TaskDao; import fits.sample.dao.DaoFactory; public class ToDoService { private TaskDao dao = DaoFactory.getInstance().getTaskDao(); public String getTaskTitle(Integer taskId) throws NoTaskException { Task t = dao.getTask(taskId); if (t == null) { throw new NoTaskException(); } return t.getTitle(); } public boolean addTask(String title) { boolean result = false; try { dao.addTask(title); result = true; } catch (Exception ex) { } return result; } }
スペック(テスト)の作成
上記の Java コードに対するスペックは以下のようになります。
- Mock() もしくは Mock(インターフェース名) でモックを作成
- Groovy の ExpandoMetaClass を使って dao フィールドの値をモックに置き換え
- ">> {例外オブジェクト}" を使ってモックから例外を throw
なお、spock では when: で実行する処理を、then: で検証内容を実装できます。(setup: とかもあります)
スペック(ToDoServiceSpec.groovy)
package fits.sample.service import spock.lang.* import fits.sample.dao.* import fits.sample.model.* class ToDoServiceSpec extends Specification { def service TaskDao mockDao def setup() { service = new ToDoService() //モックの定義 //def mockDao と定義して mockDao = Mock(TaskDao) でも可 mockDao = Mock() //dao フィールドの値をモック mockDao に置き換え ToDoService.metaClass.setAttribute(service, "dao", mockDao) } def "タスクの追加に成功すると true"() { /* //setup() を使わずフィーチャー毎にセットアップ内容を記載する事も可 setup: def service = new ToDoService() def mockDao = Mock(TaskDao) ・・・ */ when: def res = service.addTask("test") then: mockDao.addTask("test") >> new Task(taskId: 1, title: "test") res == true } def "タスクの追加に失敗すると false"() { when: def res = service.addTask(null) then: //モックで例外を throw mockDao.addTask(null) >> {throw new IllegalArgumentException()} res == false } def "登録済みタスクのタイトル取得"() { when: String res = service.getTaskTitle(1) then: mockDao.getTask(1) >> new Task(taskId: 1, title: "test") res == "test" } def "未登録タスクのタイトル取得は例外発生"() { when: String res = service.getTaskTitle(2) then: mockDao.getTask(2) >> null //クロージャを使って引数の制約を記載する事も可 //mockDao.getTask({it == 2}) >> null //引数の値がどうでも良ければ以下でも可 //mockDao.getTask(_) >> null //例外の発生を検証 thrown(NoTaskException) } }
ちなみに、フィールドの値を変更する MetaClass.setAttribute メソッドの引数は以下のように指定します。
setAttribute の使用方法
クラス名.metaClass.setAttribute(インスタンス, フィールド名, 新しい値)
スペックの実行
今回は Maven を使って実行する事にします。
id:fits:20110321 での pom.xml と基本的に同じ内容ですが、Groovy 1.8.5 用に更新しました。
pom.xml
<project ・・・> ・・・ <build> ・・・ <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <configuration> <includes> <include>**/*Spec.java</include> </includes> </configuration> </plugin> <plugin> <groupId>org.codehaus.gmaven</groupId> <artifactId>gmaven-plugin</artifactId> <version>1.4</version> <configuration> <providerSelection>1.8</providerSelection> </configuration> <executions> <execution> <goals> <goal>testCompile</goal> </goals> </execution> </executions> </plugin> </plugins> </build> <dependencies> <dependency> <groupId>org.codehaus.groovy</groupId> <artifactId>groovy-all</artifactId> <version>1.8.5</version> <scope>test</scope> </dependency> <!-- spock の設定 --> <dependency> <groupId>org.spockframework</groupId> <artifactId>spock-core</artifactId> <version>0.5-groovy-1.8</version> <scope>test</scope> </dependency> </dependencies> </project>
実行例
> mvn test ・・・ ------------------------------------------------------- T E S T S ------------------------------------------------------- Running fits.sample.service.ToDoServiceSpec Tests run: 4, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.483 sec Results : Tests run: 4, Failures: 0, Errors: 0, Skipped: 0 [INFO] ------------------------------------------------------------------------