1. テスト

テスト 

基本 

テストの標準的なソース場所は以下のとおりです。

  • src/test/scala/内のScalaソース
  • src/test/java/内のJavaソース
  • src/test/resources/内のテストクラスパス用リソース

リソースは、java.lang.Classまたはjava.lang.ClassLoadergetResourceメソッドを使用してテストからアクセスできます。

主要なScalaテストフレームワーク(ScalaCheckScalaTestspecs2)は共通のテストインターフェースの実装を提供しており、sbtで動作させるにはクラスパスに追加するだけです。例えば、ScalaCheckは管理された依存関係として宣言することで使用できます。

lazy val scalacheck = "org.scalacheck" %% "scalacheck" % "1.17.0"
libraryDependencies += scalacheck % Test

Test設定であり、ScalaCheckはテストクラスパスにのみ存在することを意味し、メインソースには必要ありません。これはライブラリにとって一般的に良い習慣です。なぜなら、ユーザーは通常、ライブラリを使用するためにテスト依存関係を必要としないからです。

ライブラリ依存関係を定義したら、上記の位置にテストソースを追加し、テストをコンパイルして実行できます。テストを実行するためのタスクはtesttestOnlyです。testタスクはコマンドライン引数を受け付けず、すべてのテストを実行します。

> test

testOnly 

testOnlyタスクは、実行するテスト名の空白区切りのリストを受け付けます。例えば

> testOnly org.example.MyTest1 org.example.MyTest2

ワイルドカードもサポートしています。

> testOnly org.example.*Slow org.example.MyTest1

testQuick 

testQuickタスクは、testOnlyと同様に、同じ構文を使用してフィルターを示すことで、実行するテストを特定のテストまたはワイルドカードに絞り込むことができます。明示的なフィルターに加えて、次の条件のいずれかを満たすテストのみが実行されます。

  • 前回の実行で失敗したテスト
  • 以前に実行されなかったテスト
  • 1つ以上の推移的依存関係を持つテスト(別のプロジェクトにある可能性があります)が再コンパイルされました。
タブ補完 

タブ補完は、最後のTest/compileの結果に基づいてテスト名に対して提供されます。これは、新しいソースはコンパイルされるまでタブ補完で使用できず、削除されたソースは再コンパイルされるまでタブ補完から削除されないことを意味します。新しいテストソースは、testOnlyを使用して手動で記述して実行することもできます。

その他のタスク 

メインソースで使用できるタスクは、一般的にテストソースでも使用できますが、コマンドラインではTest /をプレフィックスとして付け、ScalaコードではTest /を使用して参照されます。これらのタスクには以下が含まれます。

  • Test / compile
  • Test / console
  • Test / consoleQuick
  • Test / run
  • Test / runMain

これらのタスクの詳細については、実行を参照してください。

出力 

デフォルトでは、ログは各テストソースファイルごとに、そのファイルのすべてのテストが完了するまでバッファリングされます。これはlogBufferedを設定することで無効にできます。

Test / logBuffered := false

テストレポート 

デフォルトでは、sbtはビルド内のすべてのテストのJUnit XMLテストレポートを生成し、プロジェクトのtarget/test-reportsディレクトリに配置します。これはJUnitXmlReportPluginを無効にすることで無効にできます。

val myProject = (project in file(".")).disablePlugins(plugins.JUnitXmlReportPlugin)

オプション 

テストフレームワーク引数 

テストフレームワークへの引数は、--セパレータに続いて、コマンドラインでtestOnlyタスクに提供できます。例えば

> testOnly org.example.MyTest -- -verbosity 1

ビルドの一部としてテストフレームワーク引数を指定するには、Tests.Argumentで構築されたオプションを追加します。

Test / testOptions += Tests.Argument("-verbosity", "1")

特定のテストフレームワークのみに指定するには

Test / testOptions += Tests.Argument(TestFrameworks.ScalaCheck, "-verbosity", "1")

セットアップとクリーンアップ 

Tests.SetupTests.Cleanupを使用して、セットアップとクリーンアップアクションを指定します。これらは、() => Unit型またはClassLoader => Unit型の関数のいずれかを受け付けます。ClassLoaderを受け取る変種には、テストの実行に使用された(または使用されていた)クラスローダーが渡されます。これにより、テストクラスとテストフレームワーククラスの両方にアクセスできます。

注記:フォーキングする場合、テストクラスを含むClassLoaderは別のJVMにあるため、提供できません。この場合は、() => Unit変種のみを使用してください。

Test / testOptions += Tests.Setup( () => println("Setup") )
Test / testOptions += Tests.Cleanup( () => println("Cleanup") )
Test / testOptions += Tests.Setup( loader => ... )
Test / testOptions += Tests.Cleanup( loader => ... )

テストの並列実行の無効化 

デフォルトでは、sbtはすべてのタスクを並列に、そしてsbt自体と同じJVM内で実行します。各テストはタスクにマッピングされるため、テストもデフォルトで並列に実行されます。特定のプロジェクト内のテストを直列に実行するには:

