Jak twoje dziecko ma kroki w TDD?

37

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.

SiberianGuy
źródło
25
Tak mały, że moi współpracownicy tryskają „Ooahhh dazso cuuuuuute”
Adel
6
@Adel: Prawie dusiłem się przy śniadaniu, klawiatura jest teraz pełna lub ślina i okruchy
Binary Worrier
2
@Adel, podobnie jak w przypadku osób, które nie są rodzimymi użytkownikami języka, raczej trudno jest mi zrozumieć ten humor, ale myślę, że Twoi współpracownicy lubią pytanie :)
SiberianGuy
8
@Isa: transponuje odpowiedź współpracowników, gdy pokazano jej pierwsze kroki dziecka „Ooahhh dazso cuuuuuute” = „Och, to takie słodkie” (wypowiedziane śpiewem, który nie jest tak bardzo słodki), wraz z ich odpowiedzią widząc Testy Jednostkowe napisane przez Adela, patrząc na kroki dziecka w Testach Jednostkowych, mówią „Och, to takie słodkie”. Reakcja na „prawdziwe” kroki dziecka = reakcja na testy jednostkowe „kroki dziecka”.
Binary Worrier
3
@Binaryworrier chciałbym dać ci prawdziwe punkty za poświęcenie czasu na wyjaśnienie
rodzicielstwa

Odpowiedzi:

31

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ł:

throw NotImplementedException();

Zdaj test:

return 3;

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:

numbers = input.Split(',');
return int.Parse(numbers[0]) + int.Parse(numbers[1]);

(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:

return input.Length; # Still satisfies the first test

I bez wykonania obu kroków nigdy nie sprawiłeś, aby drugi test stał się czerwony (co oznacza, że ​​sam test jest podejrzany).

pdr
źródło
Jeśli chodzi o swoim przykładzie input.Length, z takim samym powodzeniem można sobie wyobrazić jakąś szaloną błędnej realizacji, który nie zostanie złapany przez obu testów
SiberianGuy
@Idsa - Tak, absolutnie, a im więcej testów piszesz, tym bardziej szalone musi być wdrożenie. input.Lengthnie 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().
pdr
6
+1. Jeśli chodzi o naukę TDD, jest to właściwy sposób. Gdy się go nauczysz, czasami możesz przejść bezpośrednio do oczywistej implementacji, ale aby poczuć TDD, jest to o wiele lepsze.
Carl Manaster,
1
Mam pytanie dotyczące samego „testu”. Czy napiszesz nowy test dla danych wejściowych „4,5”, czy zmodyfikujesz oryginalny test?
mxmissile
1
@mxmissile: Napiszę nowy test. Nie zajmuje to dużo czasu i kończysz na dwa razy więcej testach, aby zabezpieczyć się, gdy później dokonasz refaktoryzacji.
pdr
50

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ść.

Christophe Vanfleteren
źródło
+1 i dziękuję za kazanie mi spojrzeć w górę i nauczyć się nowego słowa (asinine)
Marjan Venema
12
+1 za nazwanie go paraliżująco głupim. TDD jest całkiem fajny i taki, ale jak w każdej nowoczesnej technice programowania hipedowego, powinieneś uważać, aby się w nim nie zagubić.
stijn
2
„Zwłaszcza jeśli pierwotny problem, który rozwiązujesz, jest już bardzo mały”. - Gdyby dane wejściowe zawierały dwie liczby całkowite do dodania razem, zgodziłbym się z tym, ale nie jestem przekonany, gdy „dzieli ciąg, analizuje dwie liczby całkowite z wyniku i dodaje je”. Większość metod w prawdziwym świecie nie jest dużo bardziej skomplikowana. W rzeczywistości powinno być więcej testów, aby objąć przypadki skrajne, takie jak znalezienie dwóch przecinków, wartości niecałkowite itp.
pdr
4
@pdr: Zgadzam się z tobą, że powinno być więcej testów do obsługi przypadków krawędzi. Kiedy je napiszesz i zauważysz, że twoja implementacja musi się zmienić, aby je obsłużyć, zrób to wszystko. Wydaje mi się, że mam problem z podjęciem kroków zygoty do pierwszej „oczywistej implementacji” szczęśliwej ścieżki, zamiast po prostu zapisać to i zacząć od tego momentu. Nie widzę wartości w wypisywaniu stwierdzenia „if”, które wie, że każde włókno w moim ciele zniknie w następnej chwili.
Christophe Vanfleteren
1
@ChristopheVanfleteren: Kiedy Beck opisuje oczywistą implementację, używa sumy dwóch liczb int jako przykładu i wciąż rzuca ogromne dramatyczne ostrzeżenie o tym, jak umrzesz ze wstydu, jeśli twoja para / recenzent może pomyśleć o prostszym kodzie, który sprawia, że zaliczenie testu. Jest to absolutna pewność, jeśli napiszesz tylko jeden test dla tego scenariusza. Mogę też wymyślić co najmniej trzy „oczywiste” sposoby rozwiązania tego problemu: podziel i dodaj, zamień przecinek na + i oceń lub używając wyrażenia regularnego. Celem TDD jest doprowadzenie cię do właściwego wyboru.
pdr
19

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:

Kiedy używam TDD w praktyce, często przełączam się między tymi dwoma trybami implementacji. Kiedy wszystko idzie gładko i wiem, co wpisać, włączam Obvious Implementation po Obvious Implementation (uruchamiam testy za każdym razem, aby upewnić się, że to, co dla mnie oczywiste) jest nadal oczywiste dla komputera). Gdy tylko pojawia się nieoczekiwany czerwony pasek, cofam się, przechodzę do fałszywych implementacji i zmieniam kod na właściwy. Kiedy wraca moja pewność siebie, wracam do Obvious Implementations.

HorusKol
źródło
18

Widzę to jako zgodne z literą prawa, ale nie jego duchem.

Twoje kroki dziecka powinny być:

Tak proste, jak to możliwe, ale nie prostsze.

Również czasownik w metodzie to sum

if (input == "1,2")
   return 3;

nie jest sumą, to test na określone dane wejściowe.

StuperUser
źródło
4

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.

Péter Török
źródło
1

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 :)

Lexx
źródło
1

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 :)

keppla
źródło
1

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ść.

Grzbiet
źródło
1
Nie ma znaczenia, jeśli całkowicie nie obchodzi Cię marnowanie czasu
SiberianGuy
1

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.

Tom W.
źródło
Dodałem aktualizację dotyczącą „zwracania stałej wartości”
SiberianGuy