**斷言**和**單元測試**是檢查軟件的行為是否符合需要的兩種重要方式。本章將展示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)
)
}
~~~
~~~
其中 "==>" 是含義操作符。說明當前左側的表達式為真時,那么右側的表達式也必須為真。
~~~