学习Scala:第3部分-单元测试





哈Ha!编写好的代码是不够的。我们仍然需要通过良好的单元测试来涵盖它。在上一篇文章中,我做了一个简单的Web服务器。现在,我将尝试编写多少个测试。常规,基于属性和嘲笑。欲了解更多信息,欢迎来到猫下。



内容





链接



Sources

Images docker image



因此,对于单元测试,您需要3个库。



  1. 用于创建测试的库
  2. 将生成测试数据的库
  3. 一个模拟对象的库


我使用ScalaTest库创建测试



"org.scalatest" %% "scalatest" % "3.2.0" % Test


我使用ScalaCheck为基于属性的测试生成测试数据



"org.scalacheck" %% "scalacheck" % "1.14.3" % Test


以及结合了ScalaTest + ScalaCheck ScalaTestPlusScalaCheck的扩展



"org.scalatestplus" %% "scalacheck-1-14" % "3.2.0.0" % Test


我用ScalaMock模拟对象



"org.scalamock" %% "scalamock" % "4.4.0" % Test


一个简单的类,代表填充(非空)字符串的类型。我们现在将对其进行测试。



package domain.common

sealed abstract case class FilledStr private(value: String) {
  def copy(): FilledStr = new FilledStr(this.value) {}
}

object FilledStr {
  def apply(value: String): Option[FilledStr] = {
    val trimmed = value.trim
    if (trimmed.nonEmpty) {
      Some(new FilledStr(trimmed) {})
    } else {
      None
    }
  }
}


为我们的测试创建一个类



class FilledStrTests extends AnyFlatSpec with should.Matchers with ScalaCheckPropertyChecks {

}


我们创建一个方法,该方法将检查从相同的行创建类时是否将接收相同的数据。



 "equals" should "return true fro equal value" in {
    val str = "1234AB"
    val a = FilledStr(str).get
    val b = FilledStr(str).get
    b.equals(a) should be(true)
  }


在上一个测试中,我们已编码为手工制作的字符串。现在,让我们使用生成的数据进行测试。我们将使用基于属性的方法来测试函数的属性,以便使用此类输入数据,我们将接收此类输出数据。



  "constructor" should "save expected value" in {
    forAll { s: String =>
//   .          .
      whenever(s.trim.nonEmpty) {
        val a = FilledStr(s).get
        a.value should be(s)
      }
    }
  }


您可以显式配置测试数据生成器以仅使用我们需要的数据。例如这样:



//   
val evenInts = for (n <- Gen.choose(-1000, 1000)) yield 2 * n
//    
forAll (evenInts) { (n) => n % 2 should equal (0) }


您也不能显式传递我们的生成器,而是通过任意定义它的隐式,以便它自动作为生成器传递给测试。例如这样:



implicit lazy val myCharArbitrary = Arbitrary(Gen.oneOf('A', 'E', 'I', 'O', 'U'))
val validChars: Seq[Char] = List('X')
//     Arbitrary[Char]       .
forAll { c: Char => validChars.contains(c) }


复杂对象也可以使用任意生成。



case class Foo(intValue: Int, charValue: Char)

val fooGen = for {
  intValue <- Gen.posNum[Int]
  charValue <- Gen.alphaChar
} yield Foo(intValue, charValue)

implicit lazy val myFooArbitrary = Arbitrary(fooGen)

forAll { foo: Foo => (foo.intValue < 0) ==  && !foo.charValue.isDigit }


现在,让我们尝试更认真地编写测试。我们将模拟TodosService的依赖项。它使用2个存储库,而该存储库又对UnitOfWork事务使用抽象。让我们测试一下最简单的方法。



  def getAll(): F[List[Todo]] =
    repo.getAll().commit()


它将仅调用存储库,在其中启动事务以读取Todo列表,结束并返回结果。同样在测试中,放置Id monad而不是F [_],它仅返回存储在其中的值。



class TodoServiceTests extends AnyFlatSpec with MockFactory with should.Matchers {
  "geAll" should "  " in {
//  .
    implicit val tr = mock[TodosRepositoryContract[Id, Id]]
    implicit val ir = mock[InstantsRepositoryContract[Id]]
    implicit val uow = mock[UnitOfWorkContract[Id, List[Todo], Id]]
// .          implicit
    val service= new TodosService[Id, Id]()
// Id    Todo 
    val list: Id[List[Todo]] = List(Todo(1, "2", 3, Instant.now()))
//   getAll    uow    1 
    (tr.getAll _).expects().returning(uow).once()
//   commit        1 
    (uow.commit _).expects().returning(list).once()
//     getAll      
//   
    service.getAll() should be(list)
  }
}


事实证明,使用Scala编写测试非常令人愉快,而ScalaCheck,ScalaTest和ScalaMock却是非常好的库。以及用于创建API tapir的库,用于http4s服务器的库和用于fs2流的库。到目前为止,Scala的环境和库仅在我内部引起积极的情绪。我希望这一趋势将继续下去。



All Articles