[轉] ScalaTest測試框架


[From] https://blog.csdn.net/hany3000/article/details/51033610

 

ScalaTest測試框架

ScalaTest 是一個開源測試框架,旨在令測試更加高效。其開發者是Bill Venners(Artima主編)。Bill Venners這樣描述ScalaTest:


ScalaTest是比JUnit和TestNG更加高階的測試編寫工具,這個Scala應用在JVM上運行,可以測試Scala以及Java代碼。除了與JUnit和TestNG的深層集成外,還支持Ant任務,與maven集成,並包括了流行的Java mocking框架JMock、EasyMock以及Mockito的語法增強。通過JUnit集成,ScalaTest可以輕松地在Eclipse、NetBeans以及IntelliJ IDEA等IDE,以及Infinitest等生產工具中使用。


本文將簡單介紹如何使用 ScalaTest 測試框架對我們的Scala代碼進行測試。

部分例子來自於 《Testing in Scala》 Daniel Hinojosa O’REILLY

閱讀本文,最好對下面列出的內容有一些了解:

有了對 SBT 和 Scala 的一些了解作為前置知識,看下面的內容會比較輕松。(部分單詞為了表達上的准確並沒有翻譯成中文)

簡單的例子

下面這個例子來自於Testing in Scala一書,先來直觀的感覺一下ScalaTest這個測試框架。

例子中用到的類說明:
Artist類:

// Artist類有兩個屬性 firstNam表示名字 和 lastName姓氏
classArtist(val firstName: String, val lastName: String)

Albumo類:

// Album 有三個屬性 title表示專輯名稱 year表示發行年份 artist表示專輯作者
classAlbum(val title: String, val year: Int, val artist: Artist)

測試代碼如下:

import org . scalatest .{ FunSpec , ShouldMatchers }
 
classAlbumTestextendsFunSpecwithShouldMatchers {
describe ( "An Album" ) {
it ( "can add an Artist object to the album" ) {
val album = new Album ( "Thriller" , 1981 , new Artist ( "Michael" , "Jackson" ))
album . artist . firstName should be ( "Michael" )
}
}
}

在終端輸入test-only AlbumTest回車,得到如下輸出:

[ info ] AlbumTest :
[ info ] An Album
[ info ] - can add an Artist object to the album
[ info ] Run completed in 197 milliseconds .
[ info ] Total number of tests run : 1
[ info ] Suites : completed 1 , aborted 0
[ info ] Tests : succeeded 1 , failed 0 , canceled 0 , ignored 0 , pending 0
[ info ] All tests passed .
[ success ] Total time : 0 s , completed May 18 , 2015 9 : 47 : 05 PM

根據輸入信息可以知道通過測試為1個,則說明我們要驗證的can add an Artist object to the album這個操作是可以執行的。

有了直觀的認識,下面來詳細的說明ScalaTest這個測試框架。

在SBT項目中使用ScalaTest

本文中討論的項目都以SBT構建,相關知識可以在 SBT官網 學習。

要在SBT的項目中使用ScalaTest測試框架,需要在build.sbt文件中添加ScalaTest的依賴。

可以使用

libraryDependencies += "org.scalatest" %% "scalatest" % "2.2.4"

讓SBT自動尋最合適的編譯版本或者使用

libraryDependencies += "org.scalatest" % "scalatest_2.10.4" % "2.2.4"

手動指定編譯版本來添加依賴。

這里的區別僅在於%% "scalatest"% "scalatest_2.10.4",關於區別的詳情,請參看SBT官網 

Machers

在前面的測試代碼

import org . scalatest .{ FunSpec , ShouldMatchers }
 
classAlbumTestextendsFunSpecwithShouldMatchers {
describe ( "An Album" ) {
it ( "can add an Artist object to the album" ) {
val album = new Album ( "Thriller" , 1981 , new Artist ( "Michael" , "Jackson" ))
album . artist . firstName should be ( "Michael" )
}
}
}

里,我們作出了一個斷言:Thriller這張專輯的作者的firstName是Michael。可以看到在代碼的第7行有一個should,這就是ScalaTest中的兩大類Macher之一的Should Macher,另一類是Must Macher,用must來表示。

Macher的類型

前面講到ScalaTest中的Macher分為兩大類--Should MacherMust Macher。它們的區別僅在測試報告中體現,所以在后面不同情形下的Macher的說明中,只以Should Macher為例。

Simple Macher

Simple Macher就是在兩個值之間使用一個斷言,如下例:

val list = 2 :: 4 :: 5 :: Nil
list . size should be ( 3 )

這里使用了Should Macher,來判斷list.size是否和3相等。如果list.siz的大小不為3,則測試無法通過。

這里有幾點需要注意的:

  • 右邊的值需要使用圓括號()括起來
// 這種寫法會導致編譯錯誤
list.size should be 3
  • 可以將be替換為equal
// 這種寫法和 list.size should be(3) 等價
list.size should equal( 3 )
  • 在ScalaTest中基本不使用==!=進行條件斷言

如果上面的代碼寫成:

list . size == 5

這樣寫只會驗證list.size == 5這是表達式是true或者false,並不會進行斷言的驗證,因而不會有TestFailedException異常拋出,測試將繼續運行。

String Macher

String Macher為字符串斷言提供了一些有用的方法,利用這些方法可以判斷一個字符串是否包含另一個字符串、一個字符串以某個字符串開頭或結尾、一個字符串是否能匹配一個正則表達式等。如下面的例子:

val string = """I fell into a burning ring of fire.
I went down, down, down and the flames went higher"""
 
// 以 "I fell" 字符串開頭
string should startWith( "I fell" )
// 以 "higher" 字符串結尾
string should endWith( "higher" )
// 不以 "My favorite friend, the end" 字符串結尾
string should not endWith "My favorite friend, the end"
// 包含 "down, down, down" 字符串
string should include( "down, down, down" )
// 不包含 "Great balls of fire" 字符串
string should not include ( "Great balls of fire" )
 
// 以匹配正則表達式 "I.fel+" 的字符串開頭
string should startWith regex ( "I.fel+" )
// 以匹配正則表達式 "h.{4}r" 的字符串結尾
string should endWith regex ( "h.{4}r" )
// 不以匹配正則表達式 "\\d{5}" 的字符串結尾
string should not endWith regex( "\\d{5}" )
// 包含匹配正則表達式 "flames?" 的字符串
string should include regex ( "flames?" )
 
// 完全匹配正則表達式 "I(.|\n|\S)*higher"
string should fullyMatch regex ( """I(.|\n|\S)*higher""" )

Relational Operator Macher

