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の中でテストを実行するしかないかも?