10

我一直在尝试采用 Cake Pattern,但我在适应这种编程风格时遇到了困难,尤其是在单元测试方面。

假设我有以下业务对象:

trait Vet {
  def vaccinate(pet: Pet)
}

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

现在,我想在模拟 Vet 的函数时测试 PetStore。如果我使用组合,我将创建一个 mock[Vet] 并将其传递给 PetStore 构造函数,然后像在 Java 世界中那样对 mock 进行编程。但是,我找不到任何关于人们如何使用蛋糕图案来做到这一点的参考。

一种可能的解决方案是根据预期的用法在每个测试用例上实现 vaccinate() ,但这不允许我验证模拟是否被正确调用,不允许我使用匹配器等。

那么 - 人们如何将蛋糕模式与模拟对象一起使用?

4

4 回答 4

6

阅读这篇博文后,我开始使用蛋糕模式:https ://github.com/precog/staticsite/blob/master/contents/blog/Existential-Types-FTW/index.md 方法与大多数蛋糕模式不同使用存在类型而不是自我类型的帖子。

我已经使用这种模式几个月了,它似乎工作得很好,因为我可以在我想要的时候指定一个模拟。它确实有更多的依赖注入感觉,但它具有将代码放入特征中所获得的所有好处。

我使用存在类型的问题的混蛋版本将是这样的:

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

您可以通过创建 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
于 2013-04-29T18:23:41.910 回答
4

这是个好问题。我们得出的结论是无法做到,至少与我们习惯的方式不同。可以使用存根代替模拟,并以蛋糕的方式混合存根。但这比使用模拟更有效。

我们有两个 Scala 团队,一个团队采用了蛋糕模式,使用 stubs 而不是 mocks,而另一个团队坚持使用类和依赖注入。现在我都试过了,我更喜欢带模拟的 DI,因为它更容易测试。并且可以说更容易阅读。

于 2013-04-29T15:20:06.060 回答
2

我找到了一种将 Scalamock 与 Scalatest 一起使用的方法,以对“Cake Pattern”模块进行单元测试。

起初,我遇到了很多问题(包括这个问题),但我相信我在下面提出的解决方案是可以接受的。如果您有任何疑虑,请告诉我。

这就是我设计您的示例的方式:

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
于 2013-08-19T00:11:39.650 回答
1

虽然这是一个老问题,但我正在为未来的读者添加我的答案。我相信这篇 SO 帖子 - How to use mocks with the Cake Pattern - 问和回答了同样的事情。

我成功地遵循了 Vladimir Matveev 给出的答案(这是撰写本文时的最佳答案

于 2016-03-30T16:19:03.473 回答