简体   繁体   English

斯卡拉:嘲弄和蛋糕模式

[英]Scala: Mocking and the Cake Pattern

I've been trying to adopt the Cake Pattern but I'm having difficulties adapting to this programming styles, especially where unit tests are concerned. 我一直在尝试采用Cake Pattern,但我很难适应这种编程风格,特别是在涉及单元测试的情况下。

Lets assume that I have the following business objects: 让我们假设我有以下业务对象:

trait Vet {
  def vaccinate(pet: Pet)
}

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

Now, I'd like to test PetStore while mocking out the functions from Vet. 现在,我想在模拟Vet的功能时测试PetStore。 If I was using composition, I was creating a mock[Vet] and passing it to the PetStore constructor, then programming the mock like we do in the Java world. 如果我正在使用合成,我正在创建一个模拟[Vet]并将其传递给PetStore构造函数,然后像在Java世界中那样编写模拟。 However, I can't find any reference to how people do this with the cake pattern. 但是,我找不到人们如何用蛋糕模式做这个的任何参考。

One possible solution would be to implement vaccinate() on each test case according to the expected usage, but this then doesn't allow me to verify that the mocks were called properly, doesn't allow me to use matchers, etc. 一种可能的解决方案是根据预期的用法在每个测试用例上实施vaccinate(),但这不允许我验证模拟被正确调用,不允许我使用匹配器等。

So - how are people using Cake Pattern with mock objects? 那么 - 人们如何使用Cake Pattern与模拟对象?

I started using the cake pattern after I read this blog post: https://github.com/precog/staticsite/blob/master/contents/blog/Existential-Types-FTW/index.md The approach is different from most Cake Pattern posts in that existential-types are used instead of self-types. 我在阅读这篇博文后开始使用蛋糕模式: https//github.com/precog/staticsite/blob/master/contents/blog/Existential-Types-FTW/index.md这种方法与大多数蛋糕模式不同使用那些存在类型的帖子而不是自我类型。

I have been using this pattern for a few months and it seems to work out well as I can specify a mock when I want to. 我已经使用这种模式几个月了,它看起来效果很好,因为我可以在我想要的时候指定一个模拟。 It does have more a dependency injection feel to it, but it has all the benefits you get of having your code in traits. 它确实具有更多的依赖注入感觉,但它具有使您的代码具有特征的所有好处。

My bastardized version of your problem using existential-types would be something like this: 我使用存在类型的问题的混蛋版本将是这样的:

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

You can put it all together in your app 你可以把它们放在你的应用程序中

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 = MyApp@668dd96c

And you can test the components individually by creating an instance of VetLike and also creating a mock of VetLike an using it your PetStore test. 您可以通过创建VetLike实例来单独测试组件,还可以使用PetStore测试创建VetLike模拟器。

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

It's a good question. 这是一个很好的问题。 We came to the conclusion it can't be done, at least not quite the same way we're used to. 我们得出的结论是,它无法完成,至少与我们习惯的方式不同。 It's possible to use stubs instead of mocks and mix the stubs in cake-wise. 可以使用存根而不是模拟,并以蛋糕方式混合存根。 But this is more work than using mocks. 但这比使用模拟更多的工作。

We have two Scala teams and one team adopted the cake pattern, using stubs instead of mocks, whilst the other team stuck to classes and dependency injection. 我们有两个Scala团队和一个团队采用了蛋糕模式,使用存根而不是模拟,而另一个团队坚持使用类和依赖注入。 Now I've tried both, I prefer DI with mocks due to it being simpler to test. 现在我已经尝试了两种,我更喜欢DI与模拟,因为它更容易测试。 And arguably simpler to read too. 而且可以说也更容易阅读。

I have found a way to use Scalamock with Scalatest for the purpose of unit testing 'Cake Pattern' modules. 我找到了一种方法来使用Scalamock和Scalatest进行单元测试'Cake Pattern'模块。

At first, I had many problems (including this one), but I believe the solution I present below is acceptable. 起初,我遇到了很多问题(包括这个问题),但我相信我在下面提出的解决方案是可以接受的。 If you have any concerns, please let me know. 如果您有任何疑虑,请告诉我。

This is how I would design your example: 这就是我设计你的例子的方式:

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

The tests are then defined as following: 然后将测试定义如下:

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
}

Using this setup, you have the 'disadvantage' that you have to call val vet = fixture in every test you write. 使用此设置,您必须在编写的每个测试中调用val vet = fixture On the other hand, one can easily create another 'implementation' of the test, eg, 另一方面,人们可以轻松地创建测试的另一个“实现”,例如,

val someOtherPetStoreImpl = new PetstoreBehavior with PetStoreModuleOtherImpl

Although this is an old question, I'm adding my answer for future readers. 虽然这是一个老问题,但我正在为未来的读者添加我的答案。 I believe this SO post - How to use mocks with the Cake Pattern - asks and answers the same thing. 我相信这个SO帖子 - 如何使用蛋糕模式的模拟 - 询问并回答相同的事情。

I successfully followed the answer given by Vladimir Matveev (which was the top answer at the time of writing 我成功地遵循了弗拉基米尔·马特维耶夫给出的答案(这是撰写本文时的最佳答案)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM