scala自动化测试关注点

Overview

想在一个Scala项目里面统一描述下面的内容,

  • sbt
  • scalafmt
  • scalatest
  • scalamock
  • scalabenchmark

还是以之前的golang自动化测试关注点[1]的s3 client例子为出发点,描述一下其在scala下面的test和mock,还有benchmark,顺带将其format也统一起来。

Module

sbt

不用每次都reopen项目,只需要restart sbt shell,然后直接运行update就可以。

根据上面截图的配置,当改动了.sbt后,会触发一个让你手动import Changes的tip,然后点击就会更新到最新的sbt。

又或者点击IDEA的右上角的sbt Tab的左上角的Reimport All sbt Projects来重新import一遍。

这里有一个sbt模板,可以参考它的结构。

lazy val root = (project in file("."))
  .settings(
    name := "Hello",
    scalaVersion := "2.12.7"
  )

image

scalafmt

根据自己和团队的风格配置并执行。

version = "2.1.0-RC1"
maxColumn = 80
docstrings = ScalaDoc

scalatest

scalatest一门口语编程语言

num shouldBe odd
num should not be even

temp should be a 'file
keyEvent should be an 'actionKey

a与an在这都要区分了 >:<

个人还是喜欢在一个项目里面采用单一语言,感觉会比较纯粹,如果scala-lang和人语混合,容易混淆。

个人采用了,

  • FunSuite这种style
  • BeforeAndAfter fixture
// 官网首例
class SetSuite extends FunSuite {

  test("An empty Set should have size 0") {
    assert(Set.empty.size == 0)
  }

  test("Invoking head on an empty Set should produce NoSuchElementException") {
    assertThrows[NoSuchElementException] {
      Set.empty.head
    }
  }
}

scalamock

针对不同的场景需要采用不同的mock style, 首先选择mock风格, ScalaMock supports two mocking styles:

  • Record-then-Verify, Stub, an object that provides predefined answers to method calls(模拟接口返回)
    • Stub的典型应用场景即是当某个对象需要从数据库抓取数据时,我们并不需要真实地与数据库进行交互,而是直接返回预定义好的数据[2]
  • Expectations-First Style, Mock, an object on which you set expectations(模拟真实类)
    • Mock的典型应用场景即是对邮件发送服务的测试,我们并不希望每次进行测试的时候都发送一封邮件,毕竟我们很难去验证邮件是否真的被发出了或者被接收了。我们更多地关注于邮件服务是否按照我们的预期在合适的业务流中被调用[2]
// 官网首例
val winner = Player(id=222, nickname="Boris", country=Countries.Russia) // win
val loser = Player(id=111, nickname="Hans", country=Countries.Germany)

"MatchResultObserver" should "update PlayerLeaderBoard after finished match" in {
  // defining stubs
  val playerDatabaseStub = stub[PlayerDatabase]
  (playerDatabaseStub.getPlayerById _).when(winner.id).returns(winner) // 当被这样这样调用,返回预定义
  (playerDatabaseStub.getPlayerById _).when(loser.id).returns(loser)

  // expectations
  val countryLeaderBoardMock = mock[CountryLeaderBoard]
  (countryLeaderBoardMock.addVictoryForCountry _).expects(Countries.Russia) // 希望被这样调用(russia is win in predefined)

  // run system under test
  val matchResultObserver = new MatchResultObserver(playerDatabaseStub, countryLeaderBoardMock)
  matchResultObserver.recordMatchResult(MatchResult(winner=winner.id, loser=loser.id))
}

scalabenchmark

更准确地说是ScalaMeter,我这里也是希望能run sbt test的时候把benchmark也一并跑了的,但是一直有ClassNotFoundException,估计是sbt一些配置或者版本没有写对。

最后只能采用静态运行的方式。

::Benchmark Array.get::
cores: 12
name: Java HotSpot(TM) 64-Bit Server VM
osArch: x86_64
osName: Mac OS X
vendor: Oracle Corporation
version: 25.221-b11
Parameters(size -> 300000): 4.16E-4 ms
Parameters(size -> 600000): 3.79E-4 ms
Parameters(size -> 900000): 3.82E-4 ms
Parameters(size -> 1200000): 3.75E-4 ms
Parameters(size -> 1500000): 3.57E-4 ms

::Benchmark Array.set::
cores: 12
name: Java HotSpot(TM) 64-Bit Server VM
osArch: x86_64
osName: Mac OS X
vendor: Oracle Corporation
version: 25.221-b11
Parameters(size -> 300000): 0.018595 ms
Parameters(size -> 600000): 0.015909 ms
Parameters(size -> 900000): 0.012641 ms
Parameters(size -> 1200000): 0.009554 ms
Parameters(size -> 1500000): 0.008802 ms

从上面可以看到get()set()快3个数量级。

// 官网首例
object RangeBenchmark extends Bench.ForkedTime {
  val ranges = for {
    size <- Gen.range("size")(300000, 1500000, 300000)
  } yield 0 until size

  measure method "map" in {
    using(ranges) curve("Range") in {
      _.map(_ + 1)
    }
  }
}

Reference

  1. golang自动化测试关注点
  2. 测试中Fakes、Mocks以及Stubs概念明晰
  3. my source code