Maven での BDD - Specs, Specs2, RSpec, Easyb, spock
Maven3 を使ったプロジェクトでの BDD(振舞駆動開発)の実施方法をまとめてみました。
今回試した BDD ツールは以下の通りです。
結果として、この中では Specs/Specs2 か spock あたりを使うのが良さそうです。(RSpec と Easyb は問題あり)
なお、実行環境は以下の通りです。
サンプルのソースは http://github.com/fits/try_samples/tree/master/blog/20110321/
Specs の場合
Scala による BDD ツールの Specs を使用する場合、pom.xml を以下のように設定します。
なお、surefire プラグインのデフォルト設定では XXXSpec クラスは実行対象にならないため、include で **/*Spec.java を追加しています。(実ファイルの拡張子は .scala ですが、拡張子を .java で指定する点に注意)
pom.xml
<project ・・・> ・・・ <properties> <scala.version>2.8.1</scala.version> </properties> <!-- Specs 等 Scala 関連ライブラリを取得するためのリポジトリ設定 --> <repositories> <repository> <id>scala-tools.org</id> <name>releases</name> <url>http://scala-tools.org/repo-releases</url> </repository> </repositories> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <configuration> <includes> <!-- XXXSpec クラスを実行対象にするための設定 拡張子を .java で include しなければならない点に注意 --> <include>**/*Spec.java</include> </includes> </configuration> </plugin> <!-- Scala によるスペックファイルをテスト時にコンパイルするための設定 --> <plugin> <groupId>org.scala-tools</groupId> <artifactId>maven-scala-plugin</artifactId> <executions> <execution> <goals> <goal>testCompile</goal> </goals> </execution> </executions> <configuration> <scalaVersion>${scala.version}</scalaVersion> </configuration> </plugin> </plugins> </build> <dependencies> <!-- junit の定義が必要 --> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.8.2</version> <scope>test</scope> </dependency> <!-- Specs の設定 --> <dependency> <groupId>org.scala-tools.testing</groupId> <artifactId>specs_${scala.version}</artifactId> <version>1.6.7.2</version> <scope>test</scope> </dependency> </dependencies> </project>
次に、スペックファイルは以下のようになります。src/test/scala に配置します。
他にも org.specs.runner.JUnit4 を使って定義する方法等がありますが、今回はシンプルに定義できる方法を採用しています。
src/test/scala/BookSpec.scala
package fits.sample import scala.collection.JavaConversions._ import org.specs._ class BookSpec extends SpecificationWithJUnit { "初期状態" should { val b = new Book() "comments は null ではない" in { b.getComments() must notBeNull } "comments は空" in { b.getComments() must haveSize(0) } } "Comment を追加" should { val b = new Book() b.getComments().add(new Comment()) "Comment が追加されている" in { b.getComments() must haveSize(1) } } }
mvn test で実行します。
実行例
> mvn test ・・・ ------------------------------------------------------- T E S T S ------------------------------------------------------- Running fits.sample.BookSpec Tests run: 3, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.483 sec Results : Tests run: 3, Failures: 0, Errors: 0, Skipped: 0
Specs2 の場合
Specs の次期バージョン Specs2 は以下のようになります。基本的に Specs と同様ですが、groupId やパッケージ名、not の使い方に違いがあります。
pom.xml
<project ・・・> ・・・ <properties> <scala.version>2.8.1</scala.version> </properties> <repositories> <repository> <id>scala-tools.org</id> <name>releases</name> <url>http://scala-tools.org/repo-releases</url> </repository> </repositories> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <configuration> <includes> <include>**/*Spec.java</include> </includes> </configuration> </plugin> <!-- Scala によるスペックファイルをテスト時にコンパイルするための設定 --> <plugin> <groupId>org.scala-tools</groupId> <artifactId>maven-scala-plugin</artifactId> <executions> <execution> <goals> <goal>testCompile</goal> </goals> </execution> </executions> <configuration> <scalaVersion>${scala.version}</scalaVersion> </configuration> </plugin> </plugins> </build> <dependencies> <!-- Specs2 の設定 --> <dependency> <groupId>org.specs2</groupId> <artifactId>specs2_${scala.version}</artifactId> <version>1.0.1</version> <scope>test</scope> </dependency> </dependencies> </project>
src/test/scala/BookSpec.scala
package fits.sample import scala.collection.JavaConversions._ import org.specs2.mutable._ class BookSpec extends SpecificationWithJUnit { "初期状態" should { val b = new Book() "comments は null ではない" in { b.getComments() must not beNull } "comments は空" in { b.getComments() must haveSize(0) } } "Comment を追加" should { val b = new Book() b.getComments().add(new Comment()) "Comment が追加されている" in { b.getComments() must haveSize(1) } } }
Specs と同様に mvn test で実行します。
実行例
> mvn test ・・・ ------------------------------------------------------- T E S T S ------------------------------------------------------- Running fits.sample.BookSpec Tests run: 5, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.546 sec Results : Tests run: 5, Failures: 0, Errors: 0, Skipped: 0
なぜかテスト数のカウントが Specs と異なっているようです。
RSpec の場合
Ruby の BDD ツール RSpec を Maven で使うには一手間かかりました。(実用的では無いかもしれません)
今回は de.saumya.mojo の rspec-maven-plugin を使っていますが、別のプラグインを使う方法もあるようです。
pom.xml では TorqueBox RubyGems Maven Proxy Repository を使って RSpec の gem を Maven から取得できるようにしました。(RubyGems を直接リポジトリに設定する方法は駄目でした)
また、properties 要素内の jruby.version 要素で実行する JRuby のバージョンを指定できます。
pom.xml
<project ・・・> ・・・ <properties> <!-- JRuby のバージョン指定--> <jruby.version>1.6.0</jruby.version> <jruby.plugins.version>0.25.1</jruby.plugins.version> </properties> <repositories> <!-- TorqueBox RubyGems Maven Proxy Repository --> <repository> <id>rubygems-proxy</id> <name>Rubygems Proxy</name> <url>http://rubygems-proxy.torquebox.org/releases</url> <layout>default</layout> <releases> <enabled>true</enabled> </releases> <snapshots> <enabled>fale</enabled> <updatePolicy>never</updatePolicy> </snapshots> </repository> </repositories> <build> <plugins> <!-- rspec-maven-plugin の設定 --> <plugin> <groupId>de.saumya.mojo</groupId> <artifactId>rspec-maven-plugin</artifactId> <version>${jruby.plugins.version}</version> </plugin> </plugins> </build> <dependencies> <!-- RSpec の設定 --> <dependency> <groupId>rubygems</groupId> <artifactId>rspec</artifactId> <version>2.5.0</version> <type>gem</type> <scope>test</scope> </dependency> </dependencies> </project>
スペックファイルは以下のようになります。spec に配置します。
spec/book_spec.rb
require "java" module Fits include_package "fits.sample" end describe "Book" do context "初期状態" do before do @b = Fits::Book.new end it "comments は nil ではない" do @b.comments.should_not be_nil end it "comments は空" do @b.comments.size.should == 0 end end context "Comment を追加" do before do @b = Fits::Book.new @b.comments.add(Fits::Comment.new) end it "Comment が追加されている" do @b.comments.size.should == 1 end end end
実行例(対策前)
> mvn rspec:test ・・・ [INFO] Running RSpec tests from ・・・\20110321\rspec\spec [WARNING] NameError: uninitialized constant RSpec::Core::Formatters::BaseFormatter::StringIO ・・・ [WARNING] load at org/jruby/RubyKernel.java:1062 [WARNING] (root) at -e:1 [INFO] ------------------------------------------------------------------------ [INFO] BUILD FAILURE [INFO] ------------------------------------------------------------------------ [INFO] Total time: 2.308s [INFO] Finished at: Mon Mar 21 16:46:28 JST 2011 [INFO] Final Memory: 3M/15M [INFO] ------------------------------------------------------------------------ [ERROR] Failed to execute goal de.saumya.mojo:rspec-maven-plugin:0.25.1:test (default-cli) on project maven-rspec-sample1: Execution default-cli of goal de.saumya.mojo:rspec-maven-plugin:0.25.1:test failed: Java returned: 1 -> [Help 1] ・・・
これを回避するには rspec:test の実行時に自動生成された target/rspec-runner.rb の 48 行目をコメントアウト化して、ファイルを読み取り専用にしてしまいます。
target/rspec-runner.rb の48行目をコメントアウト化
::RSpec.configure do |config| # config.formatter = ::MultiFormatter end
とりあえずこれで、ビルドは失敗扱いになるものの、一応 RSpec は実行されるようになります。
実行例(対策後)
> mvn rspec:test ・・・ [ERROR] error emitting .rb java.io.FileNotFoundException: ・・・\rspec\target\rspec-runner.rb (アクセスが拒否されました。) ・・・ [INFO] ... [INFO] [INFO] Finished in 0.016 seconds [INFO] 3 examples, 0 failures [INFO] ------------------------------------------------------------------------ [INFO] BUILD FAILURE ・・・
上記のように rspec-runner.rb の内容を書き換える方法で回避する場合、rspec-maven-plugin の de.saumya.mojo.rspec.RSpec2ScriptFactory クラスの getRSpecRunnerScript メソッドの戻り値を AspectJ とかで書き換えてやればもう少しマシになると思います。
Easyb の場合
Groovy による BDD ツールの Easyb です。こちらは Maven 3.0.3 で実行するとエラーが出るため(http://code.google.com/p/easyb/issues/detail?id=209)、Maven 2.2.1 で実行する事にします。
pom.xml ファイルは以下のようになります。
pom.xml(Maven 2.2.1 用)
<project ・・・> ・・・ <build> <plugins> <!-- Maven 2.2.1 で JavaSE 6 のソースを使うための設定 --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>1.6</source> <target>1.6</target> </configuration> </plugin> <!-- Easyb の設定 --> <plugin> <groupId>org.easyb</groupId> <artifactId>maven-easyb-plugin</artifactId> <version>0.9.7-1</version> <executions> <execution> <goals> <goal>test</goal> </goals> </execution> </executions> </plugin> </plugins> </build> </project>
スペックファイルは以下の通りです。src/test/easyb に配置します。
なお、日本語を使用すると実行時にエラーが発生する点に注意。
src/test/easyb/BookStory.groovy
package fits.sample scenario "init state", { given "Book", { b = new Book() } when "" then "comments is not null", { b.comments.shouldNotBe null } and then "comments is empty", { b.comments.size.shouldBe 0 } } scenario "add Comment", { given "Book", { b = new Book() } when "add Comment", { b.comments.add(new Comment()) } then "added Comment", { b.comments.size.shouldBe 1 } }
mvn test で実行します。
実行例(Maven 2.2.1 で実行)
> mvn test ・・・ [INFO] Using easyb dependency org.easyb:easyb:jar:0.9.7:compile [INFO] Using easyb dependency commons-cli:commons-cli:jar:1.1:compile [INFO] Using easyb dependency org.codehaus.groovy:groovy-all:jar:1.7.2:compile [java] Running book story (BookStory.groovy) [java] Scenarios run: 2, Failures: 0, Pending: 0, Time elapsed: 0.577 sec [java] 2 total behaviors ran with no failures ・・・
Spock の場合
最後に Groovy による BDD ツール spock です。
pom.xml ファイルは以下のようになります。(surefire を設定する点は Specs と同様)
pom.xml
<project ・・・> ・・・ <build> <plugins> <!-- XXXSpec を実行対象にするための設定 --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <configuration> <includes> <include>**/*Spec.java</include> </includes> </configuration> </plugin> <!-- Groovy によるスペックファイルをテスト時にコンパイルするための設定 --> <plugin> <groupId>org.codehaus.gmaven</groupId> <artifactId>gmaven-plugin</artifactId> <version>1.3</version> <configuration> <providerSelection>1.7</providerSelection> </configuration> <executions> <execution> <goals> <goal>testCompile</goal> </goals> </execution> </executions> </plugin> </plugins> </build> <dependencies> <!-- spock の設定 --> <dependency> <groupId>org.spockframework</groupId> <artifactId>spock-core</artifactId> <version>0.5-groovy-1.7</version> <scope>test</scope> </dependency> </dependencies> </project>
スペックファイルは以下の通りです。src/test/groovy に配置します。
src/test/groovy/BookSpec.groovy
package fits.sample import spock.lang.* class InitBookSpec extends Specification { def b = new Book() def "comments は null ではない"() { expect: b.comments != null } def "comments は空"() { expect: b.comments.size == 0 } } class AddCommentSpec extends Specification { def b = new Book() def "Comment を追加"() { when: b.comments.add(new Comment()) then: b.comments.size == 1 } }
mvn test で実行します。
実行例
> mvn test ・・・ ------------------------------------------------------- T E S T S ------------------------------------------------------- Running fits.sample.AddCommentSpec Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.468 sec Running fits.sample.InitBookSpec Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.015 sec Results : Tests run: 3, Failures: 0, Errors: 0, Skipped: 0