【ScalaTest系列3】 scalatest 使用断言示例详解使用词典

本文详细介绍了ScalaTest中的断言方法,如assert、assertResult和assertThrows,展示了如何使用它们进行条件断言、期望结果验证和异常检查,以及如何提供更好的错误信息和测试标记。
摘要由CSDN通过智能技术生成

【ScalaTest系列3】 使用断言示例使用词典


使用断言

ScalaTest默认提供了三种断言方式,可以在任何样式特性中使用:

  • assert用于一般的断言;
  • assertResult用于区分期望值和实际值
  • assertThrows用于确保代码抛出了预期的异常

为了快速开始使用ScalaTest,请学习并使用这三种断言方式。稍后如果您愿意,可以切换到更具表达力的匹配器DSL。

方法总结

ScalaTest的断言定义在Assertions特性中,该特性被Suite特性扩展,是所有样式特性的超特性。Assertions特性还提供了以下方法:

  • assume用于有条件地取消测试
  • fail用于无条件地使测试失败
  • cancel用于无条件地取消测试
  • succeed用于无条件地使测试成功
  • intercept用于确保代码抛出了预期的异常,并对异常进行断言;
  • assertDoesNotCompile用于确保代码不会编译通过;
  • assertCompiles用于确保代码能够编译通过;
  • assertTypeError用于确保代码由于类型(而不是解析)错误而无法编译通过;
  • withClue用于在失败时添加更多关于错误的信息。

下面详细介绍了这些构造。

示例

assert宏

在任何Scala程序中,您可以通过调用assert并传入一个布尔表达式来编写断言,例如:

val left = 2
val right = 1
assert(left == right)

如果传递的表达式为true,assert将正常返回。如果为false,Scala的assert会抛出AssertionError异常。这个行为是由Predef对象中定义的assert方法提供的,它的成员被隐式地导入到每个Scala源文件中。Assertions特性定义了另一个assert方法,隐藏了Predef中的那个。它的行为相同,只是如果传递false,它会抛出TestFailedException而不是AssertionError。为什么?因为与AssertionError不同,TestFailedException携带了有关哪一行测试代码在堆栈跟踪中表示失败的准确信息,这可以帮助用户更快地找到错误的代码行。此外,ScalaTest的assert比Scala的assert提供更好的错误消息。

如果您在ScalaTest测试中将前面的布尔表达式left == right传递给assert,将报告一个失败,因为assert被实现为宏,其中包括了报告左值和右值的详细信息。例如,给定上述相同的代码,但使用ScalaTest的断言:

import org.scalatest.Assertions._
val left = 2
val right = 1
assert(left == right)

这个assert抛出的TestFailedException中的详细消息将为:“2 did not equal 1”。

ScalaTest的assert宏通过识别传递给assert的表达式的AST中的模式,并对于有限集的常见表达式,给出了与等效的ScalaTest匹配器表达式相同的错误消息。下面是一些示例,其中a为1,b为2,c为3,d为4,xs为List(a, b, c),num为1.0:

assert(a == b || c >= d)
// 错误消息: 1 did not equal 2, and 3 was not greater than or equal to 4

assert(xs.exists(_ == 4))
// 错误消息: List(1, 2, 3) did not contain 4

assert("hello".startsWith("h") && "goodbye".endsWith("y"))
// 错误消息: "hello" started with "h", but "goodbye" did not end with "y"

assert(num.isInstanceOf[Int])
// 错误消息: 1.0 was not instance of scala.Int

assert(Some(2).isEmpty)
// 错误消息: Some(2) was not empty

对于无法识别的表达式,该宏当前会打印(解糖后的)AST的字符串表示,并添加"was false"。以下是一些未识别表达式的错误消息示例:

assert(None.isDefined)
// 错误消息: scala.None.isDefined was false
assert(xs.exists(i => i > 10))
// 错误消息: xs.exists(((i: Int) => i.>(10))) was false

您可以通过将一个字符串作为assert的第二个参数来增加标准错误消息,例如:

val attempted = 2
assert(attempted == 1, "Execution was attempted " + left + " times instead of 1 time")

使用这种形式的assert,失败报告将更具体地针对您的问题域,从而帮助您调试问题。此Assertions特性还混入了TripleEquals特性,它提供了一个===运算符,可以自定义相等性、使用数字容差进行相等性检查,并在编译时强制执行类型约束。

期望结果

尽管assert宏为Scala的断言机制提供了一种自然、可读性好且提供良好错误消息的扩展,但是当操作数变得冗长时,代码的可读性会降低。此外,对于=比较生成的错误消息没有区分实际值和期望值。操作数只被称为left和right,因为如果一个被命名为expected而另一个被命名为actual,人们很难记住哪个是哪个。为了解决这些断言的限制,Suite包含了一个名为assertResult的方法,可以用作assert的替代方法。要使用assertResult,您将期望值放在assertResult之后的括号中,然后是包含应产生期望值的代码的大括号。例如:

val a = 5
val b = 2
assertResult(2) {
  a - b
}

在这种情况下,期望值为2,被测试的代码是a - b。这个断言将失败,并且在TestFailedException的详细消息中将读到:“Expected 2, but got 3”。

强制失败

如果您只需要使测试失败,可以写:

fail()

或者,如果您希望测试失败并附带一条消息,请写:

fail("I've got a bad feeling about this.")

取得成功

在异步样式的测试中,您必须用Future[Assertion]或Assertion来结束您的测试主体。ScalaTest的断言(包括匹配器表达式)具有Assertion结果类型,因此以断言结束将满足编译器的要求。然而,如果传递给Future.map的测试主体或函数主体不以Assertion类型结尾,您可以通过在测试主体或函数主体的末尾放置succeed来修复类型错误:

succeed // 结果类型为Assertion

期望异常

有时候,您需要测试一个方法在特定情况下是否抛出了预期的异常,比如当给方法传递无效的参数时。您可以按照JUnit 3风格这样做,例如:

val s = "hi"
try {
  s.charAt(-1)
  fail()
}
catch {
  case _: IndexOutOfBoundsException => // 预期的情况,继续执行
}

如果charAt按预期抛出IndexOutOfBoundsException的实例,控制将转移到catch语句块,该语句块什么也不做。然而,如果charAt没有抛出异常,下一条语句fail()将会执行。fail方法始终以TestFailedException完成,从而表示测试失败。

为了更容易表达和阅读这种常见用例,ScalaTest提供了两个方法:assertThrows和intercept。以下是使用assertThrows的示例:

val s = "hi"
assertThrows[IndexOutOfBoundsException] { // 结果类型为Assertion
  s.charAt(-1)
}

这段代码的行为与前面的例子类似。如果charAt抛出了一个IndexOutOfBoundsException实例,assertThrows将返回Succeeded。但是,如果charAt正常完成或抛出了其他异常,assertThrows将以TestFailedException结束。

intercept方法的行为与assertThrows相同,只是intercept不返回Succeeded,而是返回捕获到的异常,以便您可以进一步检查它(如果需要)。例如,您可能需要确保异常中包含的数据具有预期的值。以下是一个示例:

val s = "hi"
val caught =
  intercept[IndexOutOfBoundsException] { // 结果类型为IndexOutOfBoundsException
    s.charAt(-1)
  }
assert(caught.getMessage.indexOf("-1") != -1)

检查代码是否编译通过

在创建库时,通常希望确保某些代码的排列表示潜在的“用户错误”,以便您的库更加抗错。ScalaTest的Assertions特性提供了以下语法来实现此目的:

assertDoesNotCompile("val a: String = 1")

如果要确保由于类型错误(而不是语法错误)而无法编译通过的代码,可以使用:

assertTypeError("val a: String = 1")

请注意,assertTypeError调用仅在给定的代码片段由于类型错误而无法编译通过时才会成功。语法错误仍然会导致抛出TestFailedException。

如果要声明一段代码可以编译通过,您可以使用以下方式更明显地表达:

assertCompiles("val a: Int = 1")

尽管前面三个构造是使用宏实现的,在编译时确定了由字符串表示的代码片段是否能够编译通过,但是错误仍然作为运行时的测试失败报告。

假设

Assertions特性还提供了几种方法,允许您取消测试。如果测试需要一个不可用的资源,您将取消测试。例如,如果测试需要一个外部数据库在线,并且没有在线,可以取消测试以指示由于缺少数据库而无法运行。这样的测试假定数据库可用,您可以在测试开始时使用assume方法来指示这一点,例如:

assume(database.isAvailable)

对于每个重载的assert方法,Assertions特性提供了一个具有相同签名和行为的重载assume方法,除了assume方法会抛出TestCanceledException,而assert方法会抛出TestFailedException。与assert一样,assume隐藏了Predef中的一个Scala方法,该方法执行类似的功能,但会抛出AssertionError。与assert一样,您将从传递给assume的AST中提取的错误消息,可以选择提供一个提示字符串来增加这个错误消息。以下是一些示例:

assume(database.isAvailable, "The database was down again")
assume(database.getAllUsers.count === 9)

强制取消

对于每个重载的fail方法,都有一个相应的cancel方法,具有相同的签名和行为,只是cancel方法抛出TestCanceledException,而fail方法抛出TestFailedException。因此,如果您只需要取消测试,可以写:

cancel()

如果要使用一条消息取消测试,请将消息放在括号中:

cancel("Can't run the test because no internet connection was found")

获取线索

如果您想要比该特性的方法默认提供的信息更多的信息,可以通过多种方式提供一个“clue”字符串。您提供的额外信息(或“线索”)将包含在抛出异常的详细消息中。assert和assertResult还提供了直接包含线索的方法,intercept没有。以下是在assert中直接提供线索的示例:

assert(1 + 1 === 3, "this is a clue")

以及在assertResult中:

assertResult(3, "this is a clue") { 1 + 1 }

前两个语句抛出的异常将在异常的详细消息中包含线索字符串"this is a clue"。要在由失败的assertThrows调用抛出的异常的详细消息中获得相同的线索,需要使用withClue:

withClue("this is a clue") {
  assertThrows[IndexOutOfBoundsException] {
    "hi".charAt(-1)
  }
}

withClue方法只会将线索字符串添加到混入了ModifiableMessage特性的异常类型的详细消息之前。有关更多信息,请参阅ModifiableMessage的文档。如果您希望在代码块之后放置线索字符串,请参阅AppendedClues的文档。

withClue("this is a clue") {
  assertThrows[IndexOutOfBoundsException] {
    "hi".charAt(-1)
  }
}

withClue方法只会将线索字符串添加到混入了ModifiableMessage特性的异常类型的详细消息之前。有关更多信息,请参阅ModifiableMessage的文档。如果您希望在代码块之后放置线索字符串,请参阅AppendedClues的文档。

接下来,了解如何对测试进行标记。

推荐阅读

【ScalaTest系列1】由来场景用法示例详解

【ScalaTest教程2】使用ScalaTest进行测试步骤详解指南

【ScalaTest系列3】 使用断言示例使用词典

【ScalaTest系列4】Sharing fixtures共享测试夹具

【ScalaTest系列5】ScalaTest + JUnit 5+gradle+idea

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

BigDataMLApplication

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值