Совместное использование элементов между сгенерированными объектами в ScalaCheck с использованием вложенного forAll - PullRequest
4 голосов
/ 31 мая 2019

Совсем недавно начал писать код в Scala, и я попытался написать несколько тестовых примеров, основанных на свойствах. Здесь я пытаюсь сгенерировать необработанные данные, которые имитируют систему, которую я тестирую. Цель состоит в том, чтобы сначала сгенерировать базовые элементы (ctrl и idz), затем использовать эти значения для генерации двух классов (A1 и B1) и, наконец, проверить их свойства. Сначала я попробовал следующее -

import org.scalatest._
import prop._
import scala.collection.immutable._
import org.scalacheck.{Gen, Arbitrary}

case class A(
    controller: String,
    id: Double,
    x: Double
)

case class B(
    controller: String,
    id: Double,
    y: Double
)

object BaseGenerators {
    val ctrl = Gen.const("ABC")
    val idz = Arbitrary.arbitrary[Double]
}

trait Generators {
    val obj = BaseGenerators

    val A1 = for {
        controller <- obj.ctrl
        id <- obj.idz
        x <- Arbitrary.arbitrary[Double]
    } yield A(controller, id, x)

    val B1 = for {
        controller <- obj.ctrl
        id <- obj.idz
        y <- Arbitrary.arbitrary[Double]
    } yield B(controller, id, y)

}

class Something extends PropSpec with PropertyChecks with Matchers with Generators{

    property("Controllers are equal") {
        forAll(A1, B1) {
            (a:A,b:B) => 
                a.controller should be (b.controller)
        }
    }

    property("IDs are equal") {
        forAll(A1, B1) {
            (a:A,b:B) => 
                a.id should be (b.id)
        }
    }

}

Запуск sbt test в терминале дал мне следующее -

[info] Something:
[info] - Controllers are equal
[info] - IDs are equal *** FAILED ***
[info]   TestFailedException was thrown during property evaluation.
[info]     Message: 1.1794559135007427E-271 was not equal to 7.871712821709093E212
[info]     Location: (testnew.scala:52)
[info]     Occurred when passed generated values (
[info]       arg0 = A(ABC,1.1794559135007427E-271,-1.6982696700585273E-23),
[info]       arg1 = B(ABC,7.871712821709093E212,-8.820696498155311E234)
[info]     )

Теперь легко понять, почему второе свойство не удалось. Потому что каждый раз, когда я даю A1 и B1, я получаю другое значение для id, а не для ctrl, потому что это константа. Вот мой второй подход, в котором я создаю вложенный for-yield, чтобы попытаться достичь своей цели -

case class Popo(
    controller: String,
    id: Double,
    someA: Gen[A],
    someB: Gen[B]
)

trait Generators {
    val obj = for {
        ctrl <- Gen.alphaStr
        idz <- Arbitrary.arbitrary[Double]
        val someA = for {
            x <- Arbitrary.arbitrary[Double]
        } yield A(ctrl, idz, someA)
        val someB = for {
            y <- Arbitrary.arbitrary[Double]
        } yield B(ctrl, idz, y)
    } yield Popo(ctrl, idz, x, someB)
}

class Something extends PropSpec with PropertyChecks with Matchers with Generators{

    property("Controllers are equal") {
        forAll(obj) {
            (x: Popo) => 
            forAll(x.someA, x.someB) {
                (a:A,b:B) => 
                    a.controller should be (b.controller)
            }
        }
    }

    property("IDs are equal") {
        forAll(obj) {
            (x: Popo) =>
            forAll(x.someA, x.someB) {
                (a:A,b:B) => 
                    a.id should be (b.id)
            }
        }
    }
}

Запуск sbt test при втором подходе говорит мне, что все тесты пройдены.

[info] Something:
[info] - Controllers are equal
[info] - IDs are equal
[info] ScalaTest
[info] Run completed in 335 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.

Есть ли лучший / альтернативный способ воспроизвести мои желаемые результаты? Вложение forAll кажется мне довольно неуклюжим. Если бы в моем графе зависимостей для объектов, совместно использующих элементы, было R -> S -> ... V -> W, мне пришлось бы создать столько же вложенных forAll.

1 Ответ

2 голосов
/ 03 июня 2019

Я собираюсь дать ответ только в Скалахеке.Я знаю, что Scalatest популярен, но я считаю, что его включение в вопрос о Scalacheck отвлекает, особенно когда нет причин, по которым пример не может быть написан без него.

Кажется, вы хотите проверить Aи B, но они делятся информацией.Одним из способов представления этой зависимости был бы класс Popo, который вы написали.Он содержит как общую информацию, так и сгенерированные значения A и B.Другой вариант - генерация общих значений между A и B в классе.

Самое простое решение - генерировать A и B в парах (кортеж из двух).К сожалению, есть некоторые уловки, чтобы заставить это работать.Вам нужно будет использовать ключевое слово case в свойстве forAll.Вы не можете предоставить свидетельство для значения implicit для Arbitrary кортежей, поэтому у вас есть , чтобы явно указать генератор для кортежей в forAll.

import org.scalacheck.Gen
import org.scalacheck.Arbitrary
import org.scalacheck.Prop
import org.scalacheck.Prop.AnyOperators
import org.scalacheck.Properties

case class A(
  controller: String,
  id: Double,
  x: Double
)

case class B(
  controller: String,
  id: Double,
  y: Double
)

object BaseGenerators {
  val ctrl = Gen.const("ABC")
  val idz = Arbitrary.arbitrary[Double]
}

object Generators {
  val obj = BaseGenerators

  val genAB: Gen[(A,B)] = for {
    controller <- obj.ctrl
    id <- obj.idz
    x <- Arbitrary.arbitrary[Double]
    y <- Arbitrary.arbitrary[Double]
    val a = A(controller, id, x)
    val b = B(controller, id, y)
  } yield (a, b)                                         // !
}

class Something extends Properties("Something") {

  property("Controllers and IDs are equal") = {
    Prop.forAll(Generators.genAB) { case (a: A, b: B) => // !
      a.controller ?= b.controller && a.id ?= b.id
    }
  }
}

Что касается вашего более широкого вопроса о наличии объектов, которые совместно используют информацию, вы можете представить ее, написав свои генераторы с аргументами функции.Однако для этого все равно потребуются вложенные генераторы forAll.

object Generators {
  val obj = BaseGenerators

  val genA = for {
    controller <- obj.ctrl
    id <- obj.idz
    x <- Arbitrary.arbitrary[Double]
  } yield A(controller, id, x)

  def genB(a: A) = for {                                 // !
    y <- Arbitrary.arbitrary[Double]
  } yield B(a.controller, a.id, y)
}

class Something extends Properties("Something") {

  implicit val arbA: Arbitrary[A] = Arbitrary {
    Generators.genA
  }

  property("Controllers and IDs are equal") = {
    Prop.forAll { a: A =>                                // !
      Prop.forAll(Generators.genB(a)) { b: B =>          // !
        (a.controller ?= b.controller) && (a.id ?= b.id)
      }
    }
  }
}
...