【ScalaTest教程1】使用ScalaTest进行测试步骤详解指南
文章目录
ScalaTest是一个功能强大的测试框架,支持多种不同的测试风格。本指南将帮助您快速入门ScalaTest。
一、基本概念
首先是一些基本概念:
- 在ScalaTest中,测试套件(Suite)是零到多个测试的集合。
- 测试可以是任何具有名称的内容,并且可以成功、失败、待定或取消。
- ScalaTest的基本组成单位是Suite,它表示一个测试套件。
- Trait Suite 声明了 run 和其他“生命周期”方法,定义了编写和运行测试的默认方式。
- 这些生命周期方法可以被覆盖以自定义测试的编写和运行方式。
- ScalaTest提供了样式特质(style traits),这些特质扩展了Suite并重写了生命周期方法,支持不同的测试风格。
- 它还提供了混入特质(mixin traits),这些特质重写了样式特质的生命周期方法,以满足特定的测试需求。
- 您可以通过组合Suite样式和混入特质来定义测试类。
- 您可以通过组合Suite实例来定义测试套件。
二 、依赖jar
要在您的sbt项目中包含ScalaTest,只需添加以下行:
libraryDependencies += "org.scalatest" %% "scalatest" % "3.2.17" % "test"
要在Maven项目中包含ScalaTest,可以使用以下配置:
<dependency>
<groupId>org.scalatest</groupId>
<artifactId>scalatest_3</artifactId>
<version>3.2.17</version>
<scope>test</scope>
</dependency>
对于其他将ScalaTest包含在项目中的方式,请参阅运行您的测试。
三、使用步骤
使用ScalaTest非常简单,只需按照以下三个步骤进行操作:
- 选择适合您项目的测试风格。
- 定义您的基类。
- 开始编写测试。
接下来,让我们深入了解每种测试风格的用途和特点。
四、步骤1——为您的项目选择测试风格
ScalaTest支持不同的测试风格,每种风格都设计用于满足特定的需求。为了帮助您找到适合您项目的最佳测试风格,本页面将描述每个选项的预期使用情况。
我们建议您为每个项目选择一组测试风格,然后鼓励所有参与项目的人使用所选的风格。这样可以使测试风格适应团队,同时保持项目代码库的统一性。我们建议您选择一种主要的单元测试风格和另一种验收测试风格。在单元测试和验收测试之间使用不同的风格可以帮助开发人员在低级别的单元测试和高级别的验收测试之间进行“切换”。您还可以选择在特殊情况下使用特定的风格,例如在测试矩阵中使用PropSpec。我们通常会以与单元测试相同的风格编写涉及子系统(如数据库)的集成测试。
简而言之,ScalaTest的灵活性并不旨在让不同的开发人员在同一个项目中使用不同的测试风格。相反,它旨在让项目负责人为团队选择最佳风格或风格。为了帮助您在项目中保持风格的一致性,您可以在构建依赖项中指定风格工件,而不是一般的ScalaTest工件。一般的ScalaTest工件(名为scalatest)包含所有的测试风格,而例如scalatest-flatspec工件只包含FlatSpec测试风格。
您选择的风格仅决定测试声明的外观。在ScalaTest中的其他所有内容(断言、匹配器、混入特质等)都以一致的方式工作,无论您选择了哪种风格。
如果您更愿意被告知要采取哪种方法,而不是自己选择,我们建议您在单元和集成测试中使用FlatSpec风格,在验收测试中使用FeatureSpec风格。我们推荐默认选择FlatSpec风格,因为它与大多数开发人员熟悉的XUnit测试相似,但可以引导您编写具有描述性的规范风格名称的专注测试。
测试风格描述和示例
FunSuite风格
对于从xUnit转来的团队,FunSuite风格感觉舒适和熟悉,同时也带来了BDD的一些好处:FunSuite风格易于编写描述性的测试名称,自然地编写专注的测试,并生成类似规范的输出,有助于各方之间的沟通。
import org.scalatest.funsuite.AnyFunSuite
class SetSuite extends AnyFunSuite {
test("An empty Set should have size 0") {
assert(Set.empty.size == 0)
}
test("Invoking head on an empty Set should produce NoSuchElementException") {
assertThrows[NoSuchElementException] {
Set.empty.head
}
}
}
在sbt构建中仅选择FunSuite风格,请包含以下行:
libraryDependencies += "org.scalatest" %% "scalatest-funsuite" % "3.2.17" % "test"
FlatSpec风格
对于希望从xUnit转到BDD的团队来说,FlatSpec风格是一个很好的第一步,它的结构与xUnit类似,所以简单而熟悉,但测试名称必须以规范风格编写:“X should Y”、"A must B"等等。
import org.scalatest.flatspec.AnyFlatSpec
class SetSpec extends AnyFlatSpec {
"An empty Set" should "have size 0" in {
assert(Set.empty.size == 0)
}
it should "produce NoSuchElementException when head is invoked" in {
assertThrows[NoSuchElementException] {
Set.empty.head
}
}
}
在sbt构建中仅选择FlatSpec风格,请包含以下行:
libraryDependencies += "org.scalatest" %% "scalatest-flatspec" % "3.2.17" % "test"
FunSpec风格
对于从Ruby的RSpec工具转来的团队来说,FunSpec风格会感觉非常熟悉;更一般地说,对于任何喜欢BDD的团队来说,FunSpec的嵌套和逐步指导(使用describe和it)为编写规范风格的测试提供了一个优秀的通用选择。
import org.scalatest.funspec.AnyFunSpec
class SetSpec extends AnyFunSpec {
describe("A Set") {
describe("when empty") {
it("should have size 0") {
assert(Set.empty.size == 0)
}
it("should produce NoSuchElementException when head is invoked") {
assertThrows[NoSuchElementException] {
Set.empty.head
}
}
}
}
}
在sbt构建中仅选择FunSpec风格,请包含以下行:
libraryDependencies += "org.scalatest" %% "scalatest-funspec" % "3.2.17" % "test"
WordSpec风格
对于从specs或specs2转来的团队来说,WordSpec风格会感到熟悉,并且通常是将specsN测试移植到ScalaTest的最自然方式。AnyWordSpec在编写文本时非常规范,因此非常适合希望对其规范文本强制执行高度纪律的团队。
import org.scalatest.wordspec.AnyWordSpec
class SetSpec extends AnyWordSpec {
"A Set" when {
"empty" should {
"have size 0" in {
assert(Set.empty.size == 0)
}
"produce NoSuchElementException when head is invoked" in {
assertThrows[NoSuchElementException] {
Set.empty.head
}
}
}
}
}
在sbt构建中仅选择WordSpec风格,请包含以下行:
libraryDependencies += "org.scalatest" %% "scalatest-wordspec" % "3.2.17" % "test"
FreeSpec风格
因为它对规范文本的编写方式给予了绝对自由(并且没有指导),所以FreeSpec风格适合有经验的BDD团队,并且能够就如何组织规范文本达成一致。
import org.scalatest.freespec.AnyFreeSpec
class SetSpec extends AnyFreeSpec {
"A Set" - {
"when empty" - {
"should have size 0" in {
assert(Set.empty.size == 0)
}
"should produce NoSuchElementException when head is invoked" in {
assertThrows[NoSuchElementException] {
Set.empty.head
}
}
}
}
}
在sbt构建中仅选择FreeSpec风格,请包含以下行:
libraryDependencies += "org.scalatest" %% "scalatest-freespec" % "3.2.17" % "test"
PropSpec风格
AnyPropSpec风格非常适合希望完全以属性检查为基础编写测试的团队;当选择其他风格特质作为主要单元测试风格时,它也是编写偶发性测试矩阵的好选择。
import org.scalatest._
import matchers._
import prop._
import scala.collection.immutable._
class SetSpec extends AnyPropSpec with TableDrivenPropertyChecks with should.Matchers {
val examples =
Table(
"set",
BitSet.empty,
HashSet.empty[Int],
TreeSet.empty[Int]
)
property("an empty Set should have size 0") {
forAll(examples) { set =>
set.size should be (0)
}
}
property("invoking head on an empty set should produce NoSuchElementException") {
forAll(examples) { set =>
a [NoSuchElementException] should be thrownBy { set.head }
}
}
}
在sbt构建中仅选择PropSpec风格,请包含以下行:
libraryDependencies += "org.scalatest" %% "scalatest-propspec" % "3.2.17" % "test"
FeatureSpec风格
FeatureSpec风格主要用于验收测试,包括帮助程序员与非程序员一起定义验收要求的过程。
import org.scalatest._
class TVSet {
private var on: Boolean = false
def isOn: Boolean = on
def pressPowerButton() {
on = !on
}
}
class TVSetSpec extends AnyFeatureSpec with GivenWhenThen {
info("As a TV set owner")
info("I want to be able to turn the TV on and off")
info("So I can watch TV when I want")
info("And save energy when I'm not watching TV")
feature("TV power button") {
scenario("User presses power button when TV is off") {
Given("a TV set that is switched off")
val tv = new TVSet
assert(!tv.isOn)
When("the power button is pressed")
tv.pressPowerButton()
Then("the TV should switch on")
assert(tv.isOn)
}
scenario("User presses power button when TV is on") {
Given("a TV set that is switched on")
val tv = new TVSet
tv.pressPowerButton()
assert(tv.isOn)
When("the power button is pressed")
tv.pressPowerButton()
Then("the TV should switch off")
assert(!tv.isOn)
}
}
}
在sbt构建中仅选择FeatureSpec风格,请包含以下行:
libraryDependencies += "org.scalatest" %% "scalatest-featurespec" % "3.2.17" % "test"
RefSpec(仅限JVM)
RefSpec风格允许您将测试定义为方法,与以函数表示测试的风格类相比,可以节省一个函数字面量。更少的函数字面量可以提高编译时间和减少生成的类文件数量,这有助于最小化构建时间。因此,在构建时间是一个问题的大型项目中使用Spec可能是一个不错的选择,以及通过静态代码生成器通过程序方式生成大量测试。
import org.scalatest.refspec.RefSpec
class SetSpec extends RefSpec {
object `A Set` {
object `when empty` {
def `should have size 0` {
assert(Set.empty.size == 0)
}
def `should produce NoSuchElementException when head is invoked` {
assertThrows[NoSuchElementException] {
Set.empty.head
}
}
}
}
}
请注意:RefSpec中的“Ref”代表反射,RefSpec使用反射来发现测试。由于Scala.js不支持反射,因此在Scala.js上不可用。
在sbt构建中仅选择RefSpec风格,请包含以下行:
libraryDependencies += "org.scalatest" %% "scalatest-refspec" % "3.2.17" % "test"
五、步骤2——为项目定义基类。
ScalaTest是一个测试工具包:它由一些专注、轻量级的特性组成,您可以将它们混合在一起来解决手头的问题。这种方法最大程度地减少了命名和隐式冲突的可能性,并有助于加快编译速度。
我们建议您为项目创建抽象的基类,将您最常使用的特性混合在一起,而不是重复混合相同的特性。例如,您可以为单元测试创建一个UnitSpec类(不是特性,以加快编译速度),如下所示:
package com.mycompany.myproject
import org.scalatest._
import flatspec._
import matchers._
abstract class UnitSpec extends AnyFlatSpec with should.Matchers with
OptionValues with Inside with Inspectors
然后,您可以使用自定义的基类编写项目的单元测试,例如:
package com.mycompany.myproject
import org.scalatest._
class MySpec extends UnitSpec {
// 在这里编写您的测试
}
大多数项目最终会拥有多个基类,每个基类都专注于不同类型的测试。您可能会为需要数据库的集成测试创建一个基类(也许命名为DbSpec),为需要actor系统的集成测试创建另一个基类(也许命名为ActorSysSpec),为既需要数据库又需要actor系统的集成测试创建另一个基类(也许命名为DbActorSysSpec),以此类推。为了开始,您可以只创建一个用于单元测试的基类。
需要注意的是,在本用户指南的其余部分,我们不会扩展UnitSpec。相反,我们展示涉及到的所有特性,以明确使用的内容,并使每个示例都能够独立工作。
有了这些,现在是时候编写你的第一个测试了。
六、步骤3——编写你的第一个helloworld测试
- 在ScalaTest中,您可以在扩展AnyFlatSpec等样式类的类内定义测试(实际上,通常您会直接扩展为您的项目定义的基类,该基类扩展了ScalaTest样式类):
import org.scalatest.flatspec.AnyFlatSpec
class FirstSpec extends AnyFlatSpec {
// tests go here...
}
- 在AnyFlatSpec中,每个测试由一个句子和一个代码块组成,其中句子指定了所需行为的一部分,并且代码块对其进行测试。句子需要一个主语,比如"A Stack";一个动词,可以是should、must或can;以及句子的其余部分。这是一个示例:
"A Stack" should "pop values in last-in-first-out order"
如果您有关于同一主题的多个测试,可以使用it来引用先前的主题:
it should "throw NoSuchElementException if an empty stack is popped"
在句子之后,您将在花括号中放置in单词,然后是测试的主体。以下是一个完整的示例:
import collection.mutable.Stack
import org.scalatest.flatspec.AnyFlatSpec
class StackSpec extends AnyFlatSpec {
"A Stack" should "pop values in last-in-first-out order" in {
val stack = new Stack[Int]
stack.push(1)
stack.push(2)
assert(stack.pop() === 2)
assert(stack.pop() === 1)
}
it should "throw NoSuchElementException if an empty stack is popped" in {
val emptyStack = new Stack[String]
assertThrows[NoSuchElementException] {
emptyStack.pop()
}
}
}
- 将其放置在一个名为StackSpec.scala的文件中,并使用以下Jar文件进行编译:
$ scalac -cp scalatest-app_3-3.2.17.jar StackSpec.scala
要运行它,您还需要另一个artifact,即Scala的XML模块的Jar文件。一旦您下载了该Jar文件,您可以像这样使用ScalaTest的Runner运行StackSpec:
$ CLASSPATH=scalatest-app_3-3.2.17.jar:scala-xml_2.13-2.1.0.jar
$ scala -cp $CLASSPATH org.scalatest.run StackSpec
- 或者,您可以使用ScalaTest shell从Scala解释器运行它:
$ scala -cp .:scalatest-app_3-3.2.17.jar:scala-xml_2.13-2.1.0.jar
scala> import org.scalatest._
import org.scalatest._
scala> run(new StackSpec)
接下来,了解如何使用断言。
参考链接
【ScalaTest系列1】由来场景用法示例详解
【ScalaTest教程2】使用ScalaTest进行测试步骤详解指南
【ScalaTest系列3】 使用断言示例使用词典
【ScalaTest系列4】Sharing fixtures共享测试夹具
【ScalaTest系列5】ScalaTest + JUnit 5+gradle+idea