Dobrze przetestowana baza kodowa ma wiele zalet, ale testowanie niektórych aspektów systemu daje bazę kodową odporną na niektóre rodzaje zmian.
Przykładem jest testowanie określonych wyników - np. Tekstu lub HTML. Testy są często (naiwnie?) Pisane, aby oczekiwać określonego bloku tekstu jako danych wyjściowych dla niektórych parametrów wejściowych lub aby wyszukać określone sekcje w bloku.
Zmiana zachowania kodu, aby spełnić nowe wymagania lub ponieważ testowanie użyteczności spowodowało zmianę interfejsu, również wymaga zmiany testów - być może nawet testów, które nie są konkretnymi testami jednostkowymi zmienianego kodu.
Jak zarządzasz pracą polegającą na wyszukiwaniu i przepisywaniu tych testów? Co jeśli nie możesz po prostu „uruchomić ich wszystkich i pozwolić, aby środowisko je uporządkowało”?
Jakie inne rodzaje testowanego kodu powodują zwykle kruche testy?
źródło
Odpowiedzi:
Wiem, że ludzie TDD będą nienawidzić tej odpowiedzi, ale dla mnie duża część to ostrożne wybranie, gdzie coś przetestować.
Jeśli zwariuję na punkcie testów jednostkowych na niższych poziomach, nie można wprowadzić znaczących zmian bez zmiany testów jednostkowych. Jeśli interfejs nigdy nie jest narażony i nie jest przeznaczony do ponownego użycia poza aplikacją, jest to po prostu niepotrzebny narzut na to, co w innym przypadku mogłoby być szybką zmianą.
I odwrotnie, jeśli to, co próbujesz zmienić, zostanie ujawnione lub ponownie użyte w każdym z tych testów, które będziesz musiał zmienić, jest dowodem na coś, co możesz złamać gdzie indziej.
W niektórych projektach może to oznaczać zaprojektowanie testów od poziomu akceptacji w dół, a nie od testów jednostkowych w górę. oraz mniej testów jednostkowych i więcej testów stylu integracji.
Nie oznacza to, że nadal nie można zidentyfikować pojedynczej funkcji i kodu, dopóki ta funkcja nie spełni kryteriów akceptacji. Oznacza to po prostu, że w niektórych przypadkach nie kończy się mierzeniem kryteriów akceptacji testami jednostkowymi.
źródło
Właśnie zakończyłem gruntowny przegląd stosu SIP, przepisując cały transport TCP. (Był to prawie refaktor, na dość dużą skalę, w stosunku do większości refaktoryzacji).
W skrócie, istnieje TIdSipTcpTransport, podklasa TIdSipTransport. Wszystkie TIdSipTransports mają wspólny zestaw testów. Wewnątrz TIdSipTcpTransport znajdowało się kilka klas - mapa zawierająca pary połączenia / komunikatu inicjującego, wątkowych klientów TCP, wątkowy serwer TCP i tak dalej.
Oto co zrobiłem:
W ten sposób wiedziałem, co jeszcze muszę zrobić, w postaci skomentowanych testów (*) i wiedziałem, że nowy kod działa zgodnie z oczekiwaniami, dzięki nowym testom, które napisałem.
(*) Naprawdę, nie musisz ich komentować. Po prostu ich nie uruchamiaj; 100 nieudanych testów nie jest zbyt zachęcające. Ponadto w mojej konkretnej konfiguracji skompilowanie mniejszej liczby testów oznacza szybszą pętlę test-zapis-refaktor.
źródło
Kiedy testy są kruche, zwykle stwierdzam, że testuję niewłaściwą rzecz. Weźmy na przykład dane wyjściowe HTML. Jeśli sprawdzisz rzeczywiste wyjście HTML, twój test będzie kruchy. Ale nie interesuje Cię faktyczny wynik, interesuje Cię, czy przekazuje informacje, które powinien. Niestety, zrobienie tego wymaga stwierdzenia na temat zawartości mózgu użytkownika i dlatego nie można tego zrobić automatycznie.
Możesz:
To samo dzieje się z SQL. Jeśli potwierdzisz faktyczny SQL, twoje klasy spróbują sprawić, że będziesz miał kłopoty. Naprawdę chcesz potwierdzić wyniki. Dlatego podczas testów jednostkowych używam bazy danych pamięci SQLITE, aby upewnić się, że mój SQL faktycznie robi to, co powinien.
źródło
Najpierw utwórz NOWY interfejs API, który będzie działał tak, jak chcesz. Jeśli zdarza się, że ten nowy interfejs API ma taką samą nazwę jak interfejs OLDER API, to dołączam nazwę _NEW do nowej nazwy interfejsu API.
int DoSomethingInterestingAPI ();
staje się:
int DoSomethingInterestingAPI_NEW (int takes_more_arguments); int DoSomethingInterestingAPI_OLD (); int DoSomethingInterestingAPI () {DoSomethingInterestingAPI_NEW (what_default_mimics_the_old_API); OK - na tym etapie - wszystkie testy regresji przechodzą pomyślnie - używając nazwy DoSomethingInterestingAPI ().
NASTĘPNIE, przejrzyj kod i zmień wszystkie wywołania DoSomethingInterestingAPI () na odpowiedni wariant DoSomethingInterestingAPI_NEW (). Obejmuje to aktualizację / przepisywanie dowolnych części testów regresji, które należy zmienić, aby korzystać z nowego interfejsu API.
NASTĘPNY, oznacz DoSomethingInterestingAPI_OLD () jako [[przestarzałe ()]]. Trzymaj się przestarzałego interfejsu API tak długo, jak chcesz (dopóki nie zaktualizujesz bezpiecznie całego kodu, który może od niego zależeć).
Dzięki takiemu podejściu wszelkie niepowodzenia w testach regresji są po prostu błędami w teście regresji lub identyfikują błędy w kodzie - dokładnie tak, jak byś tego chciał. Ten etapowy proces przeglądu interfejsu API poprzez jawne tworzenie wersji API _NEW i _OLD pozwala na współdziałanie przez jakiś czas nowego i starego kodu.
Oto dobry (twardy) przykład tego podejścia w praktyce. Miałem funkcję BitSubstring () - w której zastosowałem podejście polegające na tym, że trzecim parametrem jest LICZBA bitów w podciągu. Aby zachować spójność z innymi interfejsami API i wzorcami w C ++, chciałem przełączyć na początek / koniec jako argumenty funkcji.
https://github.com/SophistSolutions/Stroika/commit/003dd8707405c43e735ca71116c773b108c217c0
Utworzyłem funkcję BitSubstring_NEW z nowym API i zaktualizowałem cały mój kod, aby z niej korzystać (pozostawiając NIE WIĘCEJ POŁĄCZEŃ dla BitSubString). Ale zostawiłem implementację na kilka wydań (miesiące) - i zaznaczyłem, że jest przestarzała - aby każdy mógł przełączyć się na BitSubString_NEW (i wtedy zmienić argument z stylu liczenia na początek / koniec).
NASTĘPNIE - kiedy przejście zostało zakończone, zrobiłem kolejne zatwierdzenie, usuwając BitSubString () i zmieniając nazwę BitSubString_NEW-> BitSubString () (i przestałem używać nazwy BitSubString_NEW).
źródło