Fixture翻譯成中文有這麼些意思:
固定裝置;卡具;固定附物,固定附着物;固定财産
,在ScalaTest中,可能會有這麼一種情境:在一個測試類中,不同的測試方法需要的類執行個體、依賴等資料是一樣的,顯然,沒必要為每個測試類去
new
一些它們專用的資料,可以提供一些公共的資料,然後在不同的測試方法中重用它們。
要做到資料的重用,有很多方法:
- Scala語言自帶的方法
-
測試架構提供的解決方案ScalaTest
- 每一種測試方法也有自己的一些實作
-
和JUnit
也有它們自己的結構TestNG
8.1 匿名對象
先從Scala語言本身提供的方案說起。Scala語言提供的
匿名對象
可以用來解決前面說到的資料重用的問題。Scala中的
匿名對象
就是沒有名字的對象,
匿名對象
一旦被建立,就可以在不同的測試方法中重用。每次
匿名對象
被請求的時候,它都會建立一個全新的對象。如下面的例子:
import org.scalatest.Matchers
import org.scalatest.FunSpec
class AlbumFixtureSpec extends FunSpec with Matchers {
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
class AlbumMutableFixtureSpec extends FunSpec with Matchers {
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
定義的方法在每次被調用的時候都會重新執行方法體。因而,在每個測試方法中我們得到的都是新的執行個體。
8.2 Fixture Traits
另一種在
ScalaTest
中的可供選擇的做法是自定義一個
特質
來確定每個測試方法都得到不同的對象。
特質
在混入對象後仍然後持有它原來的方法,并不會在混入的對象之中共享。下面的例子使用一個
特質
而不是一個
匿名對象
:
import org.scalatest.Matchers
import org.scalatest.FunSpec
class AlbumFixtureTraitSpec extends FunSpec with Matchers {
trait AlbumFixture {
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的語言特性。
8.3 OneInstancePerTest
除了依賴Scala的語言特性,
ScalaTest
也提供了方法來確定每個測試都有它自己的資料執行個體。下面的例子使用了
OnInstancePerTest
特質來實作:
import org.scalatest.Matchers
import collection.mutable.ListBuffer
import org.scalatest.{FreeSpec, OneInstancePerTest}
class AlbumListOneInstancePerTestFreeSpec extends FreeSpec with Matchers
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
代碼塊中的内容。
8.4 Before and After
為了更好的控制在測試方法執行前、後有什麼行為,
ScalaTest
提供了一個名為
BeforeAndAfter
的特質。可以很友善的指定在每一個測試方法執行前有什麼行為,在每個測試方法執行後有什麼行為。如下面的例子:
import collection.mutable.ListBuffer
import org.scalatest.{BeforeAndAfter, WordSpec}
import org.scalatest.Matchers
class AlbumBeforeAndAfterFixtureSpec extends WordSpec with Matchers with BeforeAndAfter {
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
特質中的
before
和
after
方法和在
JUnit
中被标記為
Before
和
After
的方法作用是一樣的。
上面的例子中,測試方法依然是在
in
代碼塊中的内容。