ScalaTest——Specifications行为规范

FunSepc,WordSpec,FeatureSpec,FreeSpec,FlatSpec大同小异,这里以FeatureSpec为例

FeatureSpec是将测试分类为一系列的功能的测试。一个feature是软件的一个简单的功能点。每个功能将有该功能的多个场景(测试用例),每个场景代表一个成功的或者失败的测试案例。场景越多,测试越充分,健壮性越好。

7.1 FunSpec

下面的FunSpec整合了前面说到的InformerGivenWhenThenpendingignoretag

import org.scalatest.matchers.ShouldMatchers
import org.scalatest.{Tag, GivenWhenThen, FunSpec}

class AlbumSpecAll extends FunSpec with Matchers with GivenWhenThen {
    describe("An Album") {
        it("can add an Artist to the album at construction time", Tag("construction")) {
            Given("The album Thriller by Michael Jackson")
            val album = new Album("Thriller", 1981, new Artist("Michael", "Jackson"))

            When("the artist of the album is obtained")
            artist = album.artist

            then("the artist should be an instance of Artist")
            artist.isInstanceOf[Artist] should be(true)

            and("the artist's first name and last name should be Michael Jackson")
            artist.firstName should be("Michael")
            artist.lastName should be("Jackson")
            info("This is still pending, since there may be more to accomplish in this test")
            pending
        }

        ignore("can add a Producer to an album at construction time") {
            //TODO: Add some logic to add a producer.
        }
    }
}

上面的例子中,SlbumSpecAll继随了类FunSpec混入特质ShouldMachersGivenWhenThen。在前面提到过,ScalaTest中有很多形式的测试类,上面例子中的FunSpec就是其中之一。执行上面的测试,将得到下面的输出:

[info] AlbumSpecAll:
[info] An Album
[info] - can add an Artist to the album at construction time (pending)
[info]
+ Given The album Thriller by Michael Jackson
[info]
+ When Artist of the album is obtained
[info]
+ Then the Artist should be an instance of Artist
[info]
+ And the artist's first name and last name should be Michael Jackson
[info]
+ This is still pending, since there may be more to accomplish in this
test
[info] - can add a Producer to an album at construction time !!! IGNORED !!!
[info] Passed: : Total 2, Failed 0, Errors 0, Passed 0, Skipped 2

注意上面的输出结果,包含pending关键字的测试将被Skip

也可以只执行有某个标记的测试: 只有标记为construction的测试才会被执行。

test-only AlbumSpecAll -- -n construction
[info] AlbumSpecAll:
[info] An Album
[info] - can add an Artist to the album at construction time (pending)
[info]   + Given The album Thriller by Michael Jackson
[info]   + When Artist of the album is obtained
[info]   + Then the Artist should be an instance of Artist
[info]   + And the artist's first name and last name should be Michael Jackson
[info]   + This is still pending, since there may be more to accomplish in this test
[info] Passed: : Total 1, Failed 0, Errors 0, Passed 0, Skipped 1
7.2 WordSpec

在研究WordSpec之前,先对前面说到的一些基本类进行一些修改:

Act类
class Act
Album类
class Album(val title: String, val year: Int, val acts: Act*)
Band类
class Band(name: String, members: List[Artist]) extends Act

WordSpecScalaTest提供的另一个测试类,它大量使用了whenshouldcan这些属于String的方法。如下面的例子:

import org.scalatest.{Matchers, WordSpec}

class AlbumWordSpec extends WordSpec with Matchers {
  "An Album" when {
    "created" should {
      "accept the title, the year, and a Band as a parameter, and be able to read those parameters back" in {
        new Album("Hotel California", 1997,
          new Band("The Eagles", List(new Artist("Don", "Henley"),
            new Artist("Glenn", "Frey"),
            new Artist("Joe", "Walsh"),
            new Artist("Randy", "Meisner"),
            new Artist("Don", "Felder"))))
      }
    }
  }
}

运行上面例子的测试,将得到如下结果:

[info] AlbumWordSpec:
[info] An Album
[info]   when created
[info]   - should accept the title, the year, and a Band as a parameter, and be able to read those parameters back
[info] Run completed in 170 milliseconds.
[info] Total number of tests run: 1
[info] Suites: completed 1, aborted 0
[info] Tests: succeeded 1, failed 0, canceled 0, ignored 0, pending 0
[info] All tests passed.
[success] Total time: 0 s, completed May 20, 2015 7:39:45 AM