ScalaTest框架支持關系運算符,如下面的例子:

val answerToLife = 42
answerToLife should be < ( 50 )
answerToLife should not be > ( 50 )
answerToLife should be > ( 3 )
answerToLife should be <= ( 100 )
answerToLife should be >= ( 0 )
answerToLife should be === ( 42 )
answerToLife should not be === ( 400 )

上面的例子中,要解釋的應該只有===這個運算符了,這個運算符驗等它左邊的部分是否等於右邊的部分。在前面說過,==只是驗證值是否相等並不會驗證斷言,因此在涉及驗證是否相等時最好使用should beshould equal===

Floating-point Macher

浮點數在JVM中實際上是很復雜的,考慮一個算式0.9 - 0.8,在我們看來結果應該是0.1,實際上在REPL中執行這個運算,會得到如下的結果:

scala > 0.9 - 0.8
res0 : Double = 0.06565656565659998

顯然,計算結果是有誤差的。在ScalaTest框架中,提供了一個plusOrMinus方法來給斷言提供一個誤差允許范圍。如下面的例子:

// 允許右邊的范圍在 0.1 - 0.01 到 0.1 + 0.01 之間
( 0.9 - 0.8 ) should be ( 0.1 plusOrMinus .01 )
// 允許右邊的范圍在 40 - 0.3 到 40 + 0.3 之間
( 0.4 + 0.1 ) should not be ( 40.00 plusOrMinus .30 )

實際上上面例子的第4行 0.4 + 0.1 在REPL中會輸出一個准確的值 0.5

Reference Macher

在Scala中,==運算符不會驗證引用是否相等,要驗證引用是否相等,在ScalaTest中提供了theSameInstanceAs方法,如下面的例子:

val garthBrooks = new Artist ( "Garth" , "Brooks" )
val chrisGaines = garthBrooks
 
garthBrooks should be theSameInstanceAs ( chrisGaines )
 
val debbieHarry = new Artist ( "Debbie" , "Harry" )
garthBrooks should not be theSameInstanceAs ( debbieHarry )

Iterable Macher

對於Scala的可遍歷集合類型,ScalaTest框架提供了多種進行斷言的方法。如下面的例子:

