ScalaTest 2.2.0がリリースされたようです。2.2.0の変更内容でちょっと気になるものに、「DiagrammedAssertions」というのがあったので試してみました。
2.2.0の変更内容はRelease Notesを見ていただければと思うのですが、
ScalaTest/Scalactic 2.2.0 Release Notes
http://www.scalatest.org/release_notes/2.2.0
今回は
- Enhanced Assertions error messages
- DiagrammedAssertions
を取り扱います。
いや、せっかくなのでメッセージの改善というのも見てみようと思いまして。
準備
とりあえず、2.1.7と比較しつつやろうかなと思ったので、以下のようなsbtの設定ファイルを用意しました。
build.sbt
name := "scalatest-2.2.0-improve" version := "0.0.1-SNAPSHOT" scalaVersion := "2.11.1" organization := "org.littlewings" scalacOptions ++= Seq("-Xlint", "-deprecation", "-unchecked", "-feature") incOptions := incOptions.value.withNameHashing(true) libraryDependencies ++= Seq( "org.scalatest" %% "scalatest" % "2.1.7" % "test" //"org.scalatest" %% "scalatest" % "2.2.0" % "test" )
これで、ScalaTest 2.1.7と2.2.0を切り替えつつ遊んでみたいと思います。
メッセージの改善
実際にプログラムを書いたりRelease Notesを見ていて思ったのですが、これもそうですがDiagrammedAssertionsも、基本的にはassert関係の話なんですねぇ…。
普段ScalaTestを使っている時は、Matchersを使っている方なのですが…。
まあ、いいんですけど。そういえば、今回見ていて気付いたのですが、assertってマクロなんですね。
で、とりあえず、こんなテストケースを書いてみました。
src/test/scala/org/littlewings/scalatest/ScalaTestSampleSpec.scala
package org.littlewings.scalatest import org.scalatest.FunSpec class ScalaTestSampleSpec extends FunSpec { describe("Assertions Improve spec") { it("compare values") { val (a, b, c, d) = (1, 2, 3, 4) assert(a == b || c >= d) } it("List#exists#1") { val xs = List(1, 2, 3) assert(xs.exists(_ == 4)) } it("string test") { assert("hello".startsWith("h") && "goodbye".endsWith("y")) } it("instanceOf") { val num = 1.0 assert(num.isInstanceOf[Int]) } it("Some#isEmpty") { assert(Some(2).isEmpty) } it("None#isDefined") { assert(None.isDefined) } it("List#exists#2") { val xs = List(1, 2, 3) assert(xs.exists(i => i > 10)) } } }
ええ、かなりRelease Notes的な感じです!このテストは、すべて失敗します。
そういえば、Assertions使って書くの、初めてですね…。
では、ScalaTest 2.1.7で動かした結果を、一部載せてみます。
> test
結果。
[info] ScalaTestSampleSpec: [info] Assertions Improve spec [info] - compare values *** FAILED *** [info] org.scalatest.exceptions.TestFailedException was thrown. (ScalaTestSampleSpec.scala:13) 〜以下、省略〜
…おお、マジですか?これだけだったんだ!
では、2.1.7から2.2.0でどう変わったか、テストコード自体は一切触らずにsbtでの依存関係の変更のみでどれだけ変わるか、見てみましょう。
まあ、これを
libraryDependencies ++= Seq( "org.scalatest" %% "scalatest" % "2.1.7" % "test" //"org.scalatest" %% "scalatest" % "2.2.0" % "test" )
こうするだけですね。
libraryDependencies ++= Seq( //"org.scalatest" %% "scalatest" % "2.1.7" % "test" "org.scalatest" %% "scalatest" % "2.2.0" % "test" )
以下、順に。
テストコード。
it("compare values") { val (a, b, c, d) = (1, 2, 3, 4) assert(a == b || c >= d) }
ScalaTest 2.1.7での結果。
[info] - compare values *** FAILED *** [info] org.scalatest.exceptions.TestFailedException was thrown. (ScalaTestSampleSpec.scala:13)
ScalaTest 2.2.0での結果。
[info] - compare values *** FAILED *** [info] 1 did not equal 2, and 3 was not greater than or equal to 4 (ScalaTestSampleSpec.scala:13)
テストコード。
it("List#exists#1") { val xs = List(1, 2, 3) assert(xs.exists(_ == 4)) }
ScalaTest 2.1.7での結果。
[info] - List#exists#1 *** FAILED *** [info] org.scalatest.exceptions.TestFailedException was thrown. (ScalaTestSampleSpec.scala:19)
ScalaTest 2.2.0での結果。
[info] - List#exists#1 *** FAILED *** [info] List(1, 2, 3) did not contain 4 (ScalaTestSampleSpec.scala:19)
テストコード。
it("string test") { assert("hello".startsWith("h") && "goodbye".endsWith("y")) }
ScalaTest 2.1.7での結果。
[info] - string test *** FAILED *** [info] org.scalatest.exceptions.TestFailedException was thrown. (ScalaTestSampleSpec.scala:23)
ScalaTest 2.2.0での結果。
[info] - string test *** FAILED *** [info] "hello" started with "h", but "goodbye" did not end with "y" (ScalaTestSampleSpec.scala:23)
テストコード。
it("instanceOf") { val num = 1.0 assert(num.isInstanceOf[Int]) }
ScalaTest 2.1.7での結果。
[info] - instanceOf *** FAILED *** [info] org.scalatest.exceptions.TestFailedException was thrown. (ScalaTestSampleSpec.scala:29)
ScalaTest 2.2.0での結果。
[info] - instanceOf *** FAILED *** [info] 1.0 was not instance of scala.Int (ScalaTestSampleSpec.scala:29)
テストコード。
it("Some#isEmpty") { assert(Some(2).isEmpty) }
ScalaTest 2.1.7での結果。
[info] - Some#isEmpty *** FAILED *** [info] org.scalatest.exceptions.TestFailedException was thrown. (ScalaTestSampleSpec.scala:33)
ScalaTest 2.2.0での結果。
[info] - Some#isEmpty *** FAILED *** [info] Some(2) was not empty (ScalaTestSampleSpec.scala:33)
テストコード。
it("None#isDefined") {
assert(None.isDefined)
}
ScalaTest 2.1.7での結果。
[info] - None#isDefined *** FAILED *** [info] org.scalatest.exceptions.TestFailedException was thrown. (ScalaTestSampleSpec.scala:37)
ScalaTest 2.2.0での結果。
[info] - None#isDefined *** FAILED *** [info] scala.None.isDefined was false (ScalaTestSampleSpec.scala:37)
テストコード。
it("List#exists#2") { val xs = List(1, 2, 3) assert(xs.exists(i => i > 10)) }
ScalaTest 2.1.7での結果。
[info] - List#exists#2 *** FAILED *** [info] org.scalatest.exceptions.TestFailedException was thrown. (ScalaTestSampleSpec.scala:43)
ScalaTest 2.2.0での結果。
[info] - List#exists#2 *** FAILED *** [info] xs.exists(((i: Int) => i.>(10))) was false (ScalaTestSampleSpec.scala:43)
確かにこれは、随分改善された気がします…。というか、元が…。
DiagrammedAssertions
さて、続いてDiagrammedAssertionsを試してみましょう。
そもそもDiagrammedAssertionsとは何かというところですが、特にGroovy界隈(Spock)で有名なPower-Assertができるようになったということらしいです。Groovyでは使ったことがありませんが…。
先ほどのテストコードのimport文に、以下を追加します。
import org.scalatest.DiagrammedAssertions._
変更内容、これだけです。
では、実行してみましょう!
> test
結果。
[info] ScalaTestSampleSpec: [info] Assertions Improve spec [info] - compare values *** FAILED *** [info] 1 did not equal 2, and 3 was not greater than or equal to 4 (ScalaTestSampleSpec.scala:13) 〜以下、省略〜
あれ…先ほどの結果と変わりませんが…?Release Notesで書いてたことと、同じようにやったのですが…。
と、ここでちょっと考えて、objectをimportするのではなくて、traitをmix-inする方向に変えてみました。つまり、こんな感じのクラス定義になりました。
import org.scalatest.FunSpec import org.scalatest.DiagrammedAssertions //class ScalaTestSampleSpec extends FunSpec { class ScalaTestSampleSpec extends FunSpec with DiagrammedAssertions {
もう1度、テスト実行!
> test
結果。
[info] ScalaTestSampleSpec: [info] Assertions Improve spec [info] - compare values *** FAILED *** [info] assert(a == b || c >= d) [info] | | | | | | | [info] 1 | 2 | 3 | 4 [info] | | false [info] | false [info] false (ScalaTestSampleSpec.scala:13) 〜以下、省略〜
やった!変わりました!まあ、Release NotesにはDiagrammedAssertionsトレイトって書いてますしね…。こうするのが正しいのかなぁ…。
では、結果をそれぞれ対比していってみましょう。
テストコード。
it("compare values") { val (a, b, c, d) = (1, 2, 3, 4) assert(a == b || c >= d) }
ScalaTest 2.2.0での通常のassert。
[info] - compare values *** FAILED *** [info] 1 did not equal 2, and 3 was not greater than or equal to 4
ScalaTest 2.2.0でのDiagrammedAssertions。
[info] - compare values *** FAILED *** [info] assert(a == b || c >= d) [info] | | | | | | | [info] 1 | 2 | 3 | 4 [info] | | false [info] | false [info] false (ScalaTestSampleSpec.scala:13)
テストコード。
it("List#exists#1") { val xs = List(1, 2, 3) assert(xs.exists(_ == 4)) }
ScalaTest 2.2.0での通常のassert。
[info] - List#exists#1 *** FAILED *** [info] List(1, 2, 3) did not contain 4 (ScalaTestSampleSpec.scala:19)
ScalaTest 2.2.0でのDiagrammedAssertions。
[info] - List#exists#1 *** FAILED *** [info] assert(xs.exists(_ == 4)) [info] | | [info] | false [info] List(1, 2, 3) (ScalaTestSampleSpec.scala:19)
テストコード。
it("string test") { assert("hello".startsWith("h") && "goodbye".endsWith("y")) }
ScalaTest 2.2.0での通常のassert。
[info] - string test *** FAILED *** [info] "hello" started with "h", but "goodbye" did not end with "y" (ScalaTestSampleSpec.scala:23)
ScalaTest 2.2.0でのDiagrammedAssertions。
[info] - string test *** FAILED *** [info] assert("hello".startsWith("h") && "goodbye".endsWith("y")) [info] | | | | | | | [info] "hello" true "h" | "goodbye" false "y" [info] false (ScalaTestSampleSpec.scala:23)
テストコード。
it("instanceOf") { val num = 1.0 assert(num.isInstanceOf[Int]) }
ScalaTest 2.2.0での通常のassert。
[info] - instanceOf *** FAILED *** [info] 1.0 was not instance of scala.Int (ScalaTestSampleSpec.scala:29)
ScalaTest 2.2.0でのDiagrammedAssertions。
[info] - instanceOf *** FAILED *** [info] assert(num.isInstanceOf[Int]) [info] | | [info] 1.0 false (ScalaTestSampleSpec.scala:29)
テストコード。
it("Some#isEmpty") { assert(Some(2).isEmpty) }
ScalaTest 2.2.0での通常のassert。
[info] - Some#isEmpty *** FAILED *** [info] Some(2) was not empty (ScalaTestSampleSpec.scala:33)
ScalaTest 2.2.0でのDiagrammedAssertions。
[info] - Some#isEmpty *** FAILED *** [info] assert(Some(2).isEmpty) [info] | | | [info] | 2 false [info] Some(2) (ScalaTestSampleSpec.scala:33)
テストコード。
it("None#isDefined") {
assert(None.isDefined)
}
ScalaTest 2.2.0での通常のassert。
[info] - None#isDefined *** FAILED *** [info] scala.None.isDefined was false (ScalaTestSampleSpec.scala:37)
ScalaTest 2.2.0でのDiagrammedAssertions。
[info] - None#isDefined *** FAILED *** [info] assert(None.isDefined) [info] | | [info] None false (ScalaTestSampleSpec.scala:37)
テストコード。
it("List#exists#2") { val xs = List(1, 2, 3) assert(xs.exists(i => i > 10)) }
ScalaTest 2.2.0での通常のassert。
[info] - List#exists#2 *** FAILED *** [info] xs.exists(((i: Int) => i.>(10))) was false (ScalaTestSampleSpec.scala:43)
ScalaTest 2.2.0でのDiagrammedAssertions。
[info] - List#exists#2 *** FAILED *** [info] assert(xs.exists(i => i > 10)) [info] | | [info] | false [info] List(1, 2, 3) (ScalaTestSampleSpec.scala:43)
なるほどー。
オマケ
ちなみに、自分が普段使っているMatcherだとこういうコードになって
src/test/scala/org/littlewings/scalatest/ScalaTestSample2Spec.scala
src/test/scala/org/littlewings/scalatest/ScalaTestSample2Spec.scala package org.littlewings.scalatest import org.scalatest.FunSpec import org.scalatest.Inspectors._ import org.scalatest.Matchers._ class ScalaTestSample2Spec extends FunSpec { describe("Use Matchers spec") { it("compare values") { val (a, b, c, d) = (1, 2, 3, 4) (a == b || c >= d) should be (true) } it("List contains") { val xs = List(1, 2, 3) xs should contain (4) } it("string test") { "hello" should startWith ("h") "goodbye" should endWith ("y") } it("instanceOf") { val num = 1.0 num should be (a [Int]) } it("Some#isEmpty") { Some(2) should be (empty) } it("None#isDefined") { None should not be (empty) } it("List#exists") { val xs = List(1, 2, 3) all (xs) should be > 10 } } }
こういう結果表示になります。
[info] ScalaTestSample2Spec: [info] Use Matchers spec [info] - compare values *** FAILED *** [info] false was not true (ScalaTestSample2Spec.scala:12) [info] - List contains *** FAILED *** [info] List(1, 2, 3) did not contain element 4 (ScalaTestSample2Spec.scala:18) [info] - string test *** FAILED *** [info] "goodbye" did not end with substring "y" (ScalaTestSample2Spec.scala:23) [info] - instanceOf *** FAILED *** [info] 1.0 was not an instance of int, but an instance of java.lang.Double (ScalaTestSample2Spec.scala:29) [info] - Some#isEmpty *** FAILED *** [info] Some(2) was not empty (ScalaTestSample2Spec.scala:33) [info] - None#isDefined *** FAILED *** [info] None was empty (ScalaTestSample2Spec.scala:37) [info] - List#exists *** FAILED *** [info] 'all' inspection failed, because: [info] at index 0, 1 was not greater than 10 (ScalaTestSample2Spec.scala:40) [info] in List(1, 2, 3) (ScalaTestSample2Spec.scala:43)
さて、どっちがいいでしょうかね?