从上面的结果,结合例子中的代码,可以看出在WordSpec中,一个测试类继承WordSpec类,使用如下形式的代码风格写测试:

import org.scalatest.{Matchers, WordSpec}

class A extends WordSpec with Matchers {
  "一些描述" when {
    "一些描述" should {
      "一些描述" in {
        // 其它代码
      }
    }
  }
}

在一个when代码块中,可以使用多个should代码块,同时should代码块可以不包含在when代码块中。如下面的例子:

import org.scalatest.{ShouldMatchers, WordSpec}

class AlbumWordSpec extends WordSpec with ShouldMatchers {

  "An Album" when {
    "created" should {
      "accept the title, the year, and a Band as a parameter, and be able to read those parameters back" in {
        new Album("Hotel California", 1997,
          new Band("The Eagles", List(new Artist("Don", "Henley"),
            new Artist("Glenn", "Frey"),
            new Artist("Joe", "Walsh"),
            new Artist("Randy", "Meisner"),
            new Artist("Don", "Felder"))))
      }

    }
  }
  
  "lack of parameters" should {
    "throw an IllegalArgumentException if there are no acts when created" in {
      intercept[IllegalArgumentException] {
        new Album("The Joy of Listening to Nothing", 2000)
      }
    }
  }

}

运行上面的测试,会得到如下的输出:

[info] AlbumWordSpec:
[info] An Album
[info]   when created
[info]   - should accept the title, the year, and a Band as a parameter, and be able to read those parameters back
[info] lack of parameters
[info] - should throw an IllegalArgumentException if there are no acts when created
[info] Run completed in 173 milliseconds.
[info] Total number of tests run: 2
[info] Suites: completed 1, aborted 0
[info] Tests: succeeded 2, failed 0, canceled 0, ignored 0, pending 0
[info] All tests passed.
[success] Total time: 0 s, completed May 20, 2015 7:49:07 AM

info信息的缩进可以看出whenshould的包含关系,以上就是WordSpec的用法了。

7.3 FeatureSpec

FeatureSpec可以通过测试的一些特征(feature)将测试进行分类,而每一个特征(feature)又包含若干不同的情节(scenario)。每个特征(feature)和情节(scenario)都需要用不同的字符串来描述。如下面的例子:

import org.scalatest.Matchers
import org.scalatest.FeatureSpec

class AlbumFeatureSpec extends FeatureSpec with Matchers {
    feature("An album's default constructor should support a parameter that acceptsOption(List(Tracks)) ") { ... }

    feature("An album should have an addTrack method that takes a track and returns an immutable copy of the Album with the added track") { ... }
}

上面的例子中,我们定义了一个AlbumFeatureSpec类,它继承了FeatureSpec类。在AlbumFeatureSpec类中,写了两个feature代码块,但在这两个代码块中并未写任何的scenario。在继续分析上例代码前,需要再给Album类添加一些内容。

Track

class Track(name: String)
Album类
class Album (val title:String, val year:Int, val tracks:Option[List[Track]], val acts:Act*) {
    require(acts.size > 0)

    def this(title:String, year:Int, acts:Act*) = this (title, year, None, acts:_*)
}

首先来实现第一个feature代码块,我们希望给它加入下面的一些scenario

  • 构造Album时提供一个长度为3的List[Track]
  • 构造Album时提供一个空List
  • 构造Album时提供一个null

首先对上例中代码的第一个feature填充三个scenario,得到如下代码:

class AlbumFeatureSpec extends FeatureSpec with Matchers {
    feature("An album's default constructor should support a parameter that accepts Option(List(Tracks))") {
        scenario ("Album's default constructor is given a list of the 3 tracks exactly for the tracks parameter") {pending}

	scenario ("Album's default constructor is given an empty List for the tracks parameter") {pending}

	scenario ("Album's default constructor is given null for the tracks parameter") {pending}
    }

    feature("An album should have an addTrack method that takes a track and returns an immutable copy of the Album with the added track") { }
}

接下来要做的就是给这三个scenario加上实现的代码。

Album's default constructor is given a list of the 3 tracks exactly for the tracks parameter这个scenario中,我们加入如下代码:

val depecheModeCirca1990 = new Band("Depeche Mode", List(
  new Artist("Dave", "Gahan"),
  new Artist("Martin", "Gore"),
  new Artist("Andrew", "Fletcher"),
  new Artist("Alan", "Wilder")))

val blackCelebration = new Album("Black Celebration", 1990,
  Some(List(new Track("Black Celebration"),
    new Track("Fly on the Windscreen"),
    new Track("A Question of Lust"))), depecheModeCirca1990)

blackCelebration.tracks.get should have size (3)

接下来是Album's default constructor is given an empty List for the tracks parameter这个scenario

given("the band, the Doobie Brothers from 1973")

val theDoobieBrothersCirca1973 = new Band("The Doobie Brothers",
    new Artist("Tom", "Johnston"),
    new Artist("Patrick", "Simmons"),
    new Artist("Tiran", "Porter"),
    new Artist("Keith", "Knudsen"),
    new Artist("John", "Hartman"))

when("the album is instantiated with the title, the year, none tracks, and the Doobie Brothers")
val album = new Album("The Captain and Me", 1973, None, theDoobieBrothersCirca1973)

then("calling the albums's title, year, tracks, acts property should yield the same results")
album.title should be("The Captain and Me")
album.year should be(1973)
album.tracks should be(None)
album.acts(0) should be(theDoobieBrothersCirca1973)

第三个scenario我这里就不写了,下面来看一下运行测试的结果:

[info] AlbumFeatureSpec:
[info] Feature: An album's default constructor should support a parameter that accepts Option(List(Tracks))
[info]   Scenario: Album's default constructor is given a list of the 3 tracks exactly for the tracks parameter
[info]   Scenario: Album's default constructor is given a None for the tracks parameter
[info]     Given the band, the Doobie Brothers from 1973 
[info]     When the album is instantiated with the title, the year, none tracks, and the Doobie Brothers 
[info]     Then calling the albums's title, year, tracks, acts property should yield the same results 
[info]   Scenario: Album's default constructor is given null for the tracks parameter (pending)
[info] Feature: An album should have an addTrack method that takes a track and returns an immutable copy of the Album with the added track
[info] Run completed in 177 milliseconds.
[info] Total number of tests run: 2
[info] Suites: completed 1, aborted 0
[info] Tests: succeeded 2, failed 0, canceled 0, ignored 0, pending 1
[info] All tests passed.
[success] Total time: 0 s, completed May 20, 2015 8:38:47 AM

FeatureSpec使用特征(feature)将测试进行分类,每一个特征(feature)又包含若干不同的情节(scenario),对这些情节(scenario)的实现实际上就是完成测试的过程。

7.4 FreeSpec

FreeSpec是一种形式比较自由的测试,先引入一个类:

JukeBox类
class JukeBox(val albums:Option[List[Album]]) {
    def readyToPlay = albums.isDefined
}

再来看一个例子:

import org.scalatest.Matchers
import org.scalatest.FreeSpec

class JukeboxFreeSpec extends FreeSpec with Matchers {
  "given 3 albums" - {
    val badmotorfinger = new Album("Badmotorfinger", 1991, None, new Band("Soundgarden"))
    val thaDoggFather = new Album("The Dogg Father", 1996, None, new Artist("Snoop Doggy", "Dogg"))
    val satchmoAtPasadena = new Album("Satchmo At Pasadena", 1951, None, new Artist("Louis", "Armstrong"))

    "when a juke box is instantiated it should accept some albums" - {
      val jukebox = new JukeBox(Some(List(badmotorfinger, thaDoggFather, satchmoAtPasadena)))
      "then a jukebox's album catalog size should be 3" in {
        jukebox.albums.get should have size (3)
      }
    }
  }

  "El constructor de Jukebox puedo aceptar la palabra clave de 'None'" - {
    val jukebox = new JukeBox(None)
    "y regresas 'None' cuando llamado" in {
      jukebox.albums should be(None)
    }
  }
}

从上面的例子中,可以看到FreeSpec的结构是很自由的。描述字符串加上一个-{ }的代码块,如果需要使用断言,则使用描述字符串加上in。在FreeSpec中,并不强制使用shouldwhen等内容。在FreeSpec中,使用如下形式的代码风格写测试:

import org.scalatest.Matchers
import org.scalatest.FreeSpec

class A extends FreeSpec with Matchers {
  "一些描述" - {
    // 一些代码
    "一些描述" in {
      // 断言
    }
  }
}
7.5 JUnitSuite

前面我们说到的一些测试结构可能跟之前用过的如JunitTestNG这些有较大的差异,如果你比较喜欢像JUnitTestNG这种测试风格,ScalaTest也是支持的。为了使用这种风格,首先在要build.sbt文件中添加JUnit的依赖:

libraryDependencies += "junit" % "junit" % "4.12"

下面来看一个使用ScalaTest写的JUnit风格的测试:

import org.scalatest.junit.JUnitSuite
import org.junit.{Test, Before}
import org.junit.Assert._

class ArtistJUnitSuite extends JUnitSuite {
    var artist:Artist = _

    @Before
    def startUp() {
        artist = new Artist("Kenny", "Rogers")
    }

    @Test
    def addOneAlbumAndGetCopy() {
        val copyArtist = artist.addAlbum(new Album("Love will turn you around", 1982, artist))
        assertEquals(copyArtist.albums.size, 1)
    }

    @Test
    def addTwoAlbumsAndGetCopy() {
        val copyArtist = artist
            .addAlbum(new Album("Love will turn you around", 1982, artist))
            .addAlbum(new Album("We've got tonight", 1983, artist))
        assertEquals(copyArtist.albums.size, 2)
    }

    @After
    def shutDown() {
	    this.artist = null
	}
}

上面的例子中startUp方法被注解Before标记,addOneAlbumAndGetCopy方法和addTwoAlbumsAndGetCopy方法被注解TestshutDown方法被注解After标记。注解Test将方法标记为测试方法,而注解Before将方法标记为每个测试方法执行前执行的方法,注解After则将方法标记为每个测试方法执行后执行的方法。

因此,addOneAlbumAndGetCopy方法和addTwoAlbumsAndGetCopy方法执行前startUp方法会被调用,而方法执行结束shutDown方法会被调用。

上面例子的风格跟使用JUnit来做测试是一样的,只不过我们使用了Scala语言。

7.6 TestNGSuit

JUnit类似,在ScalaTest中也提供了TestNG风格的测试写法。同样的,需要使用TestNG风格,要先在build.sbt中添加TestNG的依赖:

libraryDependencies += "org.testng" % "testng" % "6.8.21"

我们也会一个例子来说明:

import org.scalatest.testng.TestNGSuite
import collection.mutable.ArrayBuilder
import org.testng.annotations.{Test, DataProvider}
import org.testng.Assert._

class ArtistTestNGSuite extends TestNGSuite {

    @DataProvider(name = "provider")
    def provideData = {
        val g = new ArrayBuilder.ofRef[Array[Object]]()
        g += (Array[Object]("Heart", 5.asInstanceOf[java.lang.Integer]))
        g += (Array[Object]("Jimmy Buffet", 12.asInstanceOf[java.lang.Integer]))
        g.result()
    }

    @Test(dataProvider = "provider")
    def testTheStringLength(n1:String, n2:java.lang.Integer) {
        assertEquals(n1.length, n2)
    }
}

上面的例子中,provideData方法被注解DataProvider标记,testTheStringLength方法被注解Test标记。注解Test将方法标记为测试方法,属性dataProvider指定了测试数据由哪个DataProvider来提供。注解DataProvider将一个方法标记为一个DataProvider

上面例子中的测试执行,则testTheStringLength测试法的中的测试数据是来自于provideData这个方法。

另外一点,在TestNG中,标签(Tag)功能被称为group,给一个测试添加group的写法如下:

@Test(dataProvider = "provider", groups=Array("word_count_analysis"))
    def testTheStringLength(n1:String, n2:java.lang.Integer) {
    assertEquals(n1.length, n2)
}

使用如下命令执行指定group的测试:

test-only ArtistTestNGSuite -- -n word_count_analysis

关于上面这条命令中---n等符号、参数的含义在之前的标记里已经分析过了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值