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 - спрашивает и отвечает на одно и то же.
Я успешно последовал за ответом Владимира Матвеева (который был лучшим ответом на момент написания