Posted at

ScalaTestでScaledTimeSpansをうまく使うとテスト環境毎に係数をかけて実行時間を待ってくれる

More than 1 year has passed since last update.

タイトルが全てなのですが、具体的にどうやるかも書いておきます。

ドキュメントとしては、 http://www.scalatest.org/user_guide/using_the_runner#scalingTimeSpans の、 Specifying a span scale factor ってあたりに書いてあります。引用しておきます。


If you specify a integer or floating point span scale factor with -F, trait ScaledTimeSpans trait will return the specified value from its implementation of spanScaleFactor. This allows you to tune the "patience" of a run (how long to wait for asynchronous operations) from the command line. For more information, see the documentation for trait ScaledTimeSpans.


なお、以下に引用しているソースコードは、3.1.xですが、実行自体は、ScalaTest 3.0.5で試してます。


Example Code

こんな感じで書いてみました。

package example

import org.scalatest._
import org.scalatest.concurrent._

import scala.concurrent._

class HelloSpec extends FlatSpec with Matchers with ScalaFutures {
import ExecutionContext.Implicits.global
"The Hello object" should "say hello" in {
Future {
Thread.sleep(1000L);
println("hoge");
}.futureValue
Hello.greeting shouldEqual "hello"
}
}


run test

sbtでtestを実行すると、以下のようなエラーになります。

[info] - should say hello *** FAILED ***

[info] A timeout occurred waiting for a future to complete. Queried 10 times, sleeping 15 milliseconds between each query. (HelloSpec.scala:14)


分析

これは、

    }.futureValue

のところでエラーになっています。

といいますのも、Futureの中で1秒スリープしているのですが、これが待ち切れずにtimeoutしています。

このFutureをどれだけ待つかってあたりについては、

class HelloSpec extends FlatSpec with Matchers with ScalaFutures {

でMixinしている ScalaFutures をたどっていくと見えてくるのですが、最終的には、AbstractPatienceConfigurationで宣言されているcase classの、 PatienceConfig が適用されています。

ここで設定されている、

timeout: Span = scaled(Span(150, Millis)), interval: Span = scaled(Span(15, Millis))

っていうのが適用されているため、Futureの中で実施している Thread.sleep を待ち切れずにエラーとなっています。

ここで設定されているtimeoutするまでintervalあけてリトライしていく処理になっており、15ミリ秒間をあけつつ、10回リトライして全部で150ミリ秒待ったけど、終わらなかったからエラーってロジックになっています。1


係数をかける

ScalaTestのドキュメントに、

argument
description
example

-F <span scale factor>
a factor by which to scale time spans
(Note: only one -F is allowed)
-F 10 or -F 2.5

とあり、こちらを実行時に指定すると、係数をかけることができます。

sbtで実行する際は、以下のどちらかの方法で実行できます。ここでは、10を係数としてかけてます。


build.sbt

testOptions in Test += Tests.Argument(TestFrameworks.ScalaTest, "-F", "10")


もしくは、testOnlyで実行するときの引数に渡せます。

sbt:Hello> testOnly example.HelloSpec -- -F 10

どうも、build.sbtで設定しながら、testOnlyの実行時に渡すということはできない様子です。


まとめ

Thread.sleepするような箇所が環境依存で、ローカルのハイスペックMacBook Proでは通るテストが、Travisで実行するとTimeoutで落ちちゃうってときに、使うと良さげです。

build.sbtにベタって、10って書いちゃうと、ローカルとTravisでって分けられないので、10ってあたりを環境変数とかにして、.travis.ymlとかに環境変数を指定するみたいな運用になると良いのかなって思ってます。


build.sbt

testOptions in Test += Tests.Argument(TestFrameworks.ScalaTest, "-F", sys.env.getOrElse("SBT_TEST_TIME_FACTOR", "1"))


以上です。