ScalaTestを使っていて便利な機能があったので小ネタとして紹介してみます。
OptionValues
Option
型の値を検証したいときに、 OptionValues
を使うとOption値が定義されているかとその値についての検証を同時に行うことができます。
http://www.scalatest.org/user_guide/using_OptionValues
// やりがちな例 opt.get should be > 9 // opt が Noneの場合 NoSuchElementException が起きてテストが失敗する(後述) // より安全な方法 opt should be ('defined) // opt が None の場合 TestFailedException が発生。どこで失敗したかの情報が得られる opt.get should be > 9 // OptionValuesを使う import org.scalatest.OptionValues._ opt.value should be > 9 // Option型の値に対して `value` メソッドを使えるようになる // opt が None の場合 TestFailedException が発生 // テストクラスに対しては OptionValues trait を mix-in するとよい class ExampleSpec extends WordSpec with Matchers with OptionValues { // この中のテストコードで `value` メソッドを使えるようになる }
実際やってみるとこんな感じです。
import org.scalatest.{Matchers, OptionValues, WordSpec} class ExampleSpec extends WordSpec with Matchers with OptionValues { "Option value is defined" in { val opt: Option[Int] = Some(10) opt.value should be > 9 } "Option value is defined but is not match condition" in { val opt: Option[Int] = Some(1) opt.value should be > 9 } "Option value is not defined" in { val opt: Option[Int] = None opt.value should be > 9 } "Option value is not defined and access with get" in { val opt: Option[Int] = None opt.get should be > 9 } }
以下はテスト実行時の出力。注目してほしい点は3番目と4番目のテストの違いで、 value
を使った場合はテストの失敗内容が簡潔にまとめられて出力されていますが、 get
を使った場合は通常の例外における出力(Exceptionのメッセージ + スタックトレース)となっています。
[info] ExampleSpec: [info] - Option value is defined [info] - Option value is defined but is not match condition *** FAILED *** [info] 1 was not greater than 9 (ExampleSpec.scala:12) [info] - Option value is not defined *** FAILED *** [info] The Option on which value was invoked was not defined. (ExampleSpec.scala:17) [info] - Option value is not defined and access with get *** FAILED *** [info] java.util.NoSuchElementException: None.get [info] at scala.None$.get(Option.scala:349) [info] at scala.None$.get(Option.scala:347) [info] at ExampleSpec.$anonfun$new$4(ExampleSpec.scala:22) [info] at ExampleSpec$$Lambda$5658/224979600.apply(Unknown Source) [info] at org.scalatest.OutcomeOf.outcomeOf(OutcomeOf.scala:85) [info] at org.scalatest.OutcomeOf.outcomeOf$(OutcomeOf.scala:83) [info] at org.scalatest.OutcomeOf$.outcomeOf(OutcomeOf.scala:104) [info] at org.scalatest.Transformer.apply(Transformer.scala:22) [info] at org.scalatest.Transformer.apply(Transformer.scala:20) [info] at org.scalatest.WordSpecLike$$anon$1.apply(WordSpecLike.scala:1078) [info] ... [info] ScalaTest [info] Run completed in 436 milliseconds. [info] Total number of tests run: 4 [info] Suites: completed 1, aborted 0 [info] Tests: succeeded 1, failed 3, canceled 0, ignored 0, pending 0 [info] *** 3 TESTS FAILED *** [error] Failed: Total 4, Failed 3, Errors 0, Passed 1 [error] Failed tests: [error] ExampleSpec [error] (Test / testOnly) sbt.TestsFailedException: Tests unsuccessful [error] Total time: 2 s, completed 2018/08/26 11:35:09
EitherValues, TryValues, PartialFunctionValues
OptionValues
と同じような働きをするtraitは他にもあって EitherValues
TryValues
PartialFunctionValues
というものがあります。これも中の値の定義と検証を同時に行い、失敗した場合は TestFailedException
を起こしてくれます。
http://www.scalatest.org/user_guide/using_EitherValues
http://www.scalatest.org/user_guide/using_PartialFunctionValues
(なぜかTryValuesについてのページは存在しない😅)
// TryValues import org.scalatest.TryValues._ val try1: Try[Int] = Try { 100 / 10 } val try2: Try[Int] = Try { 1 / 0 } // enable `success` and `failure` try1.success.value should be > 9 try2.failure.exception should have message "/ by zero" // EitherValues import org.scalatest.EitherValues._ val either1: Either[String, Int] = Right(10) val either2: Either[String, Int] = Left("Muchas problems") // enable `value` either1.right.value should be > 9 either2.left.value should be ("Muchas problemas") // PartialFunctionValues import org.scalatest.PartialFunctionValues._ val pf: PartialFunction[String, Int] = Map("I" -> 1, "II" -> 2, "III" -> 3, "IV" -> 4) // enable `valueAt` pf.valueAt("IV") should equal (4)