Scala: Издевательство и шаблон торта

Я пытаюсь принять шаблон Cake, но у меня возникают трудности с адаптацией к этим стилям программирования, особенно в тех случаях, когда тестируются модули.

Предположим, что у меня есть следующие бизнес-объекты:

trait Vet {
  def vaccinate(pet: Pet)
}

trait PetStore { this: Vet =>
  def sell(pet: Pet) {
    vaccinate(pet)
    // do some other stuff
  }
}

Теперь, я бы хотел проверить PetStore, издеваясь над функциями Vet. Если я использовал композицию, я создавал макет [Vet] и передавал его в конструктор PetStore, а затем программировал макет, как в мире Java. Тем не менее, я не могу найти никакой ссылки на то, как люди делают это с шаблоном торта.

Одним из возможных решений было бы внедрение вакцинации() на каждом тестовом сценарии в соответствии с ожидаемым использованием, но это не позволяет мне проверять правильность начисления маков, не позволяет мне использовать сокеты и т.д..

Итак - как люди используют Cake Pattern с макетными объектами?

Ответ 1

Я начал использовать шаблон торта после того, как прочитал этот пост в блоге: https://github.com/precog/staticsite/blob/master/contents/blog/Existential-Types-FTW/index.md. Этот подход отличается от большинства сообщений Cake Pattern в этом экзистенциальном -типы используются вместо самостоятельных типов.

Я использую этот шаблон в течение нескольких месяцев, и, похоже, он работает хорошо, и я могу указать макет, когда захочу. У этого есть больше зависимостей от инъекций зависимостей, но у него есть все преимущества, которые вы получаете от своего кода в чертах.

Моя неудачная версия вашей проблемы с использованием экзистенциальных типов будет примерно такой:

case class Pet(val name: String)
trait ConfigComponent {
  type Config
  def config: Config
}

trait Vet {
  def vaccinate(pet: Pet) = {println ("Vaccinate:" + pet)}
}

trait PetStoreConfig {
  val vet: Vet
}
trait PetStore extends ConfigComponent {

    type Config <: PetStoreConfig

    def sell(pet: Pet) {
      config.vet.vaccinate(pet)
      // do some other stuff
    }
}

Вы можете собрать все это в своем приложении

class MyApp extends PetStore with PetStoreConfig {

  type Config = MyApp
  def config = this  

  val vet = new Vet{}
  sell(new Pet("Fido"))

}

scala> new MyApp
Vaccinate:Pet(Fido)
res0: MyApp = [email protected]

И вы можете протестировать компоненты по отдельности, создав экземпляр VetLike, а также создав макет VetLike, используя его тест PetStore.

//Test VetLike Behavior
scala> val vet = new Vet{}
scala> vet.vaccinate(new Pet("Fido"))
Vaccinate:Pet(Fido)


//Test Petstore Behavior

class VetMock extends Vet {
   override def vaccinate(pet: Pet) = println("MOCKED")
}

class PetStoreTest extends PetStore with PetStoreConfig {
   type Config = PetStoreTest
   def config = this

   val vet = new VetMock
   val fido = new Pet("Fido")
   sell(fido)
}

scala> new PetStoreTest
MOCKED

Ответ 2

Это хороший вопрос. Мы пришли к выводу, что это невозможно сделать, по крайней мере, не совсем так, как мы привыкли. Можно использовать заглушки вместо издевок и смешивать заглушки тортами. Но это больше, чем использование mocks.

У нас есть две команды Scala, и одна команда приняла шаблон торта, используя вместо него заглушки, а другая команда придерживалась классов и инъекций зависимостей. Теперь я пробовал оба, я предпочитаю DI с издевательствами из-за того, что его проще тестировать. И, возможно, проще читать тоже.

Ответ 3

Я нашел способ использовать Scalamock со Scalatest для модульного тестирования модулей Cake Pattern.

Сначала у меня было много проблем (в том числе this), но я считаю, что решение, представленное ниже, приемлемо. Если у вас есть какие-либо проблемы, сообщите мне.

Вот как я бы разработал ваш пример:

trait VetModule {
  def vet: Vet
  trait Vet {
    def vaccinate(pet: Pet)
  }
}

trait PetStoreModule {
  self: VetModule =>
  def sell(pet: Pet)
}

trait PetStoreModuleImpl extends PetStoreModule {
  self: VetModule =>
  def sell(pet: Pet) {
    vet.vaccinate(pet)
    // do some other stuff
  }
}

Затем тесты определяются следующим образом:

class TestPetstore extends FlatSpec with ShouldMatchers with MockFactory {

  trait PetstoreBehavior extends PetStoreModule with VetModule {

    object MockWrapper {
      var vet: Vet = null
    }

    def fixture = {
      val v = mock[Vet]
      MockWrapper.vet = v
      v
    }

    def t1 {
      val vet = fixture
      val p = Pet("Fido")
      (vet.vaccinate _).expects(p)
      sell(p)
    }

    def vet: Vet = MockWrapper.vet
  }

  val somePetStoreImpl = new PetstoreBehavior with PetStoreModuleImpl
  "The PetStore" should "vaccinate an animal before selling" in somePetStoreImpl.t1
}

Используя эту настройку, у вас есть "недостаток", который вы должны называть val vet = fixture в каждом тестировании, которое вы пишете. С другой стороны, можно легко создать еще одну "реализацию" теста, например,

val someOtherPetStoreImpl = new PetstoreBehavior with PetStoreModuleOtherImpl

Ответ 4

Хотя это старый вопрос, я добавляю свой ответ будущим читателям. Я верю, что это сообщение SO - Как использовать mocks с шаблоном Cake - спрашивает и отвечает на одно и то же.

Я успешно последовал за ответом Владимира Матвеева (который был лучшим ответом на момент написания