2012-02-02 12 views
8

otrzymał abstrakcyjne realizację fabryczną:Unit Testing streszczenie fabryka, która pobiera parametry

public class FooFactory : IFooFactory { 
    public IFoo Create(object param1, object param2) { 
     return new Foo(param1, param2); 
    } 
} 

Jakie badania jednostka będzie napisany dla tej klasy? Jak mogę sprawdzić, czy param1 i param2 zostały przekazane do utworzenia Foo? Czy muszę tworzyć te publiczne właściwości Foo? Czy to nie złamałoby hermetyzacji? Czy powinienem to zostawić testom integracyjnym?

+0

Testy jednostkowe powinny zależeć od wymagań funkcjonalnych i oczekiwań względem badanego komponentu. Nie widzę żadnej wartości jednostki testującej tę klasę bez reszty kontekstu. – ivowiblo

+0

@ivowiblo Myślę, że mylicie testy jednostkowe z testami BDD. Jeśli nie napiszesz testu dla tego urządzenia, to skąd wiesz, czy to działa? –

+0

Wykonaj test jednostki, ale co będziesz testować? jakie jest oczekiwanie na tę jednostkę? Powiedziałem, że nie widzę wartości robienia testu jednostkowego dla tej klasy, nie wiedząc nic więcej. Przeprowadzanie testów jednostkowych z powodu przeprowadzania testów jednostkowych w ogóle nie ma sensu. Testowanie (jednostki, zachowania, pełne funkcjonalności, cokolwiek chcesz) powinno zawsze opierać się na oczekiwaniach i wymaganiach. Jeśli nie, to po prostu snobizm (jeśli to słowo istnieje). – ivowiblo

Odpowiedz

11

Oto jak chciałbym napisać jedną z kilku testów jednostkowych do takiego zakładu (z xUnit.net):

[Fact] 
public void CreateReturnsInstanceWithCorrectParam1() 
{ 
    var sut = new FooFactory(); 
    var expected = new object(); 
    var actual = sut.Create(expected, new object()); 
    var concrete = Assert.IsAssignableFrom<Foo>(actual); 
    Assert.Equal(expected, concrete.Object1); 
} 

Czy przerwać hermetyzacji? Tak i nie ... trochę. W enkapsulacji nie chodzi tylko o ukrywanie danych - co ważniejsze, jest to około protecting the invariants of objects.

Załóżmy, że Foo eksponuje tę publicznego API:

public class Foo : IFoo 
{ 
    public Foo(object param1, object param2); 

    public void MethodDefinedByInterface(); 

    public object Object1 { get; } 
} 

Podczas gdy nieruchomość Object1 lekko naruszyć Law of Demeter, to nie bałagan z niezmienników klasy, ponieważ jest tylko do odczytu.

Ponadto nieruchomość Object1 jest częścią betonu klasy Foo - nie interfejs IFoo:

public interface IFoo 
{ 
    void MethodDefinedByInterface(); 
} 

Kiedy zdajesz sobie sprawę, że w luźno API, concrete members are implementation details takie konkretne tylko do odczytu tylko obiekty bardzo mały wpływ na enkapsulację. Pomyśl o tym w ten sposób:

publiczna konstruktor Foo jest również częścią API betonu klasy Foo, więc po prostu sprawdzając publicznych API, dowiadujemy się, że param1 i param2 są częścią klasy. W pewnym sensie to już "przerywa enkapsulację", więc udostępnienie każdego parametru jako właściwości tylko do odczytu na konkretnej klasie niewiele się zmienia.

Dzięki tym właściwościom możemy teraz jednostce przetestować konstrukcyjny kształt klasy Foo zwróconej przez fabrykę.

Jest to znacznie łatwiejsze niż powtarzanie zestawu behawioralnych testów jednostkowych, które, jak możemy przyjąć, obejmują już konkretną klasę Foo. To prawie jak logiczny dowód:

  1. Z testów konkretnej klasy Foo wiemy, że poprawnie używa/współdziała z jej parametrami konstruktora.
  2. Z tych testów wiemy również, że parametr konstruktora jest wyświetlany jako właściwość tylko do odczytu.
  3. Z testu FooFactory wiemy, że zwraca instancję konkretnej klasy Foo.
  4. Ponadto, z testów metody Create wiemy, że jej parametry są poprawnie przekazywane do konstruktora Foo.
  5. Q.E.D.
+0

Tak, ma sens. Podoba mi się pomysł rozróżnienia między publicznym interfejsem API a konkretną klasą. – JontyMC

+0

Dlaczego anonimowy downwise? –

0

Można uczynić FooFactory klasą ogólną, która oczekuje Foo jako parametru i określić, że jest to makiety. Wywołanie fabryki. Stwórz (...) następnie stwórz instancję próbną, a następnie możesz potwierdzić, że fałszywa otrzymała oczekiwane argumenty.

+2

Drogi Gödel nr. To testowanie szczegółów implementacji. – jason

+0

@Jason: Gorzej ... Podnosi to kwestię stworzenia fabryki dla tej fabryki, aby uprościć generowanie obiektu, ale jeśli JontyMC zapyta ... – Arafangion

0

To zależy od tego, co Foo robi z 2 obiektami wejściowymi. Sprawdź, co robi z nimi Foo, na które łatwo można się natknąć. Na przykład, jeśli metody (w tym metody pobierające lub ustawiające) są wywoływane na obiektach, podaj mocks lub kody pośredniczące, które możesz zweryfikować, gdy wywołane zostaną oczekiwane rzeczy. Jeśli istnieje coś na IFoo, które można przetestować przeciwko, możesz przekazać znane wartości za pomocą fałszywych obiektów, aby sprawdzić po utworzeniu. Obiekty nie powinny być publicznie dostępne, aby sprawdzić, czy je przeszedł.

Mogę również sprawdzić, czy oczekiwany typ (Foo) jest zwracany, w zależności od tego, czy inne testowane zachowania zależą od różnicy między implementacjami IFoo.

Jeśli wystąpią jakiekolwiek warunki błędu, spróbuję je również wymusić, co się stanie, jeśli przekażesz null dla jednego lub obu obiektów, itp. Foo byłoby testowane osobno, więc może założyć podczas testu FooFactory, że Foo robi to, co powinien.

+0

Testowałbyś typ zwrotu? Co, u licha, kupujesz? Przygotowałeś wszystkie te abstrakcje, a teraz napiszesz test, który psuje się zaraz po zmianie szczegółów implementacji. Yikes. – jason

+0

@Jason Jeśli mam zamiar napisać test dla wielu fabryk, z których każda tworzy implementację IFoo, sprawdziłbym, czy każdy zwraca konkretny typ, jakiego oczekuję. Pytam o to, aby przetestować fabrykę, która robi tak mało, jak zadzwoń, ale w sferze tego, o co go proszono ... –

+0

Nie. To nie jest zachowanie, na które mogą polegać twórcy w fabryce (typem powrotu jest 'IFoo'), więc nie powinno się go testować. – jason

0

Fabryki zazwyczaj nie są testowane na jednostkach, przynajmniej według mojego doświadczenia - nie ma zbyt wiele wartości w jednostkach testujących je. Ponieważ jest to implementacja tego mapowania implementacji interfejsu, stąd odnosi się do konkretnej implementacji. W związku z tym nie można sprawdzić, czy odbiera te parametry przekazane.

Z drugiej strony, zwykle kontenery DI działają jak fabryki, więc jeśli używasz jednego, nie potrzebujesz fabryki.

+0

Abstrakcyjne fabryki są wymagane, gdy zależność zależy od parametrów znanych tylko w czasie wykonywania lub ich żywotność jest krótsza niż w przypadku konsumenta: http://stackoverflow.com/questions/1993397/abstract-factory-pattern-on-top- of-ioc – JontyMC

+0

Większość szkieletów DI wykonywałaby przekazywanie parametrów w środowisku wykonawczym. Mogą również wykonywać zarządzanie na całe życie. – Aliostad

2

Cóż, prawdopodobnie te parametry sprawiają, że zwracany IFoo ma coś prawdziwego na ten temat. Sprawdź, czy to prawda w odniesieniu do zwróconego wystąpienia.

+0

Czy to nie oznacza, że ​​powinieneś wiedzieć o wewnętrznych cechach danego IFoo? – JontyMC

+0

Nie. Podajesz te parametry z jakiegoś powodu, aby uzyskać oczekiwane zachowanie. Jakie to zachowanie? Sprawdź to. Spec "FooFactory.Create" powinien powiedzieć coś w stylu "tworzy obiekt, który implementuje' IFoo' tak, że 'P (param1, param2)' gdzie 'P' jest predykatem zależnym od' param1' i 'param2'." Sprawdź, czy 'P (param1, param2)' jest prawdziwe dla zwróconego obiektu! – jason

+0

Więc ... zakładając, że Foo jest nietrywialny i już objęty N testami, zasadniczo musisz napisać N nowych, prawie identycznych testów, aby przetestować, że zachowanie zwróconej instancji Foo jest zgodne z danymi wejściowymi do utworzenia metoda. Wygląda na to, że pośrednio łączysz testy. W dalszej kolejności pojawią się problemy z utrzymaniem. –

Powiązane problemy