ScalaのコードをRSpecでテストする

ついったーでspecsについても教えてもらったけど、JavaをRSpecできるならScalaも可能なのではないかということでRSpecでScalaのBDDにチャレンジしてみた。

環境の準備

JRuby入れる

公式から落としてきて/opt/jrubyに放り込む。
そして/opt/jruby/binにパスを通す。

ディレクトリ構造

CLASSPATHの設定に必要となるので整理。

scala_with_rspec/
  build/
    classes/
  spec/
  src/

build/classes以下にscalacでコンパイルされたclassファイルが出力される。
spec以下にはspecファイルを置き、srcにScalaのソースを置く。

以下ずっとscala_with_rspec直下での作業。

CLASSPATHの設定

1. scala-library.jarを含める /opt/local/scala-2.8.0.final/lib/scala-library.jar
2. テスト対象のclassファイルが存在するディレクトリを含める

ということなので以下のようなコマンドを実行。

$ export CLASSPATH=$CLASSPATH:/opt/local/scala-2.8.0.final/lib/scala-library.jar:/path/to/scala_with_rspec/build/classes

※ /path/to/scala_with_rspec/build/classesはあとでRakefileの中で指定するようにした。

ScalaをRSpecで

さてspecと実装を書いてテストを実行してみる。
単純なゲッターとセッターのテスト。

sample_spec.rb
require 'java'

module ToBrassWithspec
  include_package "to.brass.withspec"
end

describe ToBrassWithspec::Sample, "は" do
  before(:each) do
    @sample = ToBrassWithspec::Sample.new
  end

  it "名前を取得できること" do
    @sample.get_name().should == 'nanashi-san'
  end

  it "名前を設定できること" do
    @sample.set_name('hoge')
    @sample.get_name.should == 'hoge'
  end
end

アンダースコア化したメソッド名も使えるのがいい!

Sample.scala
package to.brass.withspec

class Sample() {
  var name: String = "nanashi-san"
  def setName(newName: String): Unit = {
    name = newName
  }
  def getName(): String = name
}

ちなみにこのようなゲッターとセッターをScalaで書く必要は本来全くないみたい。
まああくまで実験サンプルということで。

コンパイル

あらかじめコンパイルしないとテストできないのでコンパイルしておく。

scalac -d build/classes src/Sample.scala
実行

コンパイルできたらテスト。
以下コマンドと出力。

$ jruby -S spec spec/sample_spec.rb

..

Finished in 0.2 seconds

JRubyの起動でちょっと待たされるのがやや残念。
でも通ったー!

Rakefile

コンパイルとspecの実行を別々のコマンドでやっていると、コンパイルを忘れてspecを走らせるという愚をしばしば犯しそうなので、クラス名を指定してScalaのコンパイルとRSpecの実行を行うRakefileを書いてみた。

require 'rake'
require 'rubygems'
require 'active_support/inflector'

task :default => :spec

desc "compile scala and run rspec."
task :spec do
  sh "scalac -d #{app_class_path} src/#{ENV['classname'].camelize}.scala"
  sh "jruby -S spec spec/#{ENV['classname'].underscore}_spec.rb"
end

def app_class_path
  File.join(File.dirname(__FILE__), 'build', 'classes')
end

以下のように実行。

$ rake spec classname=sample

本格的に使おうとするとまだ実用には耐えないだろうけど、あとは必要に応じていじってみよう。
俺たちのScalaはこれからだ!

参考

2010-09-20追記

Rakefileの中でENV[“CLASSPATH”]を設定する方法だとうまくいかなくなってしまった。
ということで削除。

それとも前うまくいったのはシェルでやったCLASSPATHの設定が残ってただけで、もともとうまくいかなかったのかな・・・
Rails3にしたりとかRVM入れ直したりとかいろいろ環境いじったのでよくわからない。

あとcamelizeを使うためのactive_supportのrequireの仕方もActiveSupport 3.0から変わったので修正した。

2010-09-20更に追記

「作ったクラスの最初の利用者になる」という観点から見ると実コードの言語とテストコードの言語を別々にすると言うのはイマイチだった。
RSpecのExample内で十分使いやすいと思ったメソッドを実際に(JavaやScalaで)使うともう一息使い勝手が悪かったり、またテストコードでDuck Typingを使ってしまうとRSpecは問題なく通るのに実際に使うと型に関するエラーが出たりしてガッカリ。

この技は宴会芸にとどめておくべきなのかもしれない。

次の記事

Yokohama.rb第0回に行ってきた