1. 测试

测试 

基础 

测试的标准源代码位置为

  • src/test/scala/ 中的 Scala 源代码
  • src/test/java/ 中的 Java 源代码
  • src/test/resources/ 中的测试类路径资源

可以使用 java.lang.Classjava.lang.ClassLoadergetResource 方法从测试中访问这些资源。

主要的 Scala 测试框架(ScalaCheckScalaTestspecs2)提供了一个通用测试接口的实现,只需要将它们添加到类路径中就可以与 sbt 一起使用。例如,ScalaCheck 可以通过将其声明为 托管依赖关系 来使用

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

Test配置,这意味着 ScalaCheck 仅在测试类路径上,主源代码不需要它。对于库来说,这通常是一个好习惯,因为你的用户通常不需要你的测试依赖关系来使用你的库。

定义了库依赖关系后,你可以在上面列出的位置添加测试源代码并编译和运行测试。运行测试的任务是 testtestOnlytest 任务不接受任何命令行参数,并运行所有测试

> test

testOnly 

testOnly 任务接受一个以空格分隔的测试名称列表来运行。例如

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

它也支持通配符

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

testQuick 

testQuick 任务与 testOnly 类似,允许使用相同的语法来过滤要运行的测试以筛选到特定的测试或通配符,以指示筛选器。除了显式筛选器之外,还将运行满足以下条件之一的测试

  • 在上次运行中失败的测试
  • 以前没有运行过的测试
  • 具有一个或多个可能在不同项目中的传递依赖关系的测试被重新编译。
标签补全 

基于上次 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

可以使用 IntegrationTest 替换 Test,仅按顺序执行集成测试。请注意,来自不同项目的测试可能仍会同时执行。

筛选类 

如果你只想运行名称以“Test”结尾的测试类,请使用 Tests.Filter

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

分叉测试 

设置

Test / fork := true

指定所有测试将在单个外部 JVM 中执行。有关配置分叉的标准选项,请参阅 分叉。默认情况下,在分叉的 JVM 中执行的测试按顺序执行。使用 testGrouping 键可以更好地控制如何将测试分配到 JVM 以及要传递给这些 JVM 的选项。例如,在 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 )
}

单个组中的测试按顺序运行。通过设置 Tags.ForkedTestGroup 标签的限制来控制允许同时运行的分叉 JVM 的数量,默认情况下为 1。当组被分叉时,无法使用实际测试类加载器提供 SetupCleanup 操作。

此外,还可以选择在分叉的 JVM 中并行运行分叉的测试,使用以下设置

Test / testForkedParallel := true

附加测试配置 

你可以添加一个附加测试配置,以拥有独立的测试源代码集以及相关的编译、打包和测试任务和设置。步骤如下

  • 定义配置
  • 添加任务和设置
  • 声明库依赖关系
  • 创建源代码
  • 运行任务

以下两个示例演示了这一点。第一个示例展示了如何启用集成测试。第二个示例展示了如何定义自定义测试配置。这允许你为每个项目定义多种类型的测试。

集成测试 

以下完整构建配置演示了集成测试。

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”作为配置,而不是“it,test”。

使用标准源代码层次结构

  • src/it/scala 用于 Scala 源代码
  • src/it/java 用于 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)

集成测试部分的注释仍然适用,只是将 IntegrationTest 替换为 FunTest,将 "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
并行执行的应用 

这种共享源方法的一种用途是将可以并行运行的测试与必须串行执行的测试分开。对附加配置应用本节中描述的过程。我们称配置为 serial

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

然后,我们可以使用以下方法禁用该配置中的并行执行

Serial / parallelExecution := false

要并行运行的测试将使用 test 运行,而要串行运行的测试将使用 Serial/test 运行。

JUnit 

JUnit5 的支持由 sbt-jupiter-interface 提供。要将 JUnit Jupiter 支持添加到您的项目,请在项目的 main build.sbt 文件中添加 jupiter-interface 依赖项。

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

并将 sbt-jupiter-interface 插件添加到您的 project/plugins.sbt

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

JUnit4 的支持由 junit-interface 提供。在项目的 main build.sbt 文件中添加 junit-interface 依赖项。

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

扩展 

本页介绍如何添加对其他测试库的支持并定义其他测试报告器。您可以通过实现 sbt 接口(如下所述)来实现。如果您是测试框架的作者,则可以将测试接口作为提供的依赖项。或者,任何人都可以通过在单独的项目中实现接口并将该项目打包为 sbt 插件 来提供对测试框架的支持。

自定义测试框架 

主要 Scala 测试库对 sbt 具有内置支持。要添加对其他框架的支持,请实现 统一测试接口

自定义测试报告器 

测试框架将状态和结果报告给测试报告器。您可以通过实现 TestReportListenerTestsListener 来创建一个新的测试报告器。

使用扩展 

要在项目定义中使用扩展

修改 testFrameworks 设置以引用您的测试框架

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

通过在项目定义中覆盖 testListeners 设置来指定要使用的测试报告器。

testListeners += customTestListener

其中 customTestListener 的类型为 sbt.TestReportListener