ScalaTest——Fixtures 缺乏、安全感 2022-05-12 15:10 234阅读 0赞 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`代码块中的内容。
还没有评论,来说两句吧...