Jaki jest najlepszy sposób działania w TDD, jeśli po prawidłowym zaimplementowaniu logiki test nadal się nie powiedzie (ponieważ w teście jest błąd)?
Załóżmy na przykład, że chcesz rozwinąć następującą funkcję:
int add(int a, int b) {
return a + b;
}
Załóżmy, że rozwijamy go w następujących krokach:
Test zapisu (jeszcze brak funkcji):
// test1 Assert.assertEquals(5, add(2, 3));
Powoduje błąd kompilacji.
Napisz implementację funkcji fikcyjnej:
int add(int a, int b) { return 5; }
Wynik:
test1
zalicza się.Dodaj kolejny przypadek testowy:
// test2 -- notice the wrong expected value (should be 11)! Assert.assertEquals(12, add(5, 6));
Wynik:
test2
zawodzi,test1
nadal mija.Napisz prawdziwą implementację:
int add(int a, int b) { return a + b; }
Wynik:
test1
nadal mija,test2
nadal nie działa (od11 != 12
).
W tym konkretnym przypadku: czy lepiej byłoby:
- popraw
test2
i zobacz, że teraz mija, lub - usuń nową część implementacji (tj. wróć do kroku 2 powyżej), popraw
test2
i pozwól, aby zakończyła się niepowodzeniem, a następnie ponownie wprowadź poprawną implementację (krok # 4 powyżej).
A może jest jakiś inny, mądrzejszy sposób?
Rozumiem, że przykładowy problem jest dość trywialny, ale interesuje mnie, co robić w ogólnym przypadku, który może być bardziej złożony niż dodanie dwóch liczb.
EDYCJA (W odpowiedzi na odpowiedź @Thomas Junk):
W centrum tego pytania jest to, co sugeruje TDD w takim przypadku, a nie „uniwersalna najlepsza praktyka” do uzyskania dobrego kodu lub testów (które mogą być inne niż sposób TDD).
Odpowiedzi:
Absolutnie krytyczną rzeczą jest to, że widzisz test zarówno pozytywny jak i negatywny.
Nieważne, czy usuniesz kod, aby test się nie powiódł, a następnie przepisz kod lub przekradnij go do schowka, aby wkleić go później. TDD nigdy nie powiedział, że musisz coś przepisać. Chce wiedzieć, że test zalicza się tylko wtedy, gdy powinien przejść, a test kończy się niepowodzeniem tylko wtedy, gdy się nie powiedzie.
Widzenie testu, zarówno pozytywnego, jak i negatywnego, jest sposobem na przetestowanie testu. Nigdy nie ufaj testowi, którego nigdy nie widziałeś.
Refaktoryzacja w stosunku do czerwonego paska daje nam formalne kroki w celu refaktoryzacji testu roboczego:
Jednak nie refaktoryzujemy testu roboczego. Musimy przekształcić test na błędy. Jednym z problemów jest kod, który został wprowadzony, a obejmował go tylko ten test. Taki kod powinien zostać wycofany i ponownie wprowadzony po naprawieniu testu.
Jeśli tak nie jest, a pokrycie kodu nie stanowi problemu z powodu innych testów obejmujących kod, możesz przekształcić test i wprowadzić go jako test zielony.
W tym przypadku kod jest również wycofywany, ale na tyle, aby spowodować niepowodzenie testu. Jeśli to nie wystarczy, aby pokryć cały wprowadzony kod, podczas gdy jest on objęty tylko błędnym testem, potrzebujemy większego wycofania kodu i kolejnych testów.
Wprowadź zielony test
Złamanie kodu może oznaczać komentarz lub przeniesienie go w inne miejsce, aby później wkleić go z powrotem. To pokazuje nam zakres kodu, który obejmuje test.
W przypadku tych dwóch ostatnich serii wracasz do normalnego cyklu czerwonego zielonego. Po prostu wklejasz zamiast pisać, aby odblokować kod i przejść test. Upewnij się więc, że wklejasz tylko tyle, aby test zdał pomyślnie.
Ogólny wzór polega na tym, że kolor testu zmienia się w oczekiwany sposób. Pamiętaj, że powoduje to sytuację, w której masz krótkotrwały niezaufany test ekologiczny. Uważaj, aby nie przeszkadzać ci i zapominać, gdzie jesteś na tych etapach.
Moje podziękowania dla RubberDuck za link Embracing the Red Bar .
źródło
Jaki jest ogólny cel , który chcesz osiągnąć?
Robisz niezłe testy?
Dokonywania prawidłowej realizacji?
Czy TTD ma rację religijną ?
Żadne z powyższych?
Być może przerastasz swój związek z testami i testowaniem.
Testy nie gwarantują poprawności wdrożenia. Po zaliczeniu wszystkich testów nic nie mówi o tym, czy oprogramowanie robi to, co powinno; nie czyni esencjalizmu wypowiedzi na temat twojego oprogramowania.
Biorąc twój przykład:
„Prawidłowa” implementacja tego dodatku byłaby kodem równoważnym do
a+b
. I dopóki twój kod to robi , powiedziałbyś, że algorytm jest poprawny w tym, co robi i jest poprawnie zaimplementowany.Na pierwszy rzut oka oboje zgodzilibyśmy się, że jest to wdrożenie dodatku.
Ale tak naprawdę nie mówimy, że ten kod jest jego implementacją
addition
, zachowuje się tylko w pewnym stopniu : pomyśl o przepełnieniu liczb całkowitych .Przepełnienie liczb całkowitych ma miejsce w kodzie, ale nie w koncepcji
addition
. Tak więc: Twój kod zachowuje się w pewnym stopniu jak koncepcjaaddition
, ale tak nie jestaddition
.Ten raczej filozoficzny punkt widzenia ma kilka konsekwencji.
Jednym z nich jest to, że można powiedzieć, że testy są jedynie założeniami oczekiwanego zachowania twojego kodu. Testując swój kod, możesz (być może) nigdy nie upewnić się, że Twoja implementacja jest poprawna , najlepiej powiedzieć, że twoje oczekiwania dotyczące wyników dostarczonych przez kod były lub nie zostały spełnione; niech tak będzie, że twój kod jest zły, niech tak będzie, że twój test jest zły, albo niech będzie, że oba są w błędzie.
Przydatne testy pomagają ustalić Twoje oczekiwania co do tego, co powinien zrobić kod: tak długo, jak nie zmieniam swoich oczekiwań i dopóki zmodyfikowany kod daje mi oczekiwany wynik, mogę być pewien, że założenia, które podjąłem wyniki wydają się działać.
To nie pomaga, gdy dokonałeś błędnych założeń; ale hej! przynajmniej zapobiega schizofrenii: oczekiwanie różnych rezultatów, gdy nie powinno ich być.
tl; dr
Twoje testy są założeniami dotyczącymi zachowania kodu. Jeśli masz dobry powód, by sądzić, że Twoja implementacja jest poprawna, napraw test i sprawdź, czy to założenie się spełni.
źródło
datatype
jest to zdecydowanie zły wybór. Test wykazałby, że: Twoje oczekiwania byłyby „skuteczne dla dużych liczb” i w niektórych przypadkach nie zostały spełnione. Wtedy pytanie brzmiałoby, jak sobie poradzić z tymi sprawami. Czy to są narożne skrzynki? Kiedy tak, jak sobie z nimi poradzić? Być może niektóre klauzule quard pomagają zapobiec większemu bałaganowi. Odpowiedź jest związana z kontekstem.Musisz wiedzieć, że test się nie powiedzie, jeśli implementacja jest niepoprawna, co nie jest równoznaczne z zaliczeniem, jeśli implementacja jest poprawna. Dlatego powinieneś przywrócić kod do stanu, w którym spodziewasz się, że zawiedzie przed poprawieniem testu, i upewnij się, że zawodzi z powodu, którego się spodziewałeś (tj.
5 != 12
), Zamiast czegoś innego, czego nie przewidziałeś.źródło
assertTrue(5 == add(2, 3))
daje mniej użyteczne dane wyjściowe niżassertEqual(5, add(2, 3))
pomimo tego, że oba testują to samo).W tym konkretnym przypadku, jeśli zmienisz 12 na 11, a test się teraz powiedzie, myślę, że dobrze wykonałeś testowanie testu, a także jego implementacji, więc nie ma potrzeby przechodzenia przez dodatkowe obręcze.
Ten sam problem może jednak pojawić się w bardziej złożonych sytuacjach, na przykład w przypadku błędu w kodzie instalacyjnym. W takim przypadku po naprawieniu testu prawdopodobnie powinieneś spróbować zmutować implementację w taki sposób, aby ten konkretny test się nie powiódł, a następnie cofnąć mutację. Jeśli cofnięcie implementacji jest najłatwiejszym sposobem, to jest w porządku. W twoim przykładzie możesz mutować
a + b
doa + a
luba * b
.Alternatywnie, jeśli możesz nieco mutować twierdzenie i zobaczyć, że test się nie powiedzie, może to być dość skuteczne w testowaniu testu.
źródło
Powiedziałbym, że jest to przypadek twojego ulubionego systemu kontroli wersji:
Dokonaj korekty testu, utrzymując zmiany kodu w katalogu roboczym.
Zatwierdź z odpowiednią wiadomością
Fixed test ... to expect correct output
.W przypadku
git
może to wymagać użycia opcji,git add -p
jeśli test i implementacja znajdują się w tym samym pliku, w przeciwnym razie można oczywiście po prostu rozdzielić oba pliki osobno.Zatwierdź kod implementacyjny.
Cofnij się w czasie, aby przetestować zatwierdzenie dokonane w kroku 1, upewniając się, że test faktycznie się nie powiedzie .
Widzisz, w ten sposób nie polegasz na swojej umiejętności edytowania, aby przenieść kod implementacji z drogi podczas testowania testu zakończonego niepowodzeniem. Zatrudniasz swój VCS, aby zapisać swoją pracę i upewnić się, że zapisana historia VCS poprawnie obejmuje zarówno test negatywny, jak i pozytywny.
źródło