Nie uważam się za eksperta DDD, ale jako architekt rozwiązań staram się stosować najlepsze praktyki, gdy tylko jest to możliwe. Wiem, że wokół DDD jest wiele dyskusji na temat przeciwników i przeciwników „stylu” setera no (publicznego) i widzę obie strony argumentu. Mój problem polega na tym, że pracuję w zespole o dużej różnorodności umiejętności, wiedzy i doświadczenia, co oznacza, że nie mogę ufać, że każdy programista zrobi wszystko „we właściwy sposób”. Na przykład, jeśli nasze obiekty domeny są zaprojektowane w taki sposób, że zmiany stanu wewnętrznego obiektu są przeprowadzane za pomocą metody, ale zapewniają ustawiacze właściwości publicznych, ktoś nieuchronnie ustawi właściwość zamiast wywoływać metodę. Skorzystaj z tego przykładu:
public class MyClass
{
public Boolean IsPublished
{
get { return PublishDate != null; }
}
public DateTime? PublishDate { get; set; }
public void Publish()
{
if (IsPublished)
throw new InvalidOperationException("Already published.");
PublishDate = DateTime.Today;
Raise(new PublishedEvent());
}
}
Moim rozwiązaniem było uczynienie ustawień właściwości prywatnymi, co jest możliwe, ponieważ ORM, którego używamy do uwodnienia obiektów, wykorzystuje odbicie, aby mógł uzyskać dostęp do ustawień prywatnych. Stanowi to jednak problem podczas próby napisania testów jednostkowych. Na przykład, gdy chcę napisać test jednostkowy, który weryfikuje wymóg, że nie możemy ponownie opublikować, muszę wskazać, że obiekt został już opublikowany. Z pewnością mogę to zrobić przez dwukrotne wywołanie funkcji Publikuj, ale mój test zakłada, że Publikacja jest poprawnie zaimplementowana dla pierwszego wywołania. To wydaje się trochę śmierdzące.
Uczyńmy scenariusz nieco bardziej realistycznym za pomocą następującego kodu:
public class Document
{
public Document(String title)
{
if (String.IsNullOrWhiteSpace(title))
throw new ArgumentException("title");
Title = title;
}
public String ApprovedBy { get; private set; }
public DateTime? ApprovedOn { get; private set; }
public Boolean IsApproved { get; private set; }
public Boolean IsPublished { get; private set; }
public String PublishedBy { get; private set; }
public DateTime? PublishedOn { get; private set; }
public String Title { get; private set; }
public void Approve(String by)
{
if (IsApproved)
throw new InvalidOperationException("Already approved.");
ApprovedBy = by;
ApprovedOn = DateTime.Today;
IsApproved = true;
Raise(new ApprovedEvent(Title));
}
public void Publish(String by)
{
if (IsPublished)
throw new InvalidOperationException("Already published.");
if (!IsApproved)
throw new InvalidOperationException("Cannot publish until approved.");
PublishedBy = by;
PublishedOn = DateTime.Today;
IsPublished = true;
Raise(new PublishedEvent(Title));
}
}
Chcę napisać testy jednostkowe, które weryfikują:
- Nie mogę opublikować, dopóki dokument nie zostanie zatwierdzony
- Nie mogę ponownie opublikować dokumentu
- Po opublikowaniu wartości Opublikowany przez i Opublikowane przez są odpowiednio ustawione
- Po opublikowaniu publikowane jest EventEvent
Bez dostępu do ustawiaczy nie mogę wprowadzić obiektu w stan wymagany do przeprowadzenia testów. Otwarcie dostępu do seterów uniemożliwia dostęp.
Jak rozwiązałeś (d) ten problem?
źródło
Odpowiedzi:
Jeśli nie możesz wprowadzić obiektu do stanu potrzebnego do przeprowadzenia testu, to nie możesz wprowadzić obiektu do stanu w kodzie produkcyjnym, więc nie ma potrzeby testowania tego stanu. Oczywiście, nie jest to prawdą w przypadku, można umieścić obiekt do stanu potrzebne, wystarczy zadzwonić Zatwierdź.
Nie mogę opublikować, dopóki dokument nie zostanie zatwierdzony: napisz test, który wywołując polecenie opublikuj przed wywołaniem polecenia zatwierdzenia powoduje prawidłowy błąd bez zmiany stanu obiektu.
Nie mogę ponownie opublikować dokumentu: napisz test, który zatwierdza obiekt, a następnie wywołanie polecenia opublikuj po pomyślnym zakończeniu, ale drugi raz spowoduje poprawny błąd bez zmiany stanu obiektu.
Po opublikowaniu wartości OpublikowanyBy i OpublikowanyOn są poprawnie ustawione: napisz test, który wywołuje zatwierdzenie, a następnie wywołaj publikowanie, stwierdzając, że stan obiektu zmienia się poprawnie
Po opublikowaniu publikowane jest EventEvent: podłącz system zdarzeń i ustaw flagę, aby upewnić się, że jest wywoływana
Musisz także napisać test do zatwierdzenia.
Innymi słowy, nie testuj relacji między polami wewnętrznymi a IsPublished i IsApproved, twój test byłby dość kruchy, jeśli to zrobisz, ponieważ zmiana pola oznaczałaby zmianę kodu testu, więc test byłby zupełnie bezcelowy. Zamiast tego powinieneś przetestować związek między wywołaniami metod publicznych, w ten sposób, nawet jeśli zmodyfikujesz pola, nie będziesz musiał modyfikować testu.
źródło
setup()
metodzie - nie w samym teście.approve()
czegoś jest krucha, a jednak zależność odsetApproved(true)
czegoś nie?approve()
jest uzasadnioną zależnością w testach, ponieważ jest zależnością od wymagań. Gdyby zależność istniała tylko w testach, byłby to inny problem.push()
ipop()
metody niezależnie?Jeszcze innym podejściem jest stworzenie konstruktora klasy, który umożliwia ustawienie właściwości wewnętrznych przy tworzeniu wystąpienia:
źródło
Jedna strategia polega na tym, że dziedziczysz klasę (w tym przypadku Dokument) i piszesz testy względem odziedziczonej klasy. Dziedziczona klasa pozwala w pewien sposób ustawić stan obiektu w testach.
W C # jedna strategia może polegać na wprowadzeniu wewnętrznych ustawień, a następnie wystawieniu wewnętrznych elementów do przetestowania projektu.
Możesz także użyć klasowego interfejsu API, jak opisano („Z pewnością mogę to zrobić, wywołując dwukrotnie Publish”). To byłoby ustawienie stanu obiektu za pomocą publicznych usług obiektu, nie wydaje mi się to zbyt śmierdzące. W przypadku twojego przykładu prawdopodobnie tak właśnie zrobiłbym.
źródło
Aby przetestować w absolutnej izolacji polecenia i zapytania otrzymywane przez obiekty domeny, jestem przyzwyczajony do dostarczania każdemu testowi serializacji obiektu w oczekiwanym stanie. W sekcji aranżacji testu ładuje obiekt do przetestowania z pliku, który wcześniej przygotowałem. Na początku zacząłem od serializacji binarnych, ale json okazał się o wiele łatwiejszy w utrzymaniu. Okazało się, że działa dobrze, ilekroć absolutna izolacja w testach zapewnia rzeczywistą wartość.
edytuj tylko notatkę, czasami serializacja JSON kończy się niepowodzeniem (jak w przypadku grafów cyklicznych obiektów, które są zapachem, btw). W takich sytuacjach ratuję się do serializacji binarnej. To trochę pragmatyczne, ale działa. :-)
źródło
Mówisz
i
i muszę pomyśleć, że użycie refleksji do ominięcia kontroli dostępu na twoich zajęciach nie jest tym, co nazwałbym „najlepszą praktyką”. Będzie też strasznie powolne.
Osobiście zeskrobałbym twoją platformę testów jednostkowych i poszedłbym z czymś w klasie - wygląda na to, że piszesz testy z punktu widzenia testowania całej klasy, co jest dobre. W przeszłości, w przypadku niektórych trudnych komponentów, które wymagały testowania, wbudowałem aserts i kod instalacyjny w samą klasę (kiedyś był to powszechny wzorzec projektowy mający metodę test () w każdej klasie), więc tworzysz klienta to po prostu tworzy instancję obiektu i wywołuje metodę testową, która może ustawić się tak, jak chcesz, bez nieprzyjemności, takich jak hack odbicia.
Jeśli martwisz się rozdęciem kodu, po prostu zawiń metody testowe w #ifdefs, aby były dostępne tylko w kodzie debugowania (prawdopodobnie sama najlepsza praktyka)
źródło