List () should be ( 'empty )
8 :: 6 :: 7 :: 5 :: 3 :: 0 :: 9 :: Nil should contain( 7 )

上面代碼的第一行使用了一個Scala的符號--'empty,Scala中的符號是不可變的占位符。

Seq and Traversable Macher

對於SeqTraversable類型的Scala變量,SalaTest提供了lengthsize這兩個Macher來判定它們的大小(長度)。如下面的例子:

( 1 to 9 ) should have length ( 9 )
( 20 to 60 by 2 ) should have size ( 21 )

實際上要據Scala文檔,lengthsize是等價的,使用哪個完全看你的偏好。

Map Macher

而對於Map類型的Scala變量,ScalaTest提供了一些特殊的方法,可以用來判斷一個key或者value是否在Map中。如下面的例子:

val map = Map ( "Jimmy Page" -> "Led Zeppelin" , "Sting" -> "The Police" , "Aimee Mann" -> "Til\' Tuesday" )
 
// map中應該包含值為 "Sting" 的key
map should contain key ( "Sting" )
// map中應該包含值為 "Led Zeppelin" 的value
map should contain value ( "Led Zeppelin" )
// map中應該不包含值為 "Brian May" 的key
map should not contain key( "Brian May" )

Compound Macher

ScalaTest中的andor方法可以用來在測試中使用組合的斷言。如下面的例子:

val redHotChiliPeppers = List ( "Anthony Kiedis" , "Flea" , "Chad Smith" , "Josh Klinghoffer" )
 
// redHotChiliPeppers變量中應該包含 "Anthony Kiedis" 不應該包含 "John Frusciante" 和 "Dave Navarro"
redHotChiliPeppers should (contain( "Anthony Kiedis" ) and (not contain ( "John Frusciante" ) or contain( "Dave Navarro" )))

在使用組合的Macher時,圓括號()的使用可能會造成一此困擾,下面是一些規則:

  • andor的斷言必須使用圓括號()包圍起來
  • 斷言的右邊必須使用圓括號()包圍起來

以下面的例子來說明上面兩條規則:

// 這會導致編譯錯誤
redHotChiliPeppers should not contain "The Edge" or contain "Kenny G"
 
// 這也會導致編譯錯誤
redHotChiliPeppers should not (contain "The Edge" or contain "Kenny G" )
 
// 這是正確的寫法
redHotChiliPeppers should not (contain ( "The Edge" ) or contain ( "Kenny G" ))

除了上面的兩條規則,還有一點需要注意的:使用組合Macherandor並不是短路的。換句話說,就是所有的子句都會被驗證。如下面的例子:

var total = 3
redHotChiliPeppers should not ( contain ( "The Edge" ) or contain { total += 6 ; "Kenny G" })
 
total should be ( 9 )

如果發生短路total should be (9)這里肯定不能通過,not contain ("The Edge")已經是true,則or運算沒必要再運行。但執行完這個測試發現total的值已經是9,說明此時並沒有發生短路

Scala中有一個Option類型,其值可以為SomeNone,因此,在Scala中基本不會使用null來做處理。ScalaTest是支持Java的,因此在有些情況下需要用到null。如下面的例子:

gorillaz should ( not be ( null ) and contain ( "Damon Albarn" ))

上面的例子中如果gorillaznull則會拋出NullPointerException異常。更好的最法是將組合Macher拆開,變成下面的形式:

gorillaz should not be ( null )
gorillaz should contain ( "Damon Albarn" )

經過上面的處理,如果gorillaznull,測試不會通過,但其它的測試不會拋出NullPointerException異常。

Property Macher

ScalaTest也提供了一個很不錯的方式來驗證對象的屬性,如下面的例子:

import scala . collection . mutable . WrappedArray
 
val album = new Album ( "Blizzard of Ozz" , 1980 , new Artist ( "Ozzy" , "Osbourne" ))
album should have (
'title ( "Blizzard of Ozz" ),
'year ( 1980 ),
'artist ( new Artist( "Ozzy" , "Osbourne" ))
)

屬性Macher可以將對象的屬性取出來,然后對這些屬性進行斷言。這里將屬性取出來實際上是使用了對象的getter方法,所以需要保證在對象中有getter方法並且能調用到。

java.util.Collection.machers

ScalaTest是Java友好的,因而它可以像在Scala集合上一樣在Java集合上做斷言,下面的例子使用了一些在之前用到的方法。

import java . util .{ List => JList , ArrayList => JArrayList , Map => JMap , HashMap => JHashMap }
 
val jList : JList [ Int ] = new JArrayList [ Int ]( 20 )
 
jList . add ( 3 ); jList . add ( 6 ); jList . add ( 9 )
 
val emptyJList : JList [ Int ] = new JArrayList [ Int ]()
 
emptyJList should be ( 'empty )
jList should have length ( 3 )
jList should have size ( 3 )
jList should contain( 6 )
jList should not contain ( 10 )
 
val backupBands: JMap[String, String] = new JHashMap()
 
backupBands.put( "Joan Jett" , "Blackhearts" )
backupBands.put( "Tom Petty" , "Heartbreakers" )
backupBands should contain key ( "Joan Jett" )
backupBands should contain value ( "Heartbreakers" )
backupBands should not contain key( "John Lydon" )

上面例子中的一些Scala語法在這里就不多說了,可以看到在ScalaTest在Java集合上的操作和在Scala集合上的操作是一樣的。

Must Macher

在前面的一些例子中,都是使用Should Machershould這個關鍵字,實際上可以把前面的should都換成must。如下面的一些例子:

val list = 2 :: 4 :: 5 :: Nil
list . size must be ( 3 )
 
val string = """I fell into a burning ring of fire.
I went down, down, down and the flames went higher"""
string must startWith regex ( "I.fel+" )
string must endWith regex ( "h.{4}r" )
 
val answerToLife = 42
answerToLife must be < ( 50 )
answerToLife must not be >( 50 )
 
val garthBrooks = new Artist ( "Garth" , "Brooks" )
val chrisGaines = garthBrooks
val debbieHarry = new Artist ( "Debbie" , "Harry" )
garthBrooks must be theSameInstanceAs ( chrisGaines )
 
( 0.9 - 0.8 ) must be ( 0.1 plusOrMinus .01 )
 
List () must be ( 'empty )
1 :: 2 :: 3 :: Nil must contain( 3 )
( 1 to 9 ).toList must have length ( 9 )
( 20 to 60 by 2 ).toList must have size ( 21 )
 
val map = Map( "Jimmy Page" -> "Led Zeppelin" , "Sting" -> "The Police" , "Aimee Mann" -> "Til\' Tuesday" )
map must contain key ( "Sting" )
map must contain value ( "Led Zeppelin" )
map must not contain key( "Brian May" )
 
val redHotChiliPeppers = List( "Anthony Kiedis" , "Flea" , "Chad Smith" , "Josh Klinghoffer" )
redHotChiliPeppers must (contain( "Anthony Kiedis" ) and (not contain ( "John Frusciante" ) or contain( "Dave Navarro" )))

上面的這些例子只是把之前的例子中的should換成了must,這完全是等價的。正如之前說過的,Should MacherMust Macher的不同之處只在測試報告中體現。

異常處理

ScalaTest中,有兩中方式來驗證異常的拋出和捕獲。intercept blockevaluating block

intercept block
intercept block這種方式把任何可能拋出異常的代碼放入一個intercept代碼塊中,如果代碼塊沒有拋出異常,則測試失敗。如下面的例子:

"An album" should {
"throw an IllegalArgumentException if there are no acts when created" in {
intercept [ IllegalArgumentException ] {
new Album ( "The Joy of Listening to Nothing" , 1980 , List ())
}
}
}

上面的例子表示在創建Album時這么寫會捕獲一個IllegalArgumentException.而使用了一個List()只是為了說明在創建Album時不創建Artist,實際上代碼在編譯時就會報錯。

evaluating block
evaluating block這種方式將可能拋出異常的代碼放入一個evaluating代碼塊中,使用一個shouldmust加上一個produce關鍵字來指明異常的類型。如下面的例子:

val thrownException = evaluating { new Album ( "The Joy of Listening to Nothing" , 1980 , List ())} must produce [ IllegalArgumentException ]
 
thrownException . getMessage () must be ( "An Artist is required" )

intercept blockevaluating block其實做的是同樣的事情,但是使用evaluating block方式可以捕獲到拋出的異常。如果一次調用可能拋出多個不同的異常,那么這種方法的好處就是可以捕獲到異常然后判斷出拋出的是哪個異常。如果引起某個異常的原因有多個,如上例中的IllegalArgumentException可能是Artist未創建造成的,也可能是year這個參數不合法造成的。那么在這種情況下要確保異常的信息是An Artist is required,這就只能使用evaluating block這種方式了。

Informers

Informer在ScalaTest中跟debug語句很相似,但它可以放在任何地方來輸出一些跟測試相關的信息。使用Informer是很簡單的,只要調用info(String)這上方法就好了。如下面的例子:

import org . scalatest .{ FunSpec , ShouldMatchers }
 
classAlbumTestextendsFunSpecwithShouldMatchers {
describe ( "An Album" ) {
it ( "can add an Artist object to the album" ) {
val album = new Album ( "Thriller" , 1981 , new Artist ( "Michael" , "Jackson" ))
info ( "Test firstName should be Michael" )
album . artist . firstName should be ( "Thriller" )
}
}
}

運行測試,得到如下輸出結果:

[ info ] AlbumTest :
[ info ] An Album
[ info ] - can add an Artist object to the album
[ info ] + Test firstName should be Michael
[ info ] Run completed in 231 milliseconds .
[ info ] Total number of tests run : 1
[ info ] Suites : completed 1 , aborted 0
[ info ] Tests : succeeded 1 , failed 0 , canceled 0 , ignored 0 , pending 0
[ info ] All tests passed .
[ success ] Total time : 0 s , completed May 19 , 2015 9 : 19 : 28 PM

可以和簡單的例子中的輸出結果進行比較,發現在第4行多出了一個+ Test firstName should be Michael,這里就是Informer的輸出了,以+開頭。

GivenWhenThen

在了解了Informer之后,GivenWhenThen就比較簡單了。實際上,任何一個過程者可以被描述為Given--When--ThenGiven相當於所給的前置條件,When相當於產生了某個動作或處於某種條件下,Then表示前面兩個條件產生的結果。如下面的例子:

import core .{ Artist , Album }
import org . scalatest .{ GivenWhenThen , ShouldMatchers , FunSpec }
 
classAlbumSpecextendsFunSpecwithShouldMatcherswithGivenWhenThen {
describe ( "An Album" ) {
it ( "can add an Artist to the album at construction time" ) {
Given ( "The album Thriller by Michael Jackson" )
val album = new Album ( "Thriller" , 1981 , new Artist ( "Michael" , "Jackson" ))
 
When ( "the album\'s artist is obtained" )
val artist = album . artist
 
Then ( "the artist obtained should be an instance of Artist" )
artist . isInstanceOf [ Artist ] should be ( true )
 
and ( "the artist's first name and last name should be Michael Jackson" )
artist . firstName should be ( "Michael" )
artist . lastName should be ( "Jackson" )
}
}

運行上面的測試,將產生如下的結果:

[ info ] AlbumSpec :
[ info ] An Album
[ info ] - can add an Artist to the album at construction time
[ info ] + Given The album Thriller by Michael Jackson
[ info ] + When the album 's artist is obtained
[info] + Then the artist obtained should be an instance of Artist
[info] + And the artist' s first name and last name should be Michael Jackson
[ info ] Run completed in 216 milliseconds .
[ info ] Total number of tests run : 1
[ info ] Suites : completed 1 , aborted 0
[ info ] Tests : succeeded 1 , failed 0 , canceled 0 , ignored 0 , pending 0
[ info ] All tests passed .
[ success ] Total time : 0 s , completed May 19 , 2015 9 : 31 : 47 PM

可以看到GivenWhenThenand里面的字符串都是以Informer的形式輸出的,使用一個and將測試的內容分開了,加強了可讀性。而GivenWhenThen是一個特質,可以被混入任何的類。GivenWhenThen使測試變得結構化,使得在測試時可以很好的組織思想。

待測試

待測試(Pending Test)這個思想我覺得在實際中會用的比較多。pending是一個占位符,可以將尚未實現或定義的測試以pending來填充。Pending Test實際上就是利用pending來將測試標記為TODO的。如下面的例子:

classAlbumSpecextendsFunSpecwithShouldMatcherswithGivenWhenThen {
describe ( "An Album" ) {
it ( "can add an Artist to the album at construction time" ) { pending }
it ( "can add opt to not have any artists at construction time" ) { pending }
}
}

運行測試,得到如下結果:

[ info ] AlbumSpec :
[ info ] An Album
[ info ]- can add an Artist to the album at construction time ( pending )
[ info ]- can add opt to not have any artists at construction time ( pending )

可以看到,測試都被標記為了pending

我們可以將pending關鍵字一直放在測試的最下面,直到一個測試完全的寫完。如下面的例子:

classAlbumSpecextendsFunSpecwithShouldMatchers {
describe ( "An Album" ) {
it ( "can add an Artist to the album at construction time" ) {
val album = new Album ( "Thriller" , 1981 , new Artist ( "Michael" , "Jackson" ))
info ( "Making sure that Michael Jackson is indeed the artist of Thriller" )
pending
}
it ( "can add opt to not have any artists at construction time" ) { pending }
}
}

運行測試,如我們所料,將輸出下面的結果:

[ info ] AlbumSpec :
[ info ] An Album
[ info ]- can add an Artist to the album at construction time ( pending )
[ info ] + Making sure that Michael Jackson is indeed the artist of Thriller
[ info ]- can add opt to not have any artists at construction time ( pending )

忽略測試

可能有這么一種情境:某個測試案例,可能由於生產代碼被修改而處於一種可有可無的狀態。如果留着,在進行測試的時候浪費執行時間,如果刪除又怕在后期還要使用到。此時可以使用ignore來標記該測試,這樣在執行test指令時將不會運行它,但同時又將它保存下來了。如下面的例子:

import org . scalatest .{ FunSpec , ShouldMatchers }
 
classAlbumTestextendsFunSpecwithShouldMatchers {
describe ( "An Album" ) {
it ( "can add an Artist object to the album" ) {
val album = new Album ( "Thriller" , 1981 , new Artist ( "Michael" , "Jackson" ))
album . artist . firstName should be ( "Michael" )
}
 
ignore ( "can add a Producer to an album at construction time" ) {
new Album ( "Breezin\'" , 1976 , new Artist ( "George" , "Benson" ))
//TODO: Figure out the implementation of an album producer
}
}
}

運行測試,將得到如下的輸出:

[ info ] AlbumSpec :
[ info ] An Album
[ info ]- can add an Artist to the album at construction time
[ info ]- can add a Producer to an album at construction time !!! IGNORED !!!

這是因為第二個測試can add a Producer to an album at construction time中的itignore給替代了,在運行測試時,它將被忽略。在上面的輸出結果中的反映就是在測試名后面加上了!!! IGNORED !!!。如果要恢復這個測試,只需要將ignore替換成it就好了。

標記

標記(Tagging)功能給測試加上標簽,這樣就可以分組運行測試了。標記可以在下面這些場景中運用:

  • 你想跳過某些很費時的測試
  • 某些測試是檢查一些相關的功能 需要在一起執行
  • 你想給測試分成單元測試綜合測試驗收測試等分類時

不同的測試接口對標記都有自己的實現,但都是使用字符串來進行分類標記。如下面的例子:

it ( "can add an Artist to the album at construction time" , Tag ( "construction" )) {
// 其它代碼
}

上面的例子是在FunSpec接口中的實現,給can add an Artist to the album at construction time這個測試添加了construction的標記。

在SBT中運行特定標記的測試也有一些需要注意的地方:

  • SBT的test命令暫時還不能支持運行指定標簽的測試

SBT支持多種測試框架,要使test命令能夠按指定標簽執行測試,則需要所有SBT支持的測試框架都支持標簽功能,現在ScalaTestSpecs2都支持了標簽,但ScalaCheck目前並不支持標簽功能。

  • SBT的test-only命令是支持執行指定標簽的測試的

可以用下例中的方式使用test-only命令來運行指定的測試:

test - only AlbumTest -- - n construction

在待測試類名的后面加上--再加上n再加上標簽,來指行指定的測試(有多個標簽 則需要用雙引號"將標簽包圍起來)。如果要排除某個標簽,將前面說的n換成l即可。

Specifications

FunSpec

下面的FunSpec整合了前面說到的InformerGivenWhenThenpendingignoretag

import org . scalatest . matchers . ShouldMatchers
import org . scalatest .{ Tag , GivenWhenThen , FunSpec }
 
classAlbumSpecAllextendsFunSpecwithShouldMatcherswithGivenWhenThen {
describe ( "An Album" ) {
it ( "can add an Artist to the album at construction time" , Tag ( "construction" )) {
given ( "The album Thriller by Michael Jackson" )
val album = new Album ( "Thriller" , 1981 , new Artist ( "Michael" , "Jackson" ))
 
when ( "the artist of the album is obtained" )
artist = album . artist
 
then ( "the artist should be an instance of Artist" )
artist . isInstanceOf [ Artist ] should be ( true )
 
and ( "the artist's first name and last name should be Michael Jackson" )
artist . firstName should be ( "Michael" )
artist . lastName should be ( "Jackson" )
info ( "This is still pending, since there may be more to accomplish in this test" )
pending
}
 
ignore ( "can add a Producer to an album at construction time" ) {
//TODO: Add some logic to add a producer.
}
}
}

上面的例子中,SlbumSpecAll繼隨了類FunSpec混入特質ShouldMachersGivenWhenThen。在前面提到過,ScalaTest中有很多形式的測試類,上面例子中的FunSpec就是其中之一。執行上面的測試,將得到下面的輸出:

[ info ] AlbumSpecAll :
[ info ] An Album
[ info ] - can add an Artist to the album at construction time ( pending )
[ info ]
+ Given The album Thriller by Michael Jackson
[ info ]
+ When Artist of the album is obtained
[ info ]
+ Then the Artist should be an instance of Artist
[ info ]
+ And the artist 's first name and last name should be Michael Jackson
[info]
+ This is still pending, since there may be more to accomplish in this
test
[info] - can add a Producer to an album at construction time !!! IGNORED !!!
[info] Passed: : Total 2, Failed 0, Errors 0, Passed 0, Skipped 2

注意上面的輸出結果,包含pending關鍵字的測試將被Skip

也可以只執行有某個標記的測試:

test - only AlbumSpecAll -- - n construction
[ info ] AlbumSpecAll :
[ info ] An Album
[ info ] - can add an Artist to the album at construction time ( pending )
[ info ] + Given The album Thriller by Michael Jackson
[ info ] + When Artist of the album is obtained
[ info ] + Then the Artist should be an instance of Artist
[ info ] + And the artist 's first name and last name should be Michael Jackson
[info] + This is still pending, since there may be more to accomplish in this test
[info] Passed: : Total 1, Failed 0, Errors 0, Passed 0, Skipped 1

只有標記為construction的測試才會被執行。

這就是FuncSpec這個測試類的用法了,一個測試類繼承了FuncSpec,將使用如下形式的代碼風格寫測試:

import org . scalatest . matchers . ShouldMatchers
import org . scalatest .{ Tag , GivenWhenThen , FunSpec }
 
classAextendsFunSpecwithShouldMachers {
describe ( "一些描述信息" ) {
it ( "一些描述信息" ) {
 
}
// 其它的 it
}
}

在斷言中,可以使用ShouldMacherMustMacherInformer、等特質提供的方法,在describe代碼塊中也可以使用GivenWhenThen特質來使測試更加結構化。

WordSpec

在研究WordSpec之前,先對前面說到的一些基本類進行一些修改:

Act類

classAct

Album類

classAlbum(val title:String, val year:Int, val acts:Act*)

Band類

classBand(name:String, members:List[Artist])extendsAct

WordSpecScalaTest提供的另一個測試類,它大量使用了whenshouldcan這些屬於String的方法。如下面的例子:

import org . scalatest .{ ShouldMatchers , WordSpec }
 
classAlbumWordSpecextendsWordSpecwithShouldMatchers {
"An Album" when {
"created" should {
"accept the title, the year, and a Band as a parameter, and be able to read those parameters back" in {
new Album ( "Hotel California" , 1997 ,
new Band ( "The Eagles" , List ( new Artist ( "Don" , "Henley" ),
new Artist ( "Glenn" , "Frey" ),
new Artist ( "Joe" , "Walsh" ),
new Artist ( "Randy" , "Meisner" ),
new Artist ( "Don" , "Felder" ))))
}
}
}
}

運行上面例子的測試,將得到如下結果:

[ info ] AlbumWordSpec :
[ info ] An Album
[ info ] when created
[ info ] - should accept the title , the year , and a Band as a parameter , and be able to read those parameters back
[ info ] Run completed in 170 milliseconds .
[ info ] Total number of tests run : 1
[ info ] Suites : completed 1 , aborted 0
[ info ] Tests : succeeded 1 , failed 0 , canceled 0 , ignored 0 , pending 0
[ info ] All tests passed .
[ success ] Total time : 0 s , completed May 20 , 2015 7 : 39 : 45 AM

從上面的結果,結合例子中的代碼,可以看出在WordSpec中,一個測試類繼承WordSpec類,使用如下形式的代碼風格寫測試:

import org . scalatest .{ ShouldMatchers , WordSpec }
 
classAextendsWordSpecwithShouldMatchers {
"一些描述" when {
"一些描述" should {
"一些描述" in {
// 其它代碼
}
}
}
}

在一個when代碼塊中,可以使用多個should代碼塊,同時should代碼塊可以不包含在when代碼塊中。如下面的例子:

import org . scalatest .{ ShouldMatchers , WordSpec }
 
classAlbumWordSpecextendsWordSpecwithShouldMatchers {
 
"An Album" when {
"created" should {
"accept the title, the year, and a Band as a parameter, and be able to read those parameters back" in {
new Album ( "Hotel California" , 1997 ,
new Band ( "The Eagles" , List ( new Artist ( "Don" , "Henley" ),
new Artist ( "Glenn" , "Frey" ),
new Artist ( "Joe" , "Walsh" ),
new Artist ( "Randy" , "Meisner" ),
new Artist ( "Don" , "Felder" ))))
}
 
}
}
 
"lack of parameters" should {
"throw an IllegalArgumentException if there are no acts when created" in {
intercept [ IllegalArgumentException ] {
new Album ( "The Joy of Listening to Nothing" , 2000 )
}
}
}
 
}

運行上面的測試,會得到如下的輸出:

[ info ] AlbumWordSpec :
[ info ] An Album
[ info ] when created
[ info ] - should accept the title , the year , and a Band as a parameter , and be able to read those parameters back
[ info ] lack of parameters
[ info ] - should throw an IllegalArgumentException if there are no acts when created
[ info ] Run completed in 173 milliseconds .
[ info ] Total number of tests run : 2
[ info ] Suites : completed 1 , aborted 0
[ info ] Tests : succeeded 2 , failed 0 , canceled 0 , ignored 0 , pending 0
[ info ] All tests passed .
[ success ] Total time : 0 s , completed May 20 , 2015 7 : 49 : 07 AM

info信息的縮進可以看出whenshould的包含關系,以上就是WordSpec的用法了。

FeatureSpec

FeatureSpec可以通過測試的一些特征(feature)將測試進行分類,而每一個特征(feature)又包含若干不同的情節(scenario)。每個特征(feature)和情節(scenario)都需要用不同的字符串來描述。如下面的例子:

import org . scalatest . matchers . ShouldMatchers
import org . scalatest . FeatureSpec
 
classAlbumFeatureSpecextendsFeatureSpecwithShouldMatchers {
feature ( "An album's default constructor should support a parameter that acceptsOption(List(Tracks)) " ) { ... }
 
feature ( "An album should have an addTrack method that takes a track and returns an immutable copy of the Album with the added track" ) { ... }
}

上面的例子中,我們定義了一個AlbumFeatureSpec類,它繼承了FeatureSpec類。在AlbumFeatureSpec類中,寫了兩個feature代碼塊,但在這兩個代碼塊中並未寫任何的scenario。在繼續分析上例代碼前,需要再給Album類添加一些內容。

Track

classTrack(name:String)

Album類

classAlbum(val title:String, val year:Int, val tracks:Option[List[Track]], val acts:Act*) {
require ( acts . size > 0 )
 
def this ( title : String , year : Int , acts : Act *) = this ( title , year , None , acts : _ *)
}

首先來實現第一個feature代碼塊,我們希望給它加入下面的一些scenario

  • 構造Album時提供一個長度為3的List[Track]
  • 構造Album時提供一個空List
  • 構造Album時提供一個null

首先對上例中代碼的第一個feature填充三個scenario,得到如下代碼:

classAlbumFeatureSpecextendsFeatureSpecwithShouldMatchers {
feature ( "An album's default constructor should support a parameter that accepts Option(List(Tracks))" ) {
scenario ( "Album's default constructor is given a list of the 3 tracks exactly for the tracks parameter" ) { pending }
 
scenario ( "Album's default constructor is given an empty List for the tracks parameter" ) { pending }
 
scenario ( "Album's default constructor is given null for the tracks parameter" ) { pending }
}
 
feature ( "An album should have an addTrack method that takes a track and returns an immutable copy of the Album with the added track" ) { }
}

接下來要做的就是給這三個scenario加上實現的代碼。

Album's default constructor is given a list of the 3 tracks exactly for the tracks parameter這個scenario中,我們加入如下代碼:

val depecheModeCirca1990 = new Band ( "Depeche Mode" , List (
new Artist ( "Dave" , "Gahan" ),
new Artist ( "Martin" , "Gore" ),
new Artist ( "Andrew" , "Fletcher" ),
new Artist ( "Alan" , "Wilder" )))
 
val blackCelebration = new Album ( "Black Celebration" , 1990 ,
Some ( List ( new Track ( "Black Celebration" ),
new Track ( "Fly on the Windscreen" ),
new Track ( "A Question of Lust" ))), depecheModeCirca1990 )
 
blackCelebration . tracks . get should have size ( 3 )

接下來是Album's default constructor is given an empty List for the tracks parameter這個scenario

given ( "the band, the Doobie Brothers from 1973" )
 
val theDoobieBrothersCirca1973 = new Band ( "The Doobie Brothers" ,
new Artist ( "Tom" , "Johnston" ),
new Artist ( "Patrick" , "Simmons" ),
new Artist ( "Tiran" , "Porter" ),
new Artist ( "Keith" , "Knudsen" ),
new Artist ( "John" , "Hartman" ))
 
when ( "the album is instantiated with the title, the year, none tracks, and the Doobie Brothers" )
val album = new Album ( "The Captain and Me" , 1973 , None , theDoobieBrothersCirca1973 )
 
then ( "calling the albums's title, year, tracks, acts property should yield the same results" )
album . title should be ( "The Captain and Me" )
album . year should be ( 1973 )
album . tracks should be ( None )
album . acts ( 0 ) should be ( theDoobieBrothersCirca1973 )

第三個scenario我這里就不寫了,下面來看一下運行測試的結果:

[ info ] AlbumFeatureSpec :
[ info ] Feature : An album 's default constructor should support a parameter that accepts Option(List(Tracks))
[info] Scenario: Album' s default constructor is given a list of the 3 tracks exactly for the tracks parameter
[ info ] Scenario : Album 's default constructor is given a None for the tracks parameter
[info] Given the band, the Doobie Brothers from 1973
[info] When the album is instantiated with the title, the year, none tracks, and the Doobie Brothers
[info] Then calling the albums' s title , year , tracks , acts property should yield the same results
[ info ] Scenario : Album 's default constructor is given null for the tracks parameter (pending)
[info] Feature: An album should have an addTrack method that takes a track and returns an immutable copy of the Album with the added track
[info] Run completed in 177 milliseconds.
[info] Total number of tests run: 2
[info] Suites: completed 1, aborted 0
[info] Tests: succeeded 2, failed 0, canceled 0, ignored 0, pending 1
[info] All tests passed.
[success] Total time: 0 s, completed May 20, 2015 8:38:47 AM

FeatureSpec使用特征(feature)將測試進行分類,每一個特征(feature)又包含若干不同的情節(scenario),對這些情節(scenario)的實現實際上就是完成測試的過程。

FreeSpec

FreeSpec是一種形式比較自由的測試,先引入一個類:

JukeBox類

classJukeBox(val albums:Option[List[Album]]) {
def readyToPlay = albums . isDefined
}

再來看一個例子:

import org . scalatest . matchers . ShouldMatchers
import org . scalatest . FreeSpec
 
classJukeboxFreeSpecextendsFreeSpecwithShouldMatchers {
"given 3 albums" - {
val badmotorfinger = new Album ( "Badmotorfinger" , 1991 , None , new Band ( "Soundgarden" ))
val thaDoggFather = new Album ( "The Dogg Father" , 1996 , None , new Artist ( "Snoop Doggy" , "Dogg" ))
val satchmoAtPasadena = new Album ( "Satchmo At Pasadena" , 1951 , None , new Artist ( "Louis" , "Armstrong" ))
 
"when a juke box is instantiated it should accept some albums" - {
val jukebox = new JukeBox ( Some ( List ( badmotorfinger , thaDoggFather , satchmoAtPasadena )))
"then a jukebox's album catalog size should be 3" in {
jukebox . albums . get should have size ( 3 )
}
}
}
 
"El constructor de Jukebox puedo aceptar la palabra clave de 'None'" - {
val jukebox = new JukeBox ( None )
"y regresas 'None' cuando llamado" in {
jukebox . albums should be ( None )
}
}
}

從上面的例子中,可以看到FreeSpec的結構是很自由的。描述字符串加上一個-{ }的代碼塊,如果需要使用斷言,則使用描述字符串加上in。在FreeSpec中,並不強制使用shouldwhen等內容。在FreeSpec中,使用如下形式的代碼風格寫測試:

import org . scalatest . matchers . ShouldMatchers
import org . scalatest . FreeSpec
 
classAextendsFreeSpecwithShouldMatchers {
"一些描述" - {
// 一些代碼
"一些描述" in {
// 斷言
}
}
}

JUnitSuite

前面我們說到的一些測試結構可能跟之前用過的如JunitTestNG這些有較大的差異,如果你比較喜歡像JUnitTestNG這種測試風格,ScalaTest也是支持的。為了使用這種風格,首先在要build.sbt文件中添加JUnit的依賴:

libraryDependencies += "junit" % "junit" % "4.12"

下面來看一個使用ScalaTest寫的JUnit風格的測試:

import org . scalatest . junit . JUnitSuite
import org . junit .{ Test , Before }
import org . junit . Assert . _
 
classArtistJUnitSuiteextendsJUnitSuite {
var artist : Artist = _
 
@Before
def startUp () {
artist = new Artist ( "Kenny" , "Rogers" )
}
 
@Test
def addOneAlbumAndGetCopy () {
val copyArtist = artist . addAlbum ( new Album ( "Love will turn you around" , 1982 , artist ))
assertEquals ( copyArtist . albums . size , 1 )
}
 
@Test
def addTwoAlbumsAndGetCopy () {
val copyArtist = artist
. addAlbum ( new Album ( "Love will turn you around" , 1982 , artist ))
. addAlbum ( new Album ( "We've got tonight" , 1983 , artist ))
assertEquals ( copyArtist . albums . size , 2 )
}
 
After
def shutDown () {
this . artist = null
}
}

上面的例子中startUp方法被注解Before標記,addOneAlbumAndGetCopy方法和addTwoAlbumsAndGetCopy方法被注解TestshutDown方法被注解After標記。注解Test將方法標記為測試方法,而注解Before將方法標記為每個測試方法執行前執行的方法,注解After則將方法標記為每個測試方法執行后執行的方法。

因此,addOneAlbumAndGetCopy方法和addTwoAlbumsAndGetCopy方法執行前startUp方法會被調用,而方法執行結束shutDown方法會被調用。

上面例子的風格跟使用JUnit來做測試是一樣的,只不過我們使用了Scala語言。

TestNGSuit

JUnit類似,在ScalaTest中也提供了TestNG風格的測試寫法。同樣的,需要使用TestNG風格,要先在build.sbt中添加TestNG的依賴:

libraryDependencies += "org.testng" % "testng" % "6.8.21"

我們也會一個例子來說明:

import org . scalatest . testng . TestNGSuite
import collection . mutable . ArrayBuilder
import org . testng . annotations .{ Test , DataProvider }
import org . testng . Assert . _
 
classArtistTestNGSuiteextendsTestNGSuite {
 
@DataProvider ( name = "provider" )
def provideData = {
val g = new ArrayBuilder . ofRef [ Array [ Object ]]()
g += ( Array [ Object ]( "Heart" , 5. asInstanceOf [ java . lang . Integer ]))
g += ( Array [ Object ]( "Jimmy Buffet" , 12. asInstanceOf [ java . lang . Integer ]))
g . result ()
}
 
@Test ( dataProvider = "provider" )
def testTheStringLength ( n1 : String , n2 : java . lang . Integer ) {
assertEquals ( n1 . length , n2 )
}
}

上面的例子中,provideData方法被注解DataProvider標記,testTheStringLength方法被注解Test標記。注解Test將方法標記為測試方法,屬性dataProvider指定了測試數據由哪個DataProvider來提供。注解DataProvider將一個方法標記為一個DataProvider

上面例子中的測試執行,則testTheStringLength測試法的中的測試數據是來自於provideData這個方法。

另外一點,在TestNG中,標簽(Tag)功能被稱為group,給一個測試添加group的寫法如下:

@Test ( dataProvider = "provider" , groups = Array ( "word_count_analysis" ))
def testTheStringLength ( n1 : String , n2 : java . lang . Integer ) {
assertEquals ( n1 . length , n2 )
}

使用如下命令執行指定group的測試:

test - only ArtistTestNGSuite -- - n word_count_analysis

關於上面這條命令中---n等符號、參數的含義在之前的標記里已經分析過了。

Fixtures

Fixture翻譯成中文有這么些意思:固定裝置;卡具;固定附物,固定附着物;固定財產,在ScalaTest中,可能會有這么一種情境:在一個測試類中,不同的測試方法需要的類實例、依賴等數據是一樣的,顯然,沒必要為每個測試類去new一些它們專用的數據,可以提供一些公共的數據,然后在不同的測試方法中重用它們。

要做到數據的重用,有很多方法:

  • Scala語言自帶的方法
  • ScalaTest測試框架提供的解決方案
  • 每一種測試方法也有自己的一些實現
  • JUnitTestNG也有它們自己的結構

匿名對象

先從Scala語言本身提供的方案說起。Scala語言提供的匿名對象可以用來解決前面說到的數據重用的問題。Scala中的匿名對象就是沒有名字的對象,匿名對象一旦被創建,就可以在不同的測試方法中重用。每次匿名對象被請求的時候,它都會創建一個全新的對象。如下面的例子:

import org . scalatest . matchers . ShouldMatchers
import org . scalatest . FunSpec
 
classAlbumFixtureSpecextendsFunSpecwithShouldMatchers {
def fixture = new {
val letterFromHome = new Album ( "Letter from Home" , 1989 , new Band ( "Pat Metheny Group" ))
}
 
describe ( "The Letter From Home Album by Pat Metheny" ) {
it ( "should get the year 1989 from the album" ) {
val album = fixture . letterFromHome
album . year should be ( 1989 )
}
}
}

上面的例子定義了一個fixture方法來獲取一個Album對象,fixture方法每次被調用都會產生一個匿名對象

這里有一點需要注意的,即使fixture方法產生的是一個可變mutable的對象,在另一個方法調用fixture時,它仍然后產生一個新的對象,而不是提供之前的對象。下面的例子使用了可變集合來說明:

import org . scalatest . FunSpec
import org . scalatest . matchers . ShouldMatchers
 
classAlbumMutableFixtureSpecextendsFunSpecwithShouldMatchers {
def fixture = new {
import scala . collection . mutable . _
val beachBoys = new Band ( "Beach Boys" )
val beachBoysDiscography = new ListBuffer [ Album ]()
beachBoysDiscography += ( new Album ( "Surfin' Safari" , 1962 , beachBoys ))
}
 
describe ( "Given a single fixture each beach boy discography initially contains a single album" ) {
it ( "then after 1 album is added, the discography size should have 2" ) {
val discographyDeBeachBoys = fixture . beachBoysDiscography
discographyDeBeachBoys += ( new Album ( "Surfin' Safari" , 1962 , fixture . beachBoys ))
discographyDeBeachBoys . size should be ( 2 )
}
 
it ( "then after 2 albums are added, the discography size should return 3" ) {
val discographyDeBeachBoys = fixture . beachBoysDiscography
discographyDeBeachBoys += ( new Album ( "Surfin' Safari" , 1962 , fixture . beachBoys ))
discographyDeBeachBoys += ( new Album ( "All Summer Long" , 1964 , fixture . beachBoys ))
discographyDeBeachBoys . size should be ( 3 )
}
}
}

跟前一個例子一樣,上面的例子使用了fixture方法,在Scala語言中,使用def定義的方法在每次被調用的時候都會重新執行方法體。因而,在每個測試方法中我們得到的都是新的實例。

Fixture Traits

另一種在ScalaTest中的可供選擇的做法是自定義一個特質來確保每個測試方法都得到不同的對象。特質在混入對象后仍然后持有它原來的方法,並不會在混入的對象之中共享。下面的例子使用一個特質而不是一個匿名對象

import org . scalatest . matchers . ShouldMatchers
import org . scalatest . FunSpec
 
classAlbumFixtureTraitSpecextendsFunSpecwithShouldMatchers {
 
traitAlbumFixture {
val letterFromHome = new Album ( "Letter from Home" , 1989 , new Band ( "Pat Metheny Group" ))
}
 
describe ( "The Letter From Home Album by Pat Metheny" ) {
it ( "should get the year 1989 from the album" ) {
new AlbumFixture {
letterFromHome . year should be ( 1989 )
}
}
}
}

上面的例子使用了一個特質來封裝測試方法需要的數據,在特質中又使用了匿名對象的方式來創建對象,實際上,這種實現方式依然使用了Scala的語言特性。

OneInstancePerTest

除了依賴Scala的語言特性,ScalaTest也提供了方法來確保每個測試都有它自己的數據實例。下面的例子使用了OnInstancePerTest特質來實現:

import org . scalatest . matchers . ShouldMatchers
import collection . mutable . ListBuffer
import org . scalatest .{ FreeSpec , OneInstancePerTest }
 
classAlbumListOneInstancePerTestFreeSpecextendsFreeSpecwithShouldMatchers
with OneInstancePerTest {
val graceJonesDiscography = new ListBuffer [ Album ]()
graceJonesDiscography += ( new Album ( "Portfolio" , 1977 , new Artist ( "Grace" , "Jones" )))
 
"Given an initial Grace Jones Discography" - {
"when an additional two albums are added, then the discography size should be 3" in {
graceJonesDiscography += ( new Album ( "Fame" , 1978 , new Artist ( "Grace" , "Jones" )))
graceJonesDiscography += ( new Album ( "Muse" , 1979 , new Artist ( "Grace" , "Jones" )))
graceJonesDiscography . size should be ( 3 )
}
 
"when one additional album is added, then the discography size should be 2" in {
graceJonesDiscography += ( new Album ( "Warm Leatherette" , 1980 , new Artist ( "Grace" , "Jones" )))
graceJonesDiscography . size should be ( 2 )
}
}
 
"Given an initial Grace Jones Discography " - {
"when one additional album from 1980 is added, then the discography size should be 2" in {
graceJonesDiscography += ( new Album ( "Nightclubbing" , 1981 , new Artist ( "Grace" , "Jones" )))
graceJonesDiscography . size should be ( 2 )
}
}
}

上面的例子使用了FreeSpec風格的測試寫法。在測試開始時,定義了graceJonesDiscography變量,然后該變量被用在多個測試中。由於AlbumListOneInstancePerTestFreeSpec類混入了OneInstancePerTest接口,graceJonesDiscography變量在每個測試方法中使用時都會被重新創建。

上面的例子中,測試方法是在in代碼塊中的內容。

Before and After

為了更好的控制在測試方法執行前、后有什么行為,ScalaTest提供了一個名為BeforeAndAfter的特質。可以很方便的指定在每一個測試方法執行前有什么行為,在每個測試方法執行后有什么行為。如下面的例子:

import collection . mutable . ListBuffer
import org . scalatest .{ BeforeAndAfter , WordSpec }
import org . scalatest . matchers . ShouldMatchers
 
classAlbumBeforeAndAfterFixtureSpecextendsWordSpecwithShouldMatcherswithBeforeAndAfter {
 
val humanLeagueDiscography = new ListBuffer [ Album ]()
 
before {
info ( "Starting to populate the discography" )
humanLeagueDiscography += ( new Album ( "Dare" , 1981 , new Band ( "Human League" )))
}
 
"A mutable ListBuffer of albums" should {
"have a size of 3 when two more albums are added to the Human League Discography" in {
humanLeagueDiscography += ( new Album ( "Hysteria" , 1984 , new Band ( "Human League" )))
humanLeagueDiscography += ( new Album ( "Crash" , 1986 , new Band ( "Human League" )))
humanLeagueDiscography should have size ( 3 )
}
 
"have a size of 2 when one more album is added to the Human League Discography" in {
humanLeagueDiscography += ( new Album ( "Romantic" , 1990 , new Band ( "Human League" )))
humanLeagueDiscography should have size ( 2 )
}
}
 
after {
info ( "Clearing the discography" )
humanLeagueDiscography . clear ()
}
 
}

上面的例子使用了WordSpec風格的測試,在測試方法執行前,初始化了一個名為humanLeagueDiscography的可變列表,在測試方法執行完畢后,humanLeagueDiscography可變列表被清空。BeforeAndAfter特質中的beforeafter方法和在JUnit中被標記為BeforeAfter的方法作用是一樣的。

上面的例子中,測試方法依然是在in代碼塊中的內容。

簡單的總結

ScalaTest是一個強大的測試框架,它是Java友好的,集成了JUnitTestNG的測試風格,是學習Scala語言的一大利器。本文通過各種代碼舉例,簡單的說明了ScalaTest中的各種Spec,是對ScalaTest的一個簡單的介紹。軟件 測度是一門大學問,只有通過不斷的實踐才能獲得屬於自己的經驗。

本文中大量參考了《Testing in Scala》一書,文中有些地方是對原書的翻譯,本人水平有限,對知識的理解也有限,歡迎大家指點~如果您對本文有任何疑問,歡迎與我聯系。

參考資料

Testing in Scala by Daniel Hinojosa (O’REILLY) 密碼:5vjs

本文鏈接:  ScalaTest測試框架
轉載請注明作者 xring 和出處  xring's


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM