Dzisiaj trenowaliśmy TDD i znaleźliśmy następujący punkt nieporozumienia.
Zadanie polega na tym, aby wejściowa „1,2” zwróciła sumę liczb, która wynosi 3. To, co napisałem (w C #) to:
numbers = input.Split(',');
return int.Parse(numbers[0]) + int.Parse(numbers[1]); //task said we have two numbers and input is correct
Ale inni woleli robić to inaczej. Po pierwsze, dla danych wejściowych „1,2” dodali następujący kod:
if (input == "1,2")
return 3;
Następnie wprowadzili jeszcze jeden test dla danych wejściowych „4,5” i zmienili implementację:
if (input == "1,2")
return 3;
else if (input == "4,5")
return 9;
A potem powiedzieli „OK, teraz widzimy wzór” i wdrożyli to, co początkowo zrobiłem.
Myślę, że drugie podejście lepiej pasuje do definicji TDD, ale ... czy powinniśmy być wobec tego tak surowi? Dla mnie można pominąć trywialne kroki dziecka i połączyć je w „dwa kroki”, jeśli jestem pewien, że niczego nie pominę. Czy się mylę?
Aktualizacja. Popełniłem błąd, nie wyjaśniając, że to nie był pierwszy test. Było już kilka testów, więc „return 3” nie było tak naprawdę najprostszym fragmentem kodu spełniającym wymagania.
Odpowiedzi:
Napisz najprostszy kod, który sprawi, że testy przejdą pomyślnie.
O ile wiem, żadne z was tego nie zrobiło.
Krok dziecka 1.
Test: Dla wartości wejściowej „1,2” zwracana jest suma liczb, która wynosi 3
Spraw, by test się nie powiódł:
Zdaj test:
Krok dziecka 2.
Test: Dla wartości wejściowej „1,2” zwracana jest suma liczb, która wynosi 3
Test: Dla wartości wejściowej „4,5” zwracana jest suma liczb, która wynosi 9
Drugi test kończy się niepowodzeniem, więc zalicz go:
(Znacznie prostsze niż lista if ... return)
W tym przypadku możesz oczywiście argumentować Oczywiste wdrożenie, ale jeśli mówiłeś o robieniu tego ściśle krok po kroku, to są to właściwe kroki, IMO.
Argument polega na tym, że jeśli nie napiszesz drugiego testu, wtedy może pojawić się jakaś jasna iskra i „refaktoryzować” kod do czytania:
I bez wykonania obu kroków nigdy nie sprawiłeś, aby drugi test stał się czerwony (co oznacza, że sam test jest podejrzany).
źródło
input.Length
nie jest aż tak daleko pobierany, szczególnie jeśli dane wejściowe do metody są gdzieś pomiarem z jakiegoś pliku i przypadkowo wywołałeś metodęSize()
.Myślę, że drugim sposobem jest zdumiewająco głupi umysł. Widzę wartość robienia wystarczająco małych kroków, ale pisanie tych maleńkich kroków zygoty (nawet nie mogę nazwać ich dziećmi) jest po prostu marne i strata czasu. Zwłaszcza jeśli pierwotny problem, który rozwiązujesz, jest już bardzo mały.
Wiem, że to trening i bardziej chodzi o pokazanie zasady, ale myślę, że takie przykłady robią TDD bardziej źle niż dobrze. Jeśli chcesz pokazać wartość kroków dziecka, przynajmniej użyj problemu, w którym jest jakaś wartość.
źródło
Kent Beck opisuje to w swojej książce Test Driven Development: By Example.
Twój przykład wskazuje na „ oczywistą implementację ” - chcesz zwrócić sumę dwóch wartości wejściowych, i jest to dość prosty algorytm do osiągnięcia. Twój kontrprzykład popada w „sfałszowanie go, dopóki go nie stworzysz” (choć jest to bardzo prosty przypadek).
Oczywista implementacja może być znacznie bardziej skomplikowana - ale w zasadzie uruchamia się, gdy specyfikacja metody jest dość ścisła - na przykład zwraca wersję właściwości klasy zakodowaną w adresie URL - nie musisz tracić czasu na kilka sfałszowane kodowania.
Z drugiej strony procedura połączenia z bazą danych wymagałaby nieco więcej przemyślenia i przetestowania, więc nie ma oczywistej implementacji (nawet jeśli mógłbyś napisać ją kilka razy w innych projektach).
Z książki:
źródło
Widzę to jako zgodne z literą prawa, ale nie jego duchem.
Twoje kroki dziecka powinny być:
Również czasownik w metodzie to
sum
nie jest sumą, to test na określone dane wejściowe.
źródło
Wydaje mi się, że łączenie kilku trywialnych kroków implementacyjnych w jeden jest nieco mniej trywialny - robię to cały czas. Nie wydaje mi się, żeby trzeba było religijnie podążać za TDD za każdym razem do listu.
OTOH dotyczy to tylko naprawdę trywialnych kroków, takich jak powyższy przykład. Jeśli chodzi o coś bardziej złożonego, czego nie mogę w pełni od razu zapamiętać i / lub nie jestem w 110% pewien co do wyniku, wolę iść krok po kroku.
źródło
Gdy po raz pierwszy wybierasz drogę TDD, rozmiar kroków może być mylącym zagadnieniem, jak to pytanie ilustruje. Często zadawałem sobie pytania, kiedy zaczynałem pisać aplikacje testowe; Czy test, który piszę, pomaga w rozwoju moich aplikacji? Może to wydawać się trywialne i niezwiązane z niektórymi, ale trzymaj się ze mną przez chwilę.
Teraz, gdy postanawiam napisać dowolną aplikację, zwykle zaczynam od testu. Jak duży krok tego testu w dużej mierze dotyczy mojego zrozumienia tego, co próbuję zrobić. Jeśli myślę, że mam w głowie zachowanie klasy, to krok będzie duży. Jeśli problem, który próbuję rozwiązać, jest o wiele mniej wyraźny, krokiem może być po prostu to, że wiem, że istnieje metoda o nazwie X i że zwróci Y. W tym momencie metoda nie będzie miała nawet żadnych parametrów i istnieje szansa, że zmieni się nazwa metody i typ zwrotu. W obu przypadkach testy napędzają mój rozwój. Opowiadają mi coś o mojej aplikacji:
Czy ta klasa, którą mam w głowie, rzeczywiście idzie do pracy?
lub
Jak do diabła mam zamiar to zrobić?
Chodzi o to, że w mgnieniu oka mogę przełączać się między dużymi i małymi krokami. Na przykład, jeśli duży krok nie działa i nie widzę oczywistego sposobu obejścia go, przejdę na mniejszy stopień. Jeśli to nie zadziała, przejdę na jeszcze mniejszy krok. Są też inne techniki, takie jak triangulacja, jeśli naprawdę utknę.
Jeśli tak jak ja jesteś programistą, a nie testerem, wtedy celem TDD nie jest pisanie testów, ale pisanie kodu. Nie przejmuj się pisaniem wielu drobnych testów, jeśli nie dają ci żadnych korzyści.
Mam nadzieję, że podobał Ci się trening z TDD. IMHO, gdyby więcej osób zostało zainfekowanych testowo, świat byłby lepszym miejscem :)
źródło
W części dotyczącej testów jednostkowych przeczytałem to samo podejście (kroki, które wyglądają naprawdę, naprawdę małe) i jako odpowiedź na pytanie „jak powinny być małe” coś, co mi się podobało, co (parafrazowano) w ten sposób:
Chodzi o to, jak pewny jesteś, że kroki działają. Możesz zrobić naprawdę duże kroki, jeśli chcesz. Ale po prostu spróbuj przez jakiś czas, a zobaczysz wiele mylnej pewności w miejscach, które uważasz za oczywiste. Testy pomagają więc zbudować zaufanie oparte na faktach.
Może twoja koleżanka jest trochę nieśmiała :)
źródło
Czy nie chodzi o to, że wdrożenie metody nie ma znaczenia, dopóki testy się powiodą? Przedłużenie testów zakończy się niepowodzeniem w drugim przykładzie, ale w obu przypadkach może się nie powieść.
źródło
Zgadzam się z ludźmi, którzy twierdzą, że żadna z nich nie jest najprostsza.
Metodologia jest tak rygorystyczna, że zobowiązuje cię do napisania jak największej liczby odpowiednich testów. Zwracanie stałej wartości dla jednego przypadku testowego i nazywanie go pass jest w porządku, ponieważ zmusza cię do powrotu i określenia tego, czego naprawdę chcesz, aby uzyskać coś innego niż bzdury z twojego programu. Korzystanie z tak trywialnego przypadku jest pod pewnymi względami strzelaniem w stopę, ale zasada jest taka, że błędy wkradają się w luki w specyfikacji, gdy próbujesz zrobić „zbyt wiele”, a obniżenie wymagań do najprostszej możliwej implementacji zapewnia, że test musi być napisany dla każdego unikalnego aspektu zachowania, którego naprawdę chcesz.
źródło