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)允許使用相同的語法來指示篩選器,將執行的測試篩選為特定的測試或萬用字元。除了明確的篩選器之外,只會執行符合下列其中一個條件的測試

  • 上次執行時失敗的測試
  • 先前未執行的測試
  • 具有一個或多個遞移相依性(可能在不同的專案中)重新編譯的測試。
Tab 鍵補全 

Tab 鍵補全會根據上次 Test/compile 的結果提供測試名稱。這表示,在編譯新來源之前,無法使用新的來源進行 Tab 鍵補全,而刪除的來源在重新編譯之前不會從 Tab 鍵補全中移除。仍然可以手動寫出新的測試來源,並使用 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 的變體會傳遞用於執行測試的類別載入器(或曾經用於執行測試的類別載入器)。它提供對測試類別以及測試框架類別的存取權。

注意:在 forking 時,無法提供包含測試類別的 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")))

Forking 測試 

設定

Test / fork := true

指定所有測試都會在單一外部 JVM 中執行。如需設定 forking 的標準選項,請參閱 Forking。根據預設,在 forked 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 標籤的限制(預設值為 1),控制允許同時執行的 forked JVM 數目。在 fork 群組時,無法提供具有實際測試類別載入器的 SetupCleanup 動作。

此外,使用下列設定,可以在 forked JVM 中選擇性地以平行方式執行 forked 測試

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 

sbt-jupiter-interface 提供對 JUnit5 的支援。若要將 JUnit Jupiter 支援加入到您的專案中,請將 jupiter-interface 依賴項加入到您專案的主要 build.sbt 檔案中。

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

並將 sbt-jupiter-interface 外掛程式加入到您的 project/plugins.sbt 中

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

junit-interface 提供對 JUnit4 的支援。請將 junit-interface 依賴項加入到您專案的主要 build.sbt 檔案中。

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