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
代码块中的内容。