Test / parallelExecution := false

Testは、統合テストのみを直列に実行するためにIntegrationTestで置き換えることができます。異なるプロジェクトのテストは、引き続き同時に実行される可能性があることに注意してください。

クラスのフィルター 

名前が「Test」で終わるテストクラスのみを実行する場合は、Tests.Filterを使用します。

Test / testOptions := Seq(Tests.Filter(s => s.endsWith("Test")))

テストのフォーキング 

設定

Test / fork := true

すべてのテストが単一の外部JVMで実行されることを指定します。フォーキングを参照して、フォーキングの標準オプションを設定してください。デフォルトでは、フォークされたJVMで実行されるテストは直列に実行されます。テストがJVMにどのように割り当てられ、それらにどのようなオプションを渡すかをより詳細に制御するには、testGroupingキーを使用できます。例えば、build.sbtでは

import Tests._

{
  def groupByFirst(tests: Seq[TestDefinition]) =
    tests groupBy (_.name(0)) map {
      case (letter, tests) =>
        val options = ForkOptions().withRunJVMOptions(Vector("-Dfirst.letter"+letter))
        new Group(letter.toString, tests, SubProcess(options))
    } toSeq

    Test / testGrouping := groupByFirst( (Test / definedTests).value )
}

単一グループ内のテストは直列に実行されます。同時に実行できるフォークされたJVMの数の制限を、デフォルトで1であるTags.ForkedTestGroupタグの制限を設定することで制御します。グループがフォークされている場合、実際のテストクラスローダーを使用してSetupCleanupアクションを提供することはできません。

さらに、フォークされたテストは、次の設定を使用して、フォークされたJVM内でオプションで並列に実行できます。

Test / testForkedParallel := true

追加のテスト設定 

追加のテスト設定を追加して、別個のテストソースと関連するコンパイル、パッケージング、およびテストタスクと設定を持つことができます。手順は以下のとおりです。

  • 設定を定義する
  • タスクと設定を追加する
  • ライブラリ依存関係を宣言する
  • ソースを作成する
  • タスクを実行する

次の2つの例では、これを実演します。最初の例は、統合テストを有効にする方法を示しています。2番目の例は、カスタマイズされたテスト設定を定義する方法を示しています。これにより、プロジェクトごとに複数の種類のテストを定義できます。

統合テスト 

次の完全なビルド設定は、統合テストを示しています。

lazy val scalatest = "org.scalatest" %% "scalatest" % "3.2.17"

ThisBuild / organization := "com.example"
ThisBuild / scalaVersion := "2.12.18"
ThisBuild / version      := "0.1.0-SNAPSHOT"

lazy val root = (project in file("."))
  .configs(IntegrationTest)
  .settings(
    Defaults.itSettings,
    libraryDependencies += scalatest % "it,test"
    // other settings here
  )
  • configs(IntegrationTest)は、事前に定義された統合テスト設定を追加します。この設定は、itという名前で参照されます。
  • settings(Defaults.itSettings)は、IntegrationTest設定でコンパイル、パッケージング、およびテストアクションと設定を追加します。
  • settings(libraryDependencies += scalatest % "it,test")は、scalatestを標準テスト設定と統合テスト設定itの両方に追加します。統合テストのみに依存関係を定義するには、「it,test」ではなく「it」を設定として使用します。

標準的なソース階層が使用されます。

  • Scalaソース用src/it/scala
  • Javaソース用src/it/java
  • 統合テストクラスパスに追加する必要があるリソース用src/it/resources

標準的なテストタスクは使用できますが、IntegrationTest/をプレフィックスとして付ける必要があります。例えば、すべての統合テストを実行するには

> IntegrationTest/test

または、特定のテストを実行するには

> IntegrationTest/testOnly org.example.AnIntegrationTest

同様に、標準設定はIntegrationTest設定に対して設定できます。直接指定されていない場合、ほとんどのIntegrationTest設定はデフォルトでTest設定に委任されます。例えば、テストオプションが以下のように指定されている場合

Test / testOptions += ...

これらはTest設定によって取得され、次にIntegrationTest設定によって取得されます。オプションは、IntegrationTest設定に配置することで、統合テストのみに追加できます。

IntegrationTest / testOptions += ...

または、:= を使用して既存のオプションを上書きし、これらを決定的な統合テストオプションとして宣言します。

IntegrationTest / testOptions := Seq(...)

カスタムテスト設定 

前の例は、カスタムテスト設定に一般化できます。

lazy val scalatest = "org.scalatest" %% "scalatest" % "3.2.17"
lazy val FunTest = config("fun") extend(Test)

ThisBuild / organization := "com.example"
ThisBuild / scalaVersion := "2.12.18"
ThisBuild / version      := "0.1.0-SNAPSHOT"

lazy val root = (project in file("."))
  .configs(FunTest)
  .settings(
    inConfig(FunTest)(Defaults.testSettings),
    libraryDependencies += scalatest % FunTest
    // other settings here
  )

組み込みの設定を使用する代わりに、新しい設定を定義しました。

