2013-04-29 102 views
10

我一直在尝试采用蛋糕模式,但我很难适应这种编程风格,特别是在单元测试方面。Scala:嘲讽和蛋糕模式

让我们假设我有以下业务对象:

trait Vet { 
    def vaccinate(pet: Pet) 
} 

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

现在,我想测试的PetStore而从兽医嘲笑的职能。如果我正在使用组合,我正在创建一个模拟[Vet]并将其传递给PetStore构造函数,然后像我们在Java世界中那样对模拟进行编程。然而,我无法找到人们如何用蛋糕模式来做到这一点。

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

那么 - 人们如何使用Cake Pattern与模拟对象?

回答

6

我在阅读此博客文章后开始使用蛋糕模式:https://github.com/precog/staticsite/blob/master/contents/blog/Existential-Types-FTW/index.md该方法与大多数Cake Pattern文章不同,因为使用存在类型而不是自我类型。

我一直在使用这种模式几个月,它似乎工作得很好,因为我可以指定一个模拟,当我想。它具有更多依赖注入的感觉,但它具有让您的代码具有特性的所有好处。

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

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 
+0

这很酷 - 但我错过了什么吗?你在PetStore的Vet类型中做了什么? – 2013-05-01 11:15:07

+0

所以我试图给出一个没有使用ConfigComponent特性的例子,但我是做错事。无论如何,我已经更新了这个例子并添加了ConfigComponent。希望事情会更加清晰。 – OleTraveler 2013-05-03 22:40:32

+0

+1 https://www.precog.com/blog/Existential-Types-FTW/ – 2013-08-28 08:09:32

4

这是一个很好的问题。我们得出的结论是无法完成的,至少与我们习惯的方式不一样。可以使用存根来代替模拟,并且可以用蛋糕方式混合存根。但是这比使用mock更有用。

我们有两个Scala团队和一个团队采用了蛋糕模式,使用存根而不是模拟,而另一个团队坚持类和依赖注入。现在我已经尝试过了,因为它更容易测试,所以我更喜欢DI。而且可以说读起来也更简单。

+1

的时间这是最初我以为最多的回答。但是,当我开始使用Scala越来越多的工作,我到了在这里我将相同概念业务对象的不同关注点与可测试性和清晰度目的的不同特性分开,在这里使用DI会导致一个过大的对象图和繁琐的应用程序初始化代码 – 2013-04-29 16:03:41

+0

YMMV就像他们说的那样。 – 2013-04-30 07:05:09

2

我发现了一种使用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 
1

尽管这是一个老问题,我加入我的回答对未来的读者。我相信这个后 - How to use mocks with the Cake Pattern - 问和回答相同的事情。

我成功之后弗拉基米尔Matveev给出的答案(这是在写