Как я могу получить случайные данные, сгенерированные для классов дел Scala с возможностью «изменить некоторые значения» для модульного тестирования? - PullRequest
3 голосов
/ 10 мая 2019

Я работаю с куском кода, который имеет широкую / глубокую иерархию классов дел. Для модульного тестирования я хотел бы, чтобы в классах были заполнены «случайные данные» с возможностью изменения данных для полей, которые меня интересуют?

Пример:

case class Foo(bar: Bar, name: String, value: Int)
case class Bar(baz: Baz, price: Double)
case class Baz(thing: String)

Так что-то вроде:

val randomFoo = GenerateRandomData(Foo)
randomFoo.bar.baz = Baz("custom for testing")

Я слышал о ScalaCheck и Shapeless и Scalacheck-shapeless, и они действительно обеспечивают генерацию случайных данных, но, похоже, ничего с настройкой.

В настоящее время я использую ScalaMock, но он формирует поля null и нарушает тестируемость для "других" тестов. Я использовал что-то похожее в .Net, например, Auto Fixture, и мне было интересно, было ли что-то подобное в Scala.

Ответы [ 3 ]

4 голосов
/ 11 мая 2019

Я думаю, вы ищете скаляр линзу .

Он будет делать то, что вы хотите.

Однако я должен сказать, что использование случайных данных для модульного тестирования кажется ужасной идеей.Как вы собираетесь отлаживать сбой, который происходит время от времени?

Вам следует потратить некоторое время на настройку детерминированного набора постоянных тестовых объектов, которые также напоминают фактические производственные данные, а затем просто использовать их в своих тестах.

2 голосов
/ 10 мая 2019

Scalacheck предлагает генератор констант, который позволяет определять настраиваемые / постоянные строки:

import org.scalacheck._

  val fooGen: Gen[Foo] =
    for {
      baz <- Gen.const("custom for testing").map(Baz)
      price <- Gen.choose[Double](0, 5000)
      name <- Gen.alphaStr
      value <- Gen.choose(0, 100)
    } yield {
      val bar = Bar(baz, price)
      Foo(bar, name, value)
    }

Вот что мы получаем, когда запускаем его:

scala> fooGen.sample
res6: Option[Foo] = Some(
  Foo(
    Bar(Baz("custom for testing"), 1854.3159675078969),
    "EegNcrrQyzuazqrkturrvsqylaauxausrkwtefowpbkptiuoHtdfJjoUImgddhsnjuzpoiVpjAtjzulkMonIrzmfxonBmtZS",
    64
  )
)

Обновление : Как указывал @Dima, способ получения случайных значений для всех полей состоит в том, чтобы использовать [scalacheck-shapeless] (https://github.com/alexarchambault/scalacheck-shapeless) и lenses для настройки, вот пример, который используетMonocle:

  import org.scalacheck.{Arbitrary, Gen}
  import monocle.Lens
  import org.scalacheck.ScalacheckShapeless._

  implicitly[Arbitrary[Foo]]

  val lensBar = Lens[Foo, Bar](_.bar)(bar => _.copy(bar = bar))
  val lensBaz = Lens[Bar, Baz](_.baz)(baz => _.copy(baz = baz))
  val lensThing = Lens[Baz, String](_.thing)(thing => _.copy(thing = thing))

  val lens = (lensBar composeLens lensBaz composeLens lensThing).set("custom for testing")


  val fooGen: Gen[Foo] = Arbitrary.arbitrary[Foo].map(lens)


  println(fooGen.sample)
  // Display 
  // Some(Foo(Bar(Baz(custom for testing),1.2227226413326224E-91),〗❌䟤䉲㙯癏<,-2147483648))
0 голосов
/ 13 мая 2019

Ваш вопрос состоит из двух частей.Можно автоматически генерировать классы в ScalaCheck с помощью Gen.resultOf.Ранее это задавалось в генераторе случайных данных класса случая скалярной проверки .

может быть возможно сделать *1004* бесформенной скалярной формы

, сделав это даже с меньшим количеством шаблонов. Вторая часть вашего вопроса о мутирующих полях в неизменяемых объектах.По большей части достаточно использовать конструктор copy, предоставленный case class.Библиотека, подобная Monocle , может помочь, но мне никогда не приходилось ее использовать.

import org.scalacheck.Arbitrary
import org.scalacheck.Prop
import org.scalacheck.Prop.AnyOperators // Adds ?= operator
import org.scalacheck.Properties
import org.scalacheck.Gen

object FooSpec extends Properties("Foo") {

  val genBaz: Gen[Baz] = Gen.resultOf(Baz)

  implicit val arbBaz = Arbitrary(genBaz)

  val genBar: Gen[Bar] = Gen.resultOf(Bar)

  implicit val arbBar = Arbitrary(genBar)

  val genFoo: Gen[Foo] = Gen.resultOf(Foo)

  implicit val arbFoo = Arbitrary(genFoo)

  val genCustomFoo: Gen[Foo] = {
    Arbitrary.arbitrary[Foo].map { foo =>
      foo.copy(bar = foo.bar.copy(baz = Baz("custom for testing")))
    }
  }

  property("arbFoo") = {
    Prop.forAll { foo: Foo  =>
      foo.bar.baz.thing != "custom for testing"
    }
  }

  property("genCustomFoo") = {
    Prop.forAll(genCustomFoo) { foo: Foo  =>
      foo.bar.baz.thing ?= "custom for testing"
    }
  }
}
...