DevDevデブ!!

プログラミングのこととか書きます。多分。。。

ScalaTestでUsingを使うときに気をつけたいこと

ScalaTestでUsingを使っててちょっとハマったので、そのメモ

TL;DR Usingの中でfailさせてもテスト終了しないよ

Usingの中でテスト対象の処理を実行し、明示的にfailしても、テストは完走します。

なぜならUsingがTryでcatchしてしまうから。

Usingって何よ?

scala.util.Using

scala2.13から追加された、Loanパターンができるやつ。

Loanパターンっていうのは、ファイルなどのリソースを使い終わったらコードブロックを抜けるときに自動でcloseしてくれるデザインパターン

具体的にはこんな感じで使う。

Using(Source.fromFile("hoge.txt")) {sc =>
//ファイルから読みこんだ内容をあつかう処理
}

なぜUsingの中でfailしてもテスト終了しないのか

正確にはUsingにわたす関数の中でfailしてもテストが終了しない。

Usingのapplyの定義は以下のようになっている。

  def apply[R: Releasable, A](resource: => R)(f: R => A): Try[A] = Try { Using.resource(resource)(f) }

関数f実行時に例外が発生しても、Tryでくくられているため、戻り値がFailureになるわけだ。

で、ScalaTestのfailは以下のように例外をスローすることでテストを中断している。

  def fail(message: String)(implicit pos: source.Position): Nothing = {

    requireNonNull(message)
     
    throw newAssertionFailedException(Some(message),  None, pos)
  }

つまり、failでスローされた例外はUsingでキャッチされてしまうので、テストが中断せずに続行されてしまう。

ただ、その場合はUsingで実行した関数の結果がFailureになるので、以下のようにするとテストを中断させることは可能。

Using(Source.fromFile("hoge.txt") {sc => 
//ファイルを使う処理
} match {
  case Success(_) =>
  case Failure(t) => fail(t.getMessage)
}

Failureを補足して再度failを実行するだけ。

とは言っても、さすがにfailを2回書くのは違和感すごいので、自分は以下のようにしました。

Using(Source.fromFile("hoge.txt") { sc =>
  sc.mkString //SourceをStringにする
} match {
  case Failure(t) => fail(t.getMessage)
  case Success(testString) =>
  //testStringを使ってテストを実行
}

上記はテスト用データがStringでも問題ない場合でしか使えない。

InputStreamを引数にとるメソッドのテストなんかだと、Usingの中でテストを実行するしかないかも?