天天看點

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

代碼塊中的内容。