lazy val FunTest = config("fun") extend(Test)

extend(Test)の部分は、定義されていないFunTest設定に対してTestに委譲することを意味します。新しいテスト設定のタスクと設定を追加する行は次のとおりです。

settings(inConfig(FunTest)(Defaults.testSettings))

これは、FunTest設定にテストと設定のタスクを追加することを意味します。統合テストでも同じ方法で実行できます。実際、Defaults.itSettingsは便宜的な定義です。val itSettings = inConfig(IntegrationTest)(Defaults.testSettings)

統合テストセクションのコメントは、IntegrationTestFunTestに、"it""fun"に置き換えた場合を除いて有効です。たとえば、テストオプションはFunTest用に具体的に構成できます。

FunTest / testOptions += ...

テストタスクは、fun: をプレフィックスとして実行します。

> FunTest / test

共有ソースを使用した追加のテスト設定 

別々のテストソースセット(とコンパイル)を追加する代わりに、ソースを共有することができます。このアプローチでは、ソースは同じクラスパスを使用して一緒にコンパイルされ、一緒にパッケージ化されます。ただし、構成に応じて異なるテストが実行されます。

lazy val scalatest = "org.scalatest" %% "scalatest" % "3.2.17"
lazy val FunTest = config("fun") extend(Test)

ThisBuild / organization := "com.example"
ThisBuild / scalaVersion := "2.12.18"
ThisBuild / version      := "0.1.0-SNAPSHOT"

def itFilter(name: String): Boolean = name endsWith "ITest"
def unitFilter(name: String): Boolean = (name endsWith "Test") && !itFilter(name)

lazy val root = (project in file("."))
  .configs(FunTest)
  .settings(
    inConfig(FunTest)(Defaults.testTasks),
    libraryDependencies += scalatest % FunTest,
    Test / testOptions := Seq(Tests.Filter(unitFilter)),
    FunTest / testOptions := Seq(Tests.Filter(itFilter))
    // other settings here
  )

主な違いは次のとおりです。

  • テストタスク(inConfig(FunTest)(Defaults.testTasks))のみを追加し、コンパイルとパッケージングのタスクと設定は追加しません。
  • 各構成で実行するテストをフィルタリングします。

標準の単体テストを実行するには、test(または同等のTest / test)を実行します。

> test

追加された構成(ここでは"FunTest")のテストを実行するには、前と同様に構成名をプレフィックスとして付けます。

> FunTest / test
> FunTest / testOnly org.example.AFunTest
並列実行への適用 

この共有ソースアプローチの用途の1つは、並列で実行できるテストと、直列で実行する必要があるテストを分離することです。追加の構成に対して、このセクションで説明した手順を実行します。構成をserialと呼びましょう。

lazy val Serial = config("serial") extend(Test)

次に、次の方法を使用して、その構成でのみ並列実行を無効にできます。

Serial / parallelExecution := false

並列で実行するテストはtestで実行され、直列で実行するテストはSerial/testで実行されます。

JUnit 

JUnit5のサポートは、sbt-jupiter-interfaceによって提供されています。プロジェクトにJUnit Jupiterのサポートを追加するには、プロジェクトのメインのbuild.sbtファイルにjupiter-interfaceの依存関係を追加します。

libraryDependencies += "net.aichler" % "jupiter-interface" % "0.9.0" % Test

そして、project/plugins.sbtにsbt-jupiter-interfaceプラグインを追加します。

addSbtPlugin("net.aichler" % "sbt-jupiter-interface" % "0.9.0")

JUnit4のサポートは、junit-interfaceによって提供されています。プロジェクトのメインのbuild.sbtファイルにjunit-interfaceの依存関係を追加します。

libraryDependencies += "com.github.sbt" % "junit-interface" % "0.13.3" % Test

拡張機能 

このページでは、追加のテストライブラリのサポートを追加し、追加のテストレポーターを定義する方法について説明します。(下記で説明されている)sbtインターフェースを実装することでこれを行います。テストフレームワークの作者であれば、テストインターフェースを提供された依存関係として依存させることができます。あるいは、誰でもインターフェースを別のプロジェクトに実装し、そのプロジェクトをsbt Pluginとしてパッケージ化することで、テストフレームワークのサポートを提供できます。

カスタムテストフレームワーク 

主要なScalaテストライブラリは、sbtの組み込みサポートを持っています。異なるフレームワークのサポートを追加するには、統一されたテストインターフェースを実装します。

カスタムテストレポーター 

テストフレームワークは、ステータスと結果をテストレポーターに報告します。TestReportListenerまたはTestsListenerのいずれかを実装することで、新しいテストレポーターを作成できます。

拡張機能の使用 

プロジェクト定義で拡張機能を使用するには

testFrameworks設定を変更して、テストフレームワークを参照します。

testFrameworks += new TestFramework("custom.framework.ClassName")

プロジェクト定義でtestListeners設定を上書きして、使用するテストレポーターを指定します。

testListeners += customTestListener

ここで、customTestListenersbt.TestReportListener型です。