Pisanie testów dla kodu, którego celu nie rozumiem

59

Niedawno ukończyłem refaktoryzację czarnej skrzynki. Nie mogę tego sprawdzić, ponieważ nie mogę wymyślić, jak to przetestować.

Na wysokim poziomie mam klasę, której inicjalizacja obejmuje pobieranie wartości z niektórych klas B. Jeśli klasa B jest „pusta”, generuje sensowne wartości domyślne. Wyodrębniłem tę część do metody, która inicjuje klasę B na te same wartości domyślne.

Muszę jeszcze opracować cel / kontekst jednej z klas lub sposób ich wykorzystania. Nie mogę więc zainicjować obiektu z pustej klasy B i sprawdzić, czy ma właściwe wartości / robi właściwą rzecz.

Moim najlepszym pomysłem jest uruchomienie oryginalnego kodu, twardego kodu w wynikach metod publicznych w zależności od zainicjowanych elementów członkowskich i przetestowanie nowego kodu pod tym kątem. Nie potrafię do końca wyrazić, dlaczego czuję się nieswojo z tym pomysłem.

Czy jest tu lepszy atak?

JETM
źródło
28
Czuję, że zacząłeś w złym końcu. Najpierw powinieneś zrozumieć kod, a następnie go przetestować, a następnie refaktoryzować. Dlaczego dokonujesz refaktoryzacji, nie wiedząc, do czego służy kod?
Jacob Raihle
11
@JacobRaihle To dość wyspecjalizowany program dla osób ze stopniami naukowymi, których nigdy nie dotykałem. Wychodzę z kontekstu, ale po prostu nie jest praktyczne czekać na dobre zrozumienie, zanim zacznę.
JETM
4
To, co nie jest praktyczne, polega na przepisywaniu rzeczy i, gdy zmiany są w produkcji, odkrywaniu, dlaczego nie powinieneś. Jeśli będziesz mógł wcześniej dokładnie przetestować , dobrze, może to być dobry sposób na poznanie podstawy kodu. Jeśli nie, koniecznie musisz to zrozumieć przed zmianą.
Jacob Raihle
37
Istnieje specyficzny rodzaj testowania o nazwie Testowanie charakteryzacji, gdy chcesz przetestować rzeczywiste zachowanie systemu. Po prostu weź swój oryginalny system, a następnie dodaj testy potwierdzające to, co faktycznie robi (i niekoniecznie to, co było przeznaczone!). Służą one jako rusztowanie wokół twojego systemu, które możesz bezpiecznie modyfikować, ponieważ możesz upewnić się, że zachowuje swoje zachowanie.
Vincent Savard
3
Czy nie możesz poprosić o sprawdzenie go przez kogoś, kto to rozumie?
pjc50

Odpowiedzi:

122

Dobrze ci idzie!

Tworzenie automatycznych testów regresji jest często najlepszą rzeczą, jaką możesz zrobić, aby umożliwić refaktoryzację komponentu. Może to być zaskakujące, ale takie testy często można napisać bez pełnego zrozumienia tego, co robi komponent wewnętrznie, o ile rozumiesz wejściowe i wyjściowe „interfejsy” (w ogólnym znaczeniu tego słowa). Robiliśmy to kilka razy w przeszłości dla pełnoprawnych starszych aplikacji, nie tylko klas, i często pomogło nam to uniknąć zepsucia rzeczy, których nie do końca rozumieliśmy.

Jednak powinieneś mieć wystarczającą ilość danych testowych i upewnić się, że dobrze rozumiesz, co robi oprogramowanie z punktu widzenia użytkownika tego komponentu, w przeciwnym razie możesz pominąć ważne przypadki testowe.

IMHO jest dobrym pomysłem, aby zaimplementować automatyczne testy przed rozpoczęciem refaktoryzacji, a nie później, aby można było refaktoryzować małymi krokami i weryfikować każdy krok. Samo refaktoryzacja powinna sprawić, że kod będzie bardziej czytelny, więc pomoże Ci to lepiej zrozumieć krok po kroku elementy wewnętrzne. Tak więc kolejność kroków w tym procesie jest

  1. poznaj kod „z zewnątrz”,
  2. pisać testy regresji,
  3. refaktor, co prowadzi do lepszego zrozumienia wewnętrznych elementów kodu
Doktor Brown
źródło
21
Idealna odpowiedź, również dokładnie taka, jak opisano w książce „Working with Legacy Code”
Altoyr
Raz musiałem zrobić coś takiego. Zbierz typowe dane wyjściowe z aplikacji, zanim ją zmodyfikowałem, a następnie sprawdź moją nową wersję aplikacji, uruchamiając przez nią te same dane testowe. 30 lat temu ... Fortran ... Było to coś w rodzaju przetwarzania / mapowania obrazu, więc nie mogłem naprawdę wiedzieć, jaki powinien być wynik, patrząc na to lub pisząc przypadki testowe. Zrobiłem to na wyświetlaczu wektorowym (trwałym) Tektronix. Praca rządu ... 2 Teletypy walą za mną.
4
Można dodać, że po fakcie nadal możesz pisać testy starego kodu. Następnie możesz wypróbować je na swojej refaktoryzowanej wersji, a jeśli to się zepsuje, przeszukaj bisection w historii zmian, aby znaleźć punkt, w którym zaczyna się łamać.
CodeMonkey
2
Sugerowałbym zrobienie jeszcze jednej rzeczy. Podczas zbierania danych testowych, w miarę możliwości, zbieraj statystyki pokrycia kodu. Będziesz wiedział, jak dobrze dane testowe opisują dany kod.
liori
2
@nocomprende, To zabawne, że zrobiłem dokładnie to samo ze starym kodem naukowym fortran 77 w zeszłym tygodniu. Dodaj drukowanie danych ascii do pliku, skonfiguruj katalogi testowe z danymi wejściowymi i oczekiwanymi danymi wyjściowymi, a mój przypadek testowy był tylko różnicą między dwoma zestawami danych wyjściowych. Jeśli nie pasują do charakteru, to coś zepsułem. Kiedy kod składa się głównie z dwóch podprogramów, z których każdy to 2-3k LoC, musisz gdzieś zacząć.
Godric Seer
1

Ważnym powodem pisania testów jednostkowych jest to, że w jakiś sposób dokumentują one interfejs API komponentu. Niezrozumienie celu testowanego kodu jest tutaj naprawdę problemem. Pokrycie kodu jest kolejnym ważnym celem, trudnym do osiągnięcia bez wiedzy, które gałęzie wykonawcze istnieją i jak są uruchamiane.

Jeśli jednak możliwe jest czyste zresetowanie stanu (lub skonstruowanie nowego obiektu testowego za każdym razem), można napisać testy typu „wyrzucanie śmieci do kosza”, które po prostu dostarczają do systemu głównie losowe dane wejściowe i obserwują dane wyjściowe.

Takie testy są trudne do utrzymania, ponieważ kiedy zawodzą, określenie złożonej przyczyny i powagi może być trudne. Zakres może być wątpliwy. Jednak nadal są znacznie lepsze niż nic. Gdy taki test się nie powiedzie, deweloper może z większą uwagą zweryfikować najnowsze zmiany i, mam nadzieję, wykryć tam błąd.

h22
źródło
1
Wszelkie informacje są lepsze niż latanie w ciemno. Lokalizowałem błędy w programach serwerowych, które były w produkcji, wywołując debugger w pliku zrzutu awaryjnego (Unix) i prosząc o śledzenie stosu. Dało mi nazwę funkcji, w której wystąpił błąd. Nawet bez innej wiedzy (nie wiedziałem, jak korzystać z tego debuggera) pomogło to w czymś, co w innym przypadku byłoby tajemniczą i nieproduktywną sytuacją.