ScalaTest的測試風(fēng)格

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)站。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末规惰,一起剝皮案震驚了整個(gè)濱河市睬塌,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌歇万,老刑警劉巖揩晴,帶你破解...
    沈念sama閱讀 218,858評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異贪磺,居然都是意外死亡硫兰,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門寒锚,熙熙樓的掌柜王于貴愁眉苦臉地迎上來劫映,“玉大人违孝,你說我怎么就攤上這事∷昭校” “怎么了等浊?”我有些...
    開封第一講書人閱讀 165,282評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長摹蘑。 經(jīng)常有香客問我筹燕,道長,這世上最難降的妖魔是什么衅鹿? 我笑而不...
    開封第一講書人閱讀 58,842評(píng)論 1 295
  • 正文 為了忘掉前任撒踪,我火速辦了婚禮,結(jié)果婚禮上大渤,老公的妹妹穿的比我還像新娘制妄。我一直安慰自己,他們只是感情好泵三,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,857評(píng)論 6 392
  • 文/花漫 我一把揭開白布耕捞。 她就那樣靜靜地躺著,像睡著了一般烫幕。 火紅的嫁衣襯著肌膚如雪俺抽。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,679評(píng)論 1 305
  • 那天较曼,我揣著相機(jī)與錄音磷斧,去河邊找鬼。 笑死捷犹,一個(gè)胖子當(dāng)著我的面吹牛弛饭,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播萍歉,決...
    沈念sama閱讀 40,406評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼侣颂,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼!你這毒婦竟也來了翠桦?” 一聲冷哼從身側(cè)響起横蜒,我...
    開封第一講書人閱讀 39,311評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎销凑,沒想到半個(gè)月后丛晌,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,767評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡斗幼,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評(píng)論 3 336
  • 正文 我和宋清朗相戀三年澎蛛,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蜕窿。...
    茶點(diǎn)故事閱讀 40,090評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡谋逻,死狀恐怖呆馁,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情毁兆,我是刑警寧澤浙滤,帶...
    沈念sama閱讀 35,785評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站气堕,受9級(jí)特大地震影響纺腊,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜茎芭,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,420評(píng)論 3 331
  • 文/蒙蒙 一揖膜、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧梅桩,春花似錦壹粟、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,988評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至垦页,卻和暖如春幸撕,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背外臂。 一陣腳步聲響...
    開封第一講書人閱讀 33,101評(píng)論 1 271
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留律胀,地道東北人宋光。 一個(gè)月前我還...
    沈念sama閱讀 48,298評(píng)論 3 372
  • 正文 我出身青樓,卻偏偏與公主長得像炭菌,于是被迫代替她去往敵國和親罪佳。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,033評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理黑低,服務(wù)發(fā)現(xiàn)赘艳,斷路器,智...
    卡卡羅2017閱讀 134,659評(píng)論 18 139
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,163評(píng)論 25 707
  • 文章來自:http://blog.csdn.net/mj813/article/details/52451355 ...
    好大一只鵬閱讀 9,193評(píng)論 2 126
  • 1. 等閑變卻故人心克握,卻道故人心易變蕾管。 來年陌生的,是昨日最親的某某某菩暗。 2. 記得以前臨近畢業(yè)的時(shí)候掰曾,班里總是會(huì)...
    我在和誰說晚安閱讀 542評(píng)論 1 2
  • 大家好,又到了一周的總結(jié)時(shí)間停团,就像胡班所說的旷坦,前期寫東西不費(fèi)勁掏熬,但時(shí)間一長就不知道寫什么了。我好像已經(jīng)黔驢...
    優(yōu)一lily閱讀 314評(píng)論 7 3