2013-04-29 14 views
10

Próbowałem przyjąć wzór ciasta, ale mam trudności z dostosowaniem się do tego stylu programowania, szczególnie w przypadku testów jednostkowych.Scala: Kpiny i wzór ciasta

Załóżmy, że mam następujące obiekty biznesowe:

trait Vet { 
    def vaccinate(pet: Pet) 
} 

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

Teraz chciałbym przetestować PetStore podczas szydząc swoje funkcje od weterynarza. Gdybym używał kompozycji, tworzyłem fałszywy [Vet] i przekazywałem go konstruktorowi PetStore, a następnie programowałem symulację, tak jak robimy to w świecie Java. Jednak nie mogę znaleźć żadnego odniesienia do tego, jak ludzie robią to z wzorcem ciasta.

Jednym z możliwych rozwiązań byłoby zaimplementowanie szczepienia() w każdym przypadku testowym zgodnie z oczekiwanym użyciem, ale to nie pozwala mi na sprawdzenie, czy makiety zostały poprawnie wywołane, nie pozwala mi używać dopasowań, itp.

A więc, w jaki sposób ludzie używają wzoru ciasta z próbnymi obiektami?

Odpowiedz

6

Zacząłem używać wzoru tortu po przeczytaniu tego wpisu na blogu: https://github.com/precog/staticsite/blob/master/contents/blog/Existential-Types-FTW/index.md Podejście różni się od większości postów Cake Pattern, ponieważ typy egzystencjalne są używane zamiast self-types.

Używam tego wzoru przez kilka miesięcy i wygląda na to, że działa dobrze, ponieważ mogę określić próbę, kiedy chcę. To ma więcej poczucia zależności od wtrysku, ale ma wszystkie zalety posiadania kodu w cechach.

Moja bastardized wersję swojego problemu przy użyciu egzystencjalne typów byłoby coś takiego:

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

Można je połączyć w aplikacji

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] 

I można testować komponenty indywidualnie tworząc instancję VetLike, a także tworząc próbkę VetLike i używając jej twojego testu PetStore.

+0

To jest fajne - ale czy brakuje mi czegoś? Co robisz z typem Vet w PetStore? –

+0

Tak więc próbowałem podać przykład bez użycia cechy ConfigComponent, ale byłem robienie czegoś złego. W każdym razie zaktualizowałem ten przykład i dodałem ConfigComponent. Mam nadzieję, że wszystko wygląda trochę bardziej wyraźnie. – OleTraveler

+0

+1 dla https://www.precog.com/blog/Existential-Types-FTW/ –

4

To dobre pytanie. Doszliśmy do wniosku, że nie da się tego zrobić, przynajmniej nie w taki sam sposób, w jaki jesteśmy przyzwyczajeni. Możliwe jest używanie stubów zamiast makiet i mieszanie stubów w ciasto. Ale to więcej pracy niż przy użyciu makiet.

Mamy dwie drużyny Scali i jeden zespół zastosował wzór tortu, używając stubów zamiast makiet, podczas gdy drugi zespół przylgnął do klas i zastrzyku zależności. Teraz próbowałem obu, wolę DI z mocks ze względu na prostsze testowanie. I prawdopodobnie łatwiejsze do odczytania.

+1

To było początkowo mój pomysł. Jednak, jak zacząłem pracować więcej i więcej ze Scala, mam do etap, w którym oddzielam różne obawy tych samych konceptualnych obiektów biznesowych od różnych cech pod kątem testowalności i jasności .. Użycie tutaj DI spowoduje nadmiernie duży wykres obiektów i kłopotliwy kod inicjalizacji aplikacji –

+0

YMMV jak mówią Nasze doświadczenia były odwrotne –

2

Znalazłem sposób użycia Scalamock z Scalatest do celów testowania modułów "Cake Pattern".

Na początku miałem wiele problemów (w tym jeden this), ale uważam, że przedstawione poniżej rozwiązanie jest do przyjęcia. Jeśli masz jakiekolwiek wątpliwości, daj mi znać.

To w jaki sposób zaprojektować swój przykład:

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

Testy są następnie zdefiniowane następująco:

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 
} 

Stosując tę ​​konfigurację, masz „wadę”, że trzeba zadzwonić val vet = fixture w każdym teście, który napiszesz. Z drugiej strony, można łatwo stworzyć kolejny „dokonanie” testu, np

val someOtherPetStoreImpl = new PetstoreBehavior with PetStoreModuleOtherImpl 
1

Choć jest to stare pytanie, dodaję moją odpowiedź dla przyszłych czytelników. Wierzę, że to SO post - How to use mocks with the Cake Pattern - prosi i odpowiada na to samo.

pomyślnie następnie odpowiedź udzieloną przez Vladimir Matveev (która była najlepiej odpowiedź w czasie pisania