Jak poprawnie uzyskać początkowy interfejs API za pomocą TDD?

12

To może być dość głupie pytanie, ponieważ jestem przy pierwszych próbach TDD. Uwielbiałem poczucie pewności i ogólnie lepszą strukturę mojego kodu, ale kiedy zacząłem stosować go na czymś większym niż przykłady klasowych zabawek, napotkałem trudności.

Załóżmy, że piszesz bibliotekę. Wiesz, co musi zrobić, znasz ogólny sposób jego implementacji (pod względem architektury), ale ciągle „odkrywasz”, że musisz wprowadzać zmiany w publicznym interfejsie API podczas pisania kodu. Być może musisz przekształcić tę prywatną metodę w wzorzec strategii (a teraz musisz zdać kpiącą strategię w swoich testach), być może źle dopełniłeś odpowiedzialności tu i tam i podzieliłeś istniejącą klasę.

Kiedy poprawiasz istniejący kod, TDD wydaje się być naprawdę dobrym dopasowaniem, ale kiedy piszesz wszystko od zera, API, dla którego piszesz testy, jest nieco „rozmyte”, chyba że zrobisz duży projekt z góry. Co robisz, gdy masz już 30 testów metody, której zmieniono podpis (i dla tej części, zachowanie)? To wiele testów, które należy zmienić po dodaniu.

Vytautas Mackonis
źródło
3
30 testów na jednej metodzie? Wygląda na to, że ta metoda ma zbyt dużą złożoność lub piszesz zbyt wiele testów.
Minthos,
Cóż, mogłem trochę przesadzić, aby wyrazić swój punkt widzenia. Po sprawdzeniu kodu generalnie mam mniej niż 10 metod na test, przy czym większość z nich jest poniżej 5. Ale cała część „cofania się i zmieniania rąk” była dość frustrująca.
Vytautas Mackonis,
6
@Minthos: Mogę wymyślić 6 testów od góry, że jakakolwiek metoda polegająca na łańcuchu często kończy się niepowodzeniem lub źle się sprawdza przy zapisie pierwszego szkicu (zero, pusta, za długa, niewłaściwie zlokalizowana, słabe skalowanie perf) . Podobnie w przypadku metod pobierania kolekcji. W przypadku nietrywialnej metody 30 brzmi duże, ale nie zbyt nierealne.
Steven Evers,

Odpowiedzi:

13

To, co nazywacie „dużym projektem z góry” , nazywam „rozsądnym planowaniem architektury klasowej”.

Nie można wyhodować architektury na podstawie testów jednostkowych. Nawet wujek Bob tak mówi.

Jeśli nie zastanawiasz się nad architekturą, jeśli zamiast tego ignorujesz architekturę i zlewasz testy razem i je przechodzisz, niszczysz coś, co pozwoli budynkowi pozostać w górze, ponieważ jest to koncentracja na struktura systemu i solidne decyzje projektowe, które pomogły systemowi zachować integralność strukturalną.

http://s3.amazonaws.com/hanselminutes/hanselminutes_0171.pdf , strona 4

Myślę, że bardziej rozsądne byłoby podejście do TDD z perspektywy walidacji projektu konstrukcyjnego. Skąd wiesz, że projekt jest niepoprawny, jeśli go nie przetestujesz? Jak zweryfikować poprawność wprowadzonych zmian bez zmiany oryginalnych testów?

Oprogramowanie jest „miękkie” właśnie dlatego, że może ulec zmianie. Jeśli nie czujesz się komfortowo w związku z ilością zmian, zdobywaj doświadczenie w projektowaniu architektury, a liczba zmian, które będziesz musiał wprowadzić w architekturze aplikacji, z czasem się zmniejszy.

Robert Harvey
źródło
Chodzi o to, że nawet przy „rozsądnym planowaniu” oczekuje się, że wiele się zmieni. Zwykle pozostawiam około 80% mojej pierwotnej architektury bez zmian z pewnymi zmianami pomiędzy nimi. Te 20% mnie niepokoi.
Vytautas Mackonis,
2
Myślę, że taka jest natura tworzenia oprogramowania. Nie można oczekiwać, że cała architektura będzie poprawna za pierwszym razem.
Robert Harvey,
2
+1, i to nie jest sprzeczne z TDD. TDD zaczyna się, gdy zaczynasz pisać kod, dokładnie po zakończeniu projektowania. TDD może pomóc Ci zobaczyć, co przegapiłeś w swoim projekcie, pozwalając ci przefakturować projekt i wdrożenie i kontynuować.
Steven Evers,
2
W rzeczywistości, według Boba (i zgadzam się z nim całym sercem) pisanie kodu to również projektowanie. Posiadanie architektury wysokiego poziomu jest zdecydowanie konieczne, ale projektowanie nie kończy się podczas pisania kodu.
Michael Brown,
Naprawdę dobra odpowiedź, która uderza w gwóźdź na głowie. Widzę tak wielu ludzi, zarówno za, jak i przeciw TDD, którzy wydają się przyjmować „bez wielkiego projektu z góry”, ponieważ „w ogóle nie projektują, po prostu kodują”, gdy jest to rzeczywiście uderzenie w szalone stadia projektowania starych wodospadów. Projektowanie jest zawsze dobrą inwestycją czasową i ma kluczowe znaczenie dla powodzenia każdego nietrywialnego projektu.
sara,
3

Jeśli zrobisz TDD. Nie można zmienić podpisu i zachowania bez przeprowadzenia go przez testy. Tak więc 30 testów, które zakończyły się niepowodzeniem, albo zostało usuniętych w trakcie procesu, albo zostało zmienionych / refaktoryzowanych wraz z kodem. Lub są już przestarzałe, bezpieczne do usunięcia.

Nie możesz zignorować 30-krotnego czerwonego w cyklu refaktora czerwono-zielonego?

Twoje testy powinny być refaktoryzowane wraz z kodem produkcyjnym. Jeśli możesz sobie na to pozwolić, ponownie uruchom wszystkie testy po każdej zmianie.

Nie bój się usunąć testów TDD. Niektóre testy kończą testowanie bloków konstrukcyjnych, aby uzyskać pożądany wynik. Liczy się pożądany wynik na poziomie funkcjonalnym. Testy dotyczące pośrednich kroków w algorytmie, który wybrałeś / wymyśliłeś, mogą, ale nie muszą mieć dużej wartości, gdy istnieje więcej niż jeden sposób na osiągnięcie wyniku lub początkowo wpadłeś w ślepy zaułek.

Czasami możesz stworzyć przyzwoite testy integracyjne, zatrzymać je i usunąć resztę. To w pewnym stopniu zależy od tego, czy pracujesz na lewą stronę, czy od góry i od tego, jak duże kroki wykonujesz.

Joppe
źródło
1

Jak powiedział właśnie Robert Harvey, prawdopodobnie próbujesz użyć TDD do czegoś, co powinno być obsługiwane przez inne narzędzie koncepcyjne (to znaczy: „projektowanie” lub „modelowanie”).

Spróbuj zaprojektować (lub „model”) swój system w sposób dość abstrakcyjny („ogólny”, „niejasny”). Na przykład, jeśli trzeba modelować samochód, wystarczy mieć klasę samochodu z niejasną metodą i polem, na przykład startEngine () i int. To znaczy: opisz, co chcesz ujawnić opinii publicznej , a nie jak chcesz to wdrożyć. Spróbuj udostępnić tylko podstawowe funkcje (odczytywanie, pisanie, uruchamianie, zatrzymywanie itp.) I pozostaw na nim rozbudowany kod klienta (preparMyScene (), killTheEnemy () itp.).

Zapisz swoje testy przy założeniu tego prostego publicznego interfejsu.

Zmień wewnętrzne zachowanie swoich klas i metod, kiedy tylko będziesz tego potrzebować.

Jeśli i kiedy musisz zmienić interfejs publiczny i zestaw testów, zatrzymaj się i pomyśl. Najprawdopodobniej jest to znak, że coś jest nie tak w interfejsie API i projekcie / modelowaniu.

Zmiana API nie jest niczym niezwykłym. Większość systemów w wersji 1.0 wyraźnie ostrzega programistów / użytkowników przed możliwymi zmianami w interfejsie API. Mimo to ciągły, niekontrolowany przepływ zmian API jest wyraźną oznaką złego (lub kompletnego braku) projektu.

BTW: Zwykle powinieneś po prostu mieć garść testów dla każdej metody. Metoda z definicji powinna implementować jasno określone „działanie” w odniesieniu do pewnego rodzaju danych. W idealnym świecie powinno to być pojedyncze działanie, które odpowiada pojedynczemu testowi. W prawdziwym świecie nie jest niczym niezwykłym (i nie jest źle) mieć kilka różnych „wersji” tej samej akcji i kilka różnych odpowiednich testów. Na pewno powinieneś unikać 30 testów tej samej metody. Jest to wyraźny znak, że metoda próbuje zrobić zbyt wiele (a jej wewnętrzny kod wymknął się spod kontroli).

AlexBottoni
źródło
0

Patrzę na to z perspektywy użytkownika. Na przykład, jeśli twoje interfejsy API pozwalają mi utworzyć obiekt Person o nazwie i wieku, lepiej byłoby mieć konstruktor Person i metody akcesora dla imienia i wieku. Tworzenie przypadków testowych dla nowych Osób z imieniem i wiekiem jest proste.

doug

SnoopDougieDoug
źródło