天天看点

ScalaTest——Fixtures

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

代码块中的内容。