Skala Pembelajaran: Bagian 3 - Tes Unit





Halo, Habr! Tidaklah cukup menulis kode yang baik. Kami masih perlu menutupinya dengan Tes Unit yang baik. Di artikel terakhir, saya membuat web server sederhana. Sekarang saya akan mencoba menulis berapa banyak tes. Reguler, Berbasis properti, dan diejek. Untuk detailnya, selamat datang di bawah cat.



Kandungan





Tautan



Sumber

Gambar gambar buruh pelabuhan



Dan untuk pengujian unit Anda membutuhkan 3 libs.



  1. Perpustakaan untuk membuat tes
  2. Library yang akan menghasilkan data uji
  3. Perpustakaan yang akan mengejek objek


Saya menggunakan perpustakaan ScalaTest untuk membuat tes



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


Saya menggunakan ScalaCheck untuk menghasilkan data pengujian untuk pengujian berbasis Properti .



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


dan ekstensi yang menggabungkan ScalaTest + ScalaCheck ScalaTestPlusScalaCheck



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


Saya menggunakan ScalaMock untuk mengejek objek



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


Kelas sederhana yang mewakili jenis string yang diisi (tidak kosong). Kami sekarang akan mengujinya.



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
    }
  }
}


Membuat kelas untuk pengujian kami



class FilledStrTests extends AnyFlatSpec with should.Matchers with ScalaCheckPropertyChecks {

}


Kami membuat metode yang akan memeriksa bahwa saat membuat kelas kami dari baris yang sama, kami akan menerima data yang sama.



 "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)
  }


Dalam pengujian terakhir, kami menyembunyikan kode menjadi string yang dibuat secara manual. Sekarang mari kita lakukan pengujian menggunakan data yang dihasilkan. Kita akan menggunakan pendekatan berbasis properti di mana properti fungsi diuji, sehingga dengan data masukan seperti itu, kita akan menerima data keluaran tersebut.



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


Anda dapat secara eksplisit mengonfigurasi generator data pengujian untuk hanya menggunakan data yang kami butuhkan. Contohnya seperti ini:



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


Anda juga tidak dapat meneruskan generator kami secara eksplisit, tetapi menentukan implikasinya melalui Arbitrary sehingga secara otomatis diteruskan sebagai generator untuk diuji. Contohnya seperti ini:



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) }


Objek kompleks juga dapat dibuat menggunakan Arbitrary.



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 }


Sekarang mari kita coba menulis ujian dengan lebih serius. Kami akan meniru dependensi untuk TodosService. Ini Menggunakan 2 repositori dan repositori pada gilirannya menggunakan abstraksi atas transaksi UnitOfWork. Mari kita uji metode paling sederhana.



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


Yang hanya memanggil repositori, memulai transaksi di dalamnya untuk membaca daftar Todo, mengakhirinya dan mengembalikan hasilnya. Juga dalam pengujian, alih-alih F [_], monad Id diletakkan, yang hanya mengembalikan nilai yang disimpan di dalamnya.



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)
  }
}


Tes menulis di Scala ternyata sangat menyenangkan, dan ScalaCheck, ScalaTest, ScalaMock ternyata adalah perpustakaan yang sangat bagus. Serta perpustakaan untuk membuat tapir API dan perpustakaan untuk server http4s dan perpustakaan untuk aliran fs2. Sejauh ini, lingkungan dan perpustakaan Scala hanya menimbulkan emosi positif dalam diri saya. Saya berharap tren ini akan terus berlanjut.



All Articles