ScalaTest幾乎已經(jīng)成為Scala語言默認(rèn)的測試框架剪侮,而在JVM平臺(tái)下腊瑟,無論是否使用Scala進(jìn)行開發(fā)还栓,我認(rèn)為仍有嘗試ScalaTest的必要。這主要源于它提供了多種表達(dá)力超強(qiáng)的測試風(fēng)格敬锐,能夠滿足各種層次的需求包括單元測試、BDD呆瞻、驗(yàn)收測試台夺、數(shù)據(jù)驅(qū)動(dòng)測試。正如ScalaTest的創(chuàng)建者Bill Venners所說:
A guiding design principle of ScalaTest is that different people on a team should be able look at each others test code and know immediately what’s going on.
ScalaTest is designed to make it easy for you to customize your testing tool to meet your current needs, and for the built-in traits at least, make it easy for anyone who comes along later to read and understand your code.
UT與IT的風(fēng)格選擇
ScalaTest一共提供了七種測試風(fēng)格痴脾,分別為:FunSuite颤介,F(xiàn)latSpec,F(xiàn)unSpec赞赖,WordSpec滚朵,F(xiàn)reeSpec,PropSpec和FeatureSpec前域。這就好像使用相同的原料做成不同美味乃至不同菜系的佳肴辕近,你可以根據(jù)自己的口味進(jìn)行選擇。以我個(gè)人的偏好來看匿垄,我傾向于選擇FlatSpec或FunSpec(類似Ruby下的RSpec)來編寫單元測試與集成測試移宅。雖然FunSuite的方式要更靈活,而且更符合傳統(tǒng)測試方法的風(fēng)格椿疗,區(qū)別僅在于test()方法可以接受一個(gè)閉包漏峰,但壞處恰恰就是它太靈活了。而FlatSpec和FunSpec則通過提供諸如it届榄、should浅乔、describe等方法,來規(guī)定書寫測試的一種模式铝条,例如前者明顯的“主-謂-賓”結(jié)構(gòu)靖苇,后者清晰的分級(jí)式結(jié)構(gòu),都可以使團(tuán)隊(duì)的測試更加規(guī)范攻晒。如下是ScalaTest官方網(wǎng)站的提供的FunSuite顾复、FlatSpec和FunSpec的三種風(fēng)格樣例。
//FunSuite
import org.scalatest.FunSuite
class SetSuite extends FunSuite {
test("An empty Set should have size 0") {
assert(Set.empty.size == 0)
}
test("Invoking head on an empty Set should produce NoSuchElementException") {
intercept[NoSuchElementException] {
Set.empty.head
}
}
}
//FlatSpec
import org.scalatest.FlatSpec
class SetSpec extends FlatSpec {
"An empty Set" should "have size 0" in {
assert(Set.empty.size == 0)
}
it should "produce NoSuchElementException when head is invoked" in {
intercept[NoSuchElementException] {
Set.empty.head
}
}
}
//FunSpec
import org.scalatest.FunSpec
class SetSpec extends FunSpec {
describe("A Set") {
describe("when empty") {
it("should have size 0") {
assert(Set.empty.size == 0)
}
it("should produce NoSuchElementException when head is invoked") {
intercept[NoSuchElementException] {
Set.empty.head
}
}
}
}
}
至于WordSpec和FreeSpec鲁捏,要么太復(fù)雜芯砸,要么可讀性稍差萧芙,要么慣用法風(fēng)格有些混雜,個(gè)人認(rèn)為都不是太好的選擇假丧,除非你已經(jīng)習(xí)慣了這種風(fēng)格管钳。
數(shù)據(jù)驅(qū)動(dòng)測試風(fēng)格
JUnit對(duì)類似表數(shù)據(jù)的Fixture準(zhǔn)備提供了Parameterized支持英染,但非常不直觀,而且還需要為測試編寫構(gòu)造函數(shù),然后定義一個(gè)帶有@Parameters標(biāo)記的靜態(tài)方法狡刘。TestNG的DataProvider略好,但通過在測試方法上指定DataProvider的方式走触,仍然不盡如人意澎剥。ScalaTest提供的PropSpec充分利用了Scala函數(shù)式語言的特性,使得代碼更簡單谋梭,表達(dá)性也更強(qiáng):
import org.scalatest._
import prop._
import scala.collection.immutable._
class SetSpec extends PropSpec with TableDrivenPropertyChecks with 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 }
}
}
}
驗(yàn)收測試風(fēng)格
我們會(huì)推薦由PO(或者需求分析人員BA)與測試人員結(jié)對(duì)編寫驗(yàn)收測試的業(yè)務(wù)場景信峻,然后由開發(fā)人員和測試人員結(jié)對(duì)實(shí)現(xiàn)該場景。Cocumber瓮床、JBehave盹舞、Twist乃至Robot、Fitness都可以用于編寫這樣的驗(yàn)收測試(Fitness與Robot更接近實(shí)例化需求的方式)隘庄。這些工具有一個(gè)特點(diǎn)是業(yè)務(wù)場景與測試支持代碼完全是分開的踢步。例如Cucumber將業(yè)務(wù)場景放到feature文件中,而將測試支持代碼放到rb文件中丑掺。JBehave類似获印。這樣的好處是feature文件很干凈,很純粹吼鱼,與技術(shù)實(shí)現(xiàn)沒有任何關(guān)系蓬豁,且有利于生成Living Document。然而菇肃,這種分離方式在帶來良好可讀性的同時(shí)地粪,也帶來維護(hù)成本的增加。
ScalaTest在提供類似Feature的驗(yàn)收測試Spec時(shí)琐谤,并沒有將業(yè)務(wù)場景與測試支持代碼分開蟆技,而是采用了混合的方式來表現(xiàn):
import org.scalatest.{ShouldMatchers, GivenWhenThen, FeatureSpec}
class TVSetTest extends FeatureSpec with GivenWhenThen with ShouldMatchers{
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 press power button when TV is off") {
Given("a TV set that is switched off")
val tv = new TVSet
tv.isOn should be (false)
When("The power button is pressed")
tv.pressPowerButton
Then("The TV should switch on")
tv.isOn should be (true)
}
}
}
ScalaTest的FeatureSpec支持常見的Given-When-Then模式。在上面的代碼段中斗忌,info提供了對(duì)Feature的基本描述质礼,然后提供了feature與scenario兩個(gè)層級(jí)。熟悉Cucumber和JBehave的人對(duì)此應(yīng)該不會(huì)陌生织阳。測試支持代碼直接寫在Given眶蕉、When、Then方法下唧躲,因而針對(duì)同一個(gè)Feature造挽,只產(chǎn)生一個(gè)scala文件碱璃。這就意味著測試支持代碼與自然語言描述是處于同一級(jí)的,準(zhǔn)確地說饭入,他們其實(shí)就屬于同一個(gè)測試嵌器。開發(fā)時(shí),PO(或者需求)與測試可以先編寫FeatureSpec的骨架谐丢,即info-feature-scenario以及Given-When-Then部分爽航。一旦編寫好這個(gè)FeatureSpec,就可以提交到版本管理庫乾忱。當(dāng)開發(fā)人員與需求讥珍、測試一起Kick Off要做的Story時(shí),就可以根據(jù)這個(gè)FeatureSpec進(jìn)行饭耳,然后串述,要求開發(fā)人員在完成Story的實(shí)現(xiàn)前,與測試結(jié)對(duì)完成它的測試實(shí)現(xiàn)代碼寞肖。
由于ScalaTest還提供了Tag等功能,我們還可以通過對(duì)測試提取基類或者Trait有效地對(duì)這些測試進(jìn)行重用衰腌,保證測試代碼的可維護(hù)性新蟆。由于只需要維護(hù)一個(gè)scala,成本會(huì)降低許多右蕊,也不需要在業(yè)務(wù)場景和測試支持代碼之間跳轉(zhuǎn)琼稻,降低維護(hù)的難度。唯一的缺點(diǎn)是它天然不支持Living Document饶囚。但是我們發(fā)現(xiàn)這些自然語言描述實(shí)則都集中在FeatureSpec提供的方法中帕翻,我們完全可以自行開發(fā)工具或插件,完成對(duì)場景描述以及步驟的提取萝风,生成我們需要的文檔嘀掸。
說明:文章的代碼片段全部來自ScalaTest官方網(wǎng)站。