sbt で Scala の開発サイクルを速くする (後編) 00:06

Posted at 2010/01/07 00:06, Modified at 2010/01/07 01:17

sbt のはなし の続きです。

前回で、ソースコードを編集したらコンパイルと実行が走るところまでは出来た。今回は、ソースコードを編集したら用意しておいたテストコードが走るようにしたいと思う。現代のプログラマだったらテスト書きますよねふつう。

Scala には標準のテストライブラリというのは無い。テストライブラリ自体はいくつかある なかで、今回は ScalaTest を使うことにした。

テストライブラリをいれる

まず jar をダウンロードして、というところから sbt は使えます。project/build/MyProject.scala を用意して

import sbt._

class MyProject(info: ProjectInfo) extends DefaultProject(info) {
  val scalaTest = "org.scalatest" % "scalatest" % "1.0"
}

sbt 上で reload と打つと、プロジェクトの設定が再読み込みされる。

> reload
[info] 
[info] Total session time: 9 s
[info] Recompiling project definition...
[info]    Source analysis: 1 new/modified, 0 indirectly invalidated, 0 removed.
[info] Building project scratch 1.0 using MyProject
[info]    with sbt 0.5.6 and Scala 2.7.7
[info] No actions specified, interactive session started. Execute 'help' for more information.
>

さっき書いた MyProject というクラスがプロジェクトの設定です。詳しくは BuildConfiguration をどうぞ。

なお MyProject という名前はなんでもよく、それが sbt.DefaultProject 経由で sbt.Project トレイトを実装していることが重要。また "org.scalatest", "scalatest", "1.0" という文字列は ScalaTest のダウンロードページ にあって、このトリオが Java というか Maven 発祥のライブラリの識別子らしい。

つぎに update と打ち込んでみる。

> update
[info]
[info] == update ==
[info] :: retrieving :: #scratch [sync]
[info]  confs: [compile, runtime, test, provided, system, optional, sources, javadoc]
[info]  1 artifacts copied, 0 already retrieved (1615kB/179ms)
[info] == update ==
[success] Successful.
[info]
[info] Total time: 2 s
>

これで lib_managed/ の下に ScalaTest の jar が配置されます。

テストを書く

そのまえに Hello.scala のテストが書きづらすぎるので直しておく。

object Hello {
  val greeting = "hello world"

  def main(args: Array[String]): Unit = {
    println(greeting)
  }
}

だいぶ微妙ですがとりあえず。で、テストを src/test/scala/HelloSuite.scala に書く。

import org.scalatest.FunSuite

class HelloSuite extends FunSuite {
  test("""traditional "hello world" from K&R""") {
    assert(Hello.greeting === "hello, world")
  }
}

テストを実行。

> test
[info]
[info] == compile ==
[info]   Source analysis: 1 new/modified, 0 indirectly invalidated, 0 removed.
[info] Compiling main sources...
[info] Compilation successful.
[info]   Post-analysis: 2 classes.
[info] == compile ==
[info]
[info] == copy-test-resources ==
[info] == copy-test-resources ==
[info]
[info] == copy-resources ==
[info] == copy-resources ==
[info]
[info] == test-compile ==
[info]   Source analysis: 1 new/modified, 0 indirectly invalidated, 0 removed.
[info] Compiling test sources...[info] Compilation successful.
[info]   Post-analysis: 2 classes.[info] == test-compile ==
[info][info] == test-start ==
[info] == test-start ==
[info][info] == HelloSuite ==
[info] Test Starting - traditional "hello world" from K&R
org.scalatest.TestFailedException: "hello[] world" did not equal "hello[,] world" 
        at org.scalatest.Assertions$class.newAssertionFailedException(Assertions.scala:278)
        at HelloSuite.newAssertionFailedException(HelloSuite.scala:3)
        at org.scalatest.Assertions$class.assert(Assertions.scala:363)
        at HelloSuite.assert(HelloSuite.scala:3)
...
[error] Test Failed - traditional "hello world" from K&R
[info] == HelloSuite ==
[info]
[info] == test-finish ==
[info] Run: 1, Passed: 0, Errors: 0, Failed: 1
[info] == test-finish ==
[info]
[info] == test-cleanup ==
[info] == test-cleanup ==
[error] Error running HelloSuite: Test FAILED
[error] Error running test: One or more subtasks failed
[info]
[info] Total time: 1 s
>

あ、K&R にのってる "hello world" ってカンマあったんですねー、ってわざとらしいか。スタックトレースは一部省略しました。

せっかくなのでここで test ではなく ~test と打ってから、Hello.scala にカンマを足してみよう。

> ~test
....
[error] Test Failed - traditional "hello world" from K&R
[info] == HelloSuite ==
[info]
[info] == test-finish ==
[info] Run: 1, Passed: 0, Errors: 0, Failed: 1
[info] == test-finish ==
[info]
[info] == test-cleanup ==
[info] == test-cleanup ==
[error] Error running HelloSuite: Test FAILED
[error] Error running test: One or more subtasks failed
[info]
[info] Total time: 0 s
Waiting for source changes... (press enter to interrupt)
[info]
[info] == compile ==
[info]   Source analysis: 1 new/modified, 0 indirectly invalidated, 0 removed.
[info] Compiling main sources...
[info] Compilation successful.
[info]   Post-analysis: 2 classes.
[info] == compile ==
[info]
[info] == test-compile ==
[info]   Source analysis: 0 new/modified, 1 indirectly invalidated, 0 removed.
[info] Compiling test sources...
[info] Compilation successful.
[info]   Post-analysis: 2 classes.
[info] == test-compile ==
[info]
[info] == copy-resources ==
[info] == copy-resources ==
[info]
[info] == copy-test-resources ==
[info] == copy-test-resources ==
[info]
[info] == test-start ==
[info] == test-start ==
[info]
[info] == HelloSuite ==
[info] Test Starting - traditional "hello world" from K&R
[info] Test Succeeded - traditional "hello world" from K&R
[info] == HelloSuite ==
[info]
[info] == test-complete ==
[info] == test-complete ==
[info]
[info] == test-finish ==
[info] Run: 1, Passed: 1, Errors: 0, Failed: 0
[info]
[info] All tests PASSED.
[info] == test-finish ==
[info]
[info] == test-cleanup ==
[info] == test-cleanup ==
[info]
[info] == test ==
[info] == test ==
[success] Successful.
[info]
[info] Total time: 1 s
Waiting for source changes... (press enter to interrupt)

成功。めでたしめでたし。

sbt どうよ

良いですね。

フォルダをいちいちつくるのは少し大げさだけど、最近は local::lib とか virtualenv とか、システム全体とは独立したライブラリ郡をプロジェクトごとに用意するのが流行っているので、そういうものだと思えばまあ。

0 comments
riddle for guest comment authorization:
Where is the capital city of Japan? ...

blog.8-p.info加藤和良 の個人的なブログで、プログラミングのはなしが多めです。