Co robisz, gdy piszesz test i dochodzisz do punktu, w którym musisz zdać test, i zdajesz sobie sprawę, że potrzebujesz dodatkowej funkcjonalności, która powinna być podzielona na własną funkcję? Ta nowa funkcja również musi zostać przetestowana, ale cykl TDD mówi: „Nie udać się testem, sprawić, by przeszedł, a następnie refaktoryzować”. Jeśli jestem na etapie, w którym próbuję zdać test, nie powinienem zaczynać i uruchamiać kolejnego testu zakończonego niepowodzeniem w celu przetestowania nowej funkcjonalności, którą muszę zaimplementować.
Na przykład piszę klasę punktową, która ma funkcję WillCollideWith ( LineSegment ) :
public class Point {
// Point data and constructor ...
public bool CollidesWithLine(LineSegment lineSegment) {
Vector PointEndOfMovement = new Vector(Position.X + Velocity.X,
Position.Y + Velocity.Y);
LineSegment pointPath = new LineSegment(Position, PointEndOfMovement);
if (lineSegment.Intersects(pointPath)) return true;
return false;
}
}
Pisałem test dla CollidesWithLine, kiedy zdałem sobie sprawę, że potrzebuję funkcji LineSegment.Intersects ( LineSegment ) . Ale czy powinienem po prostu przerwać to, co robię w cyklu testowym, aby stworzyć nową funkcjonalność? To wydaje się łamać zasadę „czerwony, zielony, refaktor”.
Czy powinienem po prostu napisać kod, który wykrywa, że lineSegments przecinają się wewnątrz funkcji CollidesWithLine i refaktoryzują go po jego zadziałaniu ? To by działało w tym przypadku, ponieważ mogę uzyskać dostęp do danych z LineSegment , ale co z przypadkami, w których tego rodzaju dane są prywatne?
źródło
W cyklu TDD:
W fazie „wykonaj test pozytywny” powinieneś napisać najprostszą implementację, która spowoduje pozytywny wynik testu . Aby pomyślnie przejść test, postanowiłeś utworzyć nowego współpracownika, który poradziłby sobie z brakującą logiką, ponieważ włożenie klasy punktowej do wykonania testu może być zbyt dużym nakładem pracy. Na tym polega problem. Podejrzewam, że test, który chcesz zaliczyć, był zbyt dużym krokiem . Myślę więc, że problem leży w samym teście, powinieneś usunąć / skomentować ten test i wymyślić prostszy test, który pozwoli ci zrobić mały krok bez wprowadzania LineSegment.Intersects (LineSegment). Po przejściu tego testu możesz następnie refaktoryzowaćtwój kod (tutaj zastosujesz zasadę SRP), przenosząc tę nową logikę do metody LineSegment.Intersects (LineSegment). Twoje testy nadal będą zaliczane, ponieważ nie zmieniłeś żadnego zachowania, a jedynie przeniosłeś trochę kodu.
Na twoim obecnym rozwiązaniu projektowym
Ale dla mnie masz głębszy problem projektowy polegający na tym, że naruszasz zasadę pojedynczej odpowiedzialności . Rolą Punktu jest… być punktem, to wszystko. Bycie punktem nie ma mądrości, jest to wartość sprawiedliwa i xiy. Punkty są typami wartości . To samo dotyczy segmentów, segmenty są typami wartości złożonymi z dwóch punktów. Mogą zawierać odrobinę „inteligencji”, na przykład, aby obliczyć ich długość na podstawie ich pozycji punktowej. Ale to jest to.
Teraz decyzja, czy punkt i segment kolidują, jest sama w sobie odpowiedzialnością. Z pewnością jest to zbyt wiele pracy, aby sam punkt lub segment mógł sobie z tym poradzić. Nie może należeć do klasy Point, ponieważ w przeciwnym razie Punkty będą wiedzieć o segmentach. I nie może należeć do segmentów, ponieważ segmenty już są odpowiedzialne za dbanie o punkty w segmencie, a może także obliczanie długości samego segmentu.
Tak więc odpowiedzialność powinna spoczywać na innej klasie, takiej jak na przykład „PointSegmentCollisionDetector”, która miałaby metodę taką jak:
bool AreInCollision (punkt p, segmenty)
I to jest coś, co przetestowałbyś osobno w punktach i segmentach.
Zaletą tego projektu jest to, że teraz możesz mieć inną implementację detektora kolizji. Łatwo byłoby na przykład przetestować silnik gry (zakładam, że piszesz grę: p), zmieniając metodę wykrywania kolizji w czasie wykonywania. Lub wykonać wizualne sprawdzanie / eksperymenty w czasie wykonywania między różnymi strategiami wykrywania kolizji.
W tej chwili, umieszczając tę logikę w swojej klasie punktowej, blokujesz rzeczy i przesuwasz zbyt dużą odpowiedzialność na klasę punktową.
Mam nadzieję, że to ma sens
źródło
Najłatwiejszą rzeczą do zrobienia w stylu TDD byłoby wyodrębnienie interfejsu dla LineSegment i zmiana parametru metody w celu uwzględnienia interfejsu. Następnie możesz wyśmiewać segment linii wejściowej i niezależnie kodować / testować metodę przecinania.
źródło
Dzięki jUnit4 możesz użyć
@Ignore
adnotacji do testów, które chcesz odłożyć.Dodaj adnotację do każdej metody, którą chcesz odłożyć, i kontynuuj pisanie testów pod kątem wymaganej funkcjonalności. Odwróć z powrotem, aby później przekształcić starsze przypadki testowe.
źródło