第十四章 断言和单元测试
最后更新于:2022-04-01 20:16:02
**断言**和**单元测试**是检查软件的行为是否符合需要的两种重要方式。本章将展示Scala中编写和执行它们的若干选择。
-
断言
- 是对预定义方法assert的调用。
- 表现形式:
- assert(condition) //在条件不成立时抛出AssertionError。还可以用在某方法执行结束处且在返回结果之前,用来检查作为返回结果的值是否满足条件,如果断言成立,则返回val值。ensuring方法可以简化这些操作,在此不举例说明。
- assert(condition, explanation) //将在条件不成立时抛出指定的explanation(Any类型)
注:断言可以使用JVM的-ea和-da命令行标志开放和禁止。开放时,每个断言被当作对使用软件运行时产生的实际数据进行的小测试。
-
单元测试
- Scala实现单元测试的方式有很多
- 从Java实现的工具:JUnit和TestNG
- Scala编写的新工具:ScalaTest、specs(本人比较喜欢的测试方法)和ScalaCheck
- 最简单的方法
- 创建扩展org.scalatest.Suite的类并在其中定义测试方法
- Suite代表一个测试集
- 测试方法以”test”开头
- 在Scala解释器中可以使用execute方法运行Suite
- execute方法可以在子类中重载,因此ScalaTest可以为不同风格的测试提供便利
- ScalaTest中的FunSuite特质重载了execute方法,从而可以用函数值的方式来进行测试,而不需要再用方法定义的方式。这样就不需要再用test开头命名所有测试。
-
翔实的失败报告
- 在ScalaTest中使用 “===” 这样的符号,在断言失败时,会返回详细的错误信息。它只能说明左侧的操作元不等于右侧的操作元,如“3 did not equal 2”
- 如果需要强调这种区分,可以使用ScalaTest中的expect方法,该方法会得到更加详细的信息。如“Expected 2, but got 3”
- 如果想检查方法是否出现了期待的异常,可以使用ScalaTest的intercept方法。
这些方法的目的都在于帮助我们写出更加简明清晰的基于断言的测试。
-
JUnit代码
~~~
import junit.framework.TestCase
import junit.framework.Assert.assertEquals
import junit.framework.Assert.fail
import Element.elem
class ElementTestCase extends TestCase {
def testUniformElement() {
val ele = elem('x', 2, 3)
assertEquals(2, ele.width)
assertEquals(3, ele.height)
try {
elem('x', 2, 3)
fail()
}
catch {
case e: IllegalArgumentException => // expected
}
}
}
~~~
- TestNG代码
~~~
import org.testng.annotations.Test
import org.testng.Assert.assertEquals
import Element.elem
class ElementTests {
@Test def verifyUniformElement() {
val ele = elem('x', 2, 3)
assertEquals(ele.width, 2)
assertEquals(ele.height, 3)
}
@Test {
val expectedExceptions =
Array(classOf[IllegalArgumentException])
}
def elemShouldThrowIAE() { elem('x', 2, 3) }
}
~~~
- 规格测试
- ScalaTest中包含了Spec,便于行为驱动开发(BDD)这种测试风格的测试。
- Spec包括两个部分
- 描述部分
- 写为describe,然后是带括号的字符串和代码块
- 它描述了要被规格化和测试的“目标”
- 规格部分
- 写为it,然后是带括号的字符串和代码块
- (在字符串中)规格化了目标的一小块行为,并(在代码快中)提供了验证这种行为的代码
- 示例代码
~~~
import org.scalatest.Spec
class ElementSpec extends Spec {
describe("A UniformElement") {
it("should have a width equal to the passed value") {
val ele = elem('x', 2, 3)
assert(ele.width === 2)
}
it("should have a height equal to the passed value") {
val ele = elem('x', 2, 3)
assert(ele.height === 3)
}
it("should throw an IAE if passed a negative width") {
intercept(classOf[IllegalArgumentException]) {
elem('x', 2, 3)
}
}
}
}
~~~
~~~
在解释器中调用execute方法,产生的输出读起来很像规格说明。
~~~
- 基于属性的测试
- ScalaCheck能指定待测代码须遵循的属性。
- 对于每个属性,ScalaCheck将产生测试数据并运行测试以验证其是否正确
~~~
import org.scalatest.prop.FunSuite
import org.scalacheck.Prop._
import Element.elem
class ElementSuite extends FunSuite {
test("elem result should have passed width", (w: Int) =>
w > 0 ==> (elem('x', w, 3).width == w)
)
test("elem result should have passed height", (h: Int) =>
h > 0 ==> (elem('x', 2, h).height == h)
)
}
~~~
~~~
其中 "==>" 是含义操作符。说明当前左侧的表达式为真时,那么右侧的表达式也必须为真。
~~~
';