2013-04-29 88 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給出的答案(這是在寫