Refaktoryzuję ogromną klasę kodu starszego typu. Refaktoryzacja (jak sądzę) zaleca:
- pisz testy dla starszych klas
- refaktorem do cholery z klasy
Problem: po ponownym złożeniu klasy moje testy w kroku 1 będą musiały zostać zmienione. Na przykład to, co kiedyś było starszą metodą, teraz może być osobną klasą. To, co było jedną metodą, może być teraz kilkoma metodami. Cały krajobraz klasy starszej może zostać zatarty w coś nowego, więc testy, które piszę w kroku 1, będą prawie nieważne. Zasadniczo dodam krok 3. obficie przepisz moje testy
Jaki jest zatem cel napisania testów przed refaktorem? To brzmi bardziej jak ćwiczenie akademickie polegające na tworzeniu większej ilości pracy dla siebie. Piszę teraz testy tej metody i dowiaduję się więcej o tym, jak testować rzeczy i jak działa starsza metoda. Można się tego nauczyć po prostu czytając sam starszy kod, ale pisanie testów jest prawie jak wcieranie w niego nosa, a także dokumentowanie tej tymczasowej wiedzy w osobnych testach. W ten sposób prawie nie mam wyboru, muszę się dowiedzieć, co robi kod. Powiedziałem tutaj tymczasowo, ponieważ zrefakturuję kod, a cała moja dokumentacja i testy będą znaczące i nieważne, z wyjątkiem mojej wiedzy, która pozostanie i pozwoli mi być świeższym w refaktoryzacji.
Czy to jest prawdziwy powód, aby pisać testy przed refaktoryzacją - aby pomóc mi lepiej zrozumieć kod? Musi być inny powód!
Proszę wytłumacz!
Uwaga:
Jest taki post: Czy warto pisać testy dla starszego kodu, gdy nie ma czasu na pełne refaktoryzowanie? ale mówi „napisz testy przed refaktorem”, ale nie mówi „dlaczego” ani co zrobić, jeśli „pisanie testów” wydaje się być „pracowitą pracą, która wkrótce zostanie zniszczona”
źródło
Odpowiedzi:
Refaktoryzacja polega na usunięciu fragmentu kodu (np. Poprawie stylu, projektu lub algorytmów) bez zmiany (widocznego z zewnątrz) zachowania. Piszecie testy, aby upewnić się, że kod przed i po refaktoryzacji jest taki sam, zamiast tego piszecie testy jako wskaźnik tego, że wasza aplikacja przed i po refaktoryzacji zachowuje się tak samo: Nowy kod jest kompatybilny i nie wprowadzono żadnych nowych błędów.
Twoim głównym celem powinno być napisanie testów jednostkowych publicznego interfejsu oprogramowania. Ten interfejs nie powinien ulec zmianie, więc testy (które są automatycznym sprawdzaniem tego interfejsu) również nie powinny się zmienić.
Jednak testy są również przydatne do lokalizowania błędów, więc warto również napisać testy dla prywatnych części oprogramowania. Oczekuje się, że testy te zmienią się podczas refaktoryzacji. Jeśli chcesz zmienić szczegół implementacji (np. Nazewnictwo funkcji prywatnej), najpierw zaktualizuj testy, aby odzwierciedlić zmienione oczekiwania, a następnie upewnij się, że test się nie powiedzie (Twoje oczekiwania nie są spełnione), a następnie zmień rzeczywisty kod i sprawdź, czy wszystkie testy przeszły ponownie. W żadnym momencie testy interfejsu publicznego nie powinny zakończyć się niepowodzeniem.
Jest to trudniejsze, gdy wykonuje się zmiany na większą skalę, np. Przeprojektowując wiele współzależnych części. Ale będzie jakaś granica i na tej granicy będziesz mógł pisać testy.
źródło
Ach, utrzymanie starszych systemów.
Idealnie, jeśli twoje testy traktują klasę tylko poprzez interfejs z resztą bazy kodu, innymi systemami i / lub interfejsem użytkownika. Interfejsy Nie można dokonać refaktoryzacji interfejsu bez wpływu na te komponenty. Jeśli wszystko jest jednym ściśle powiązanym bałaganem, równie dobrze możesz rozważyć wysiłek raczej niż ponowne napisanie, a nie refaktoryzację, ale w dużej mierze jest to semantyka.
Edytować: Powiedzmy, że część twojego kodu coś mierzy i ma funkcję, która po prostu zwraca wartość. Jedynym interfejsem jest wywołanie funkcji / metody / whatnot i otrzymanie zwróconej wartości. Jest to luźne sprzęgło i łatwe do przetestowania w jednostce. Jeśli twój program główny ma podskładnik, który zarządza buforem, a wszystkie wywołania do niego zależą od samego bufora, niektórych zmiennych kontrolnych i odczytuje komunikaty o błędach przez inną sekcję kodu, możesz powiedzieć, że jest ściśle powiązany i jest trudny do testowania jednostkowego. Nadal możesz to zrobić za pomocą wystarczającej liczby próbnych obiektów i tym podobnych rzeczy, ale robi się bałagan. Zwłaszcza w c. Każda zmiana sposobu działania bufora spowoduje uszkodzenie podskładnika.
Zakończ edycję
Jeśli testujesz klasę za pomocą interfejsów, które pozostają stabilne, wówczas testy powinny być ważne przed refaktoryzacją i po niej. Dzięki temu możesz dokonywać zmian z pewnością, że go nie złamałeś. Przynajmniej więcej pewności siebie.
Umożliwia także wprowadzanie zmian przyrostowych. Jeśli jest to duży projekt, nie sądzę, że będziesz chciał po prostu to wszystko zburzyć, zbudować zupełnie nowy system, a następnie rozpocząć opracowywanie testów. Możesz zmienić jedną jego część, przetestować i upewnić się, że zmiana nie sprowadzi reszty systemu. A jeśli tak, to możesz zobaczyć, jak rozwija się gigantyczny splątany bałagan, zamiast być zaskoczonym jego uwolnieniem.
Chociaż możesz podzielić metodę na trzy, nadal będą robić to samo, co poprzednia metoda, więc możesz wykonać test starej metody i podzielić ją na trzy. Wysiłek związany z pisaniem pierwszego testu nie jest marnowany.
Traktowanie wiedzy o starszym systemie jako „wiedzy tymczasowej” nie pójdzie dobrze. Wiedza o tym, jak to poprzednio było, jest niezwykle ważna, jeśli chodzi o starsze systemy. Niezwykle przydatne dla odwiecznego pytania „dlaczego, do diabła, to robi?”
źródło
Moja własna odpowiedź / realizacja:
Po naprawieniu różnych błędów podczas refaktoryzacji zdaję sobie sprawę, że nie przeprowadziłbym tak łatwo ruchów kodu bez testów. Testy ostrzegają mnie przed różnicami behawioralnymi / funkcjonalnymi, które wprowadzam poprzez zmianę kodu.
Nie musisz być bardzo świadomy, gdy masz dobre testy. Możesz edytować kod w bardziej swobodny sposób. Testy przeprowadzają dla Ciebie weryfikację i kontrolę poczytalności.
Poza tym moje testy pozostały prawie takie same, jak w przypadku przebudowy i nie zostały zniszczone. Zauważyłem dodatkowe możliwości dodawania asercji do moich testów, gdy zagłębiałem się w kod.
AKTUALIZACJA
Cóż, teraz bardzo zmieniam swoje testy: / Ponieważ zrefakturowałem pierwotną funkcję na zewnątrz (usunąłem funkcję i zamiast tego utworzyłem nową czystszą klasę, przenosząc puch, który kiedyś był wewnątrz funkcji poza nową klasę), więc teraz testowany kod, który uruchomiłem wcześniej, przyjmuje różne parametry pod inną nazwą klasy i daje różne wyniki (oryginalny kod z puchem miał więcej wyników do przetestowania). A zatem moje testy muszą odzwierciedlać te zmiany i zasadniczo przepisuję moje testy w coś nowego.
Sądzę, że są inne rozwiązania, które mogę zrobić, aby uniknąć przepisywania testów. tzn. zachowaj starą nazwę funkcji z nowym kodem i zawartym w niej puchem ... ale nie wiem, czy to najlepszy pomysł i nie mam jeszcze zbyt dużego doświadczenia, aby dokonać oceny, co należy zrobić.
źródło
Skorzystaj z testów, aby sterować kodem. W starszym kodzie oznacza to pisanie testów dla kodu, który zamierzasz zmienić. W ten sposób nie są osobnym artefaktem. Testy powinny dotyczyć tego, co kod musi osiągnąć, a nie wewnętrznych odwrotności tego, jak to robi.
Ogólnie rzecz biorąc, chcesz dodać testy kodu, który go nie ma) dla kodu idziesz do refaktoryzacji, aby upewnić się, że zachowanie kodu będzie działać zgodnie z oczekiwaniami. Zatem ciągłe uruchamianie zestawu testów podczas refaktoryzacji jest fantastyczną siatką bezpieczeństwa. Myśl o zmianie kodu bez zestawu testów w celu potwierdzenia, że zmiany nie wpływają na coś nieoczekiwanego, jest przerażająca.
Jeśli chodzi o drobiazgową aktualizację starych testów, pisanie nowych testów, usuwanie starych testów itp. Po prostu widzę to jako część kosztów nowoczesnego profesjonalnego oprogramowania.
źródło
Jaki jest cel refaktoryzacji w konkretnym przypadku?
Załóżmy, że w celu pogodzenia się z moją odpowiedzią wszyscy wierzymy (do pewnego stopnia) w TDD (rozwój oparty na testach).
Jeśli celem refaktoryzacji jest wyczyszczenie istniejącego kodu bez zmiany istniejącego zachowania, wówczas pisanie testów przed refaktoryzacją jest sposobem upewnienia się, że nie zmieniłeś zachowania kodu, jeśli ci się powiedzie, testy powiodą się zarówno przed, jak i po refaktoryzujesz.
Testy pomogą ci upewnić się, że nowa praca rzeczywiście działa.
Testy prawdopodobnie odkryją również przypadki, w których oryginalne dzieło nie działa.
Ale w jaki sposób naprawdę dokonujesz znaczącego refaktoryzacji bez wpływu na zachowanie niektórych osób stopniu ?
Oto krótka lista kilku rzeczy, które mogą się zdarzyć podczas refaktoryzacji:
Będę argumentować, że każde z wymienionych działań w pewien sposób zmienia zachowanie .
I będę argumentować, że jeśli twoje refaktoryzacja zmieni zachowanie, twoje testy nadal będą w taki sposób, jak upewnisz się, że nic nie zepsułeś.
Być może zachowanie nie zmienia się na poziomie makr, ale celem testów jednostkowych nie jest zapewnienie zachowania makr. To testy integracyjne . Celem testów jednostkowych jest upewnienie się, że poszczególne części, z których zbudujesz swój produkt, nie są zepsute. Łańcuch, najsłabsze ogniwo itp.
Co z tym scenariuszem:
Załóżmy, że masz
function bar()
function foo()
wykonuje połączenie zbar()
function flee()
wywołuje również funkcjębar()
Tylko dla odmiany,
flam()
dzwoni dofoo()
Wszystko działa wspaniale (przynajmniej najwyraźniej).
Refaktoryzujesz ...
bar()
zostaje przemianowany nabarista()
flee()
zmienia się na połączeniebarista()
foo()
się nie zmieniło na wezwaniebarista()
Oczywiście, twoje testy dla obu
foo()
iflam()
teraz nie.Może w ogóle nie zdawałeś sobie sprawy z
foo()
powołaniabar()
. Z pewnością nie zdawał sobie sprawy, żeflam()
była zależnabar()
drodzefoo()
.Cokolwiek. Chodzi o to, że twoje testy ujawnią nowo złamane zachowanie obu
foo()
iflam()
, stopniowo, podczas pracy nad refaktoryzacją.Testy pomagają ci dobrze refaktoryzować.
Chyba że nie masz żadnych testów.
To trochę wymyślony przykład. Są tacy, którzy twierdzą, że jeśli zmienia się
bar()
przerwyfoo()
, tofoo()
był zbyt skomplikowany, aby zacząć i powinien zostać zepsuty. Ale procedury mogą wywoływać inne procedury z konkretnego powodu i nie można wyeliminować całej złożoności, prawda? Naszym zadaniem jest zarządzanie rozsądne złożonością.Rozważ inny scenariusz.
Budujesz budynek.
Zbudujesz rusztowanie, aby upewnić się, że budynek jest poprawnie zbudowany.
Rusztowanie pomaga między innymi zbudować szyb windy. Następnie burzysz rusztowanie, ale szyb windy pozostaje. Zniszczyłeś „oryginalne dzieło”, niszcząc rusztowanie.
Analogia jest niepewna, ale chodzi o to, że nie jest niespotykane budowanie narzędzi, które pomogą Ci zbudować produkt. Nawet jeśli narzędzia nie są trwałe, są przydatne (nawet konieczne). Stolarze cały czas robią przyrządy, czasem tylko do jednej pracy. Następnie rozrywają przyrządy, czasem używając części do budowy innych przyrządów do innych zadań, a czasem nie. Ale to nie sprawia, że przyrządy są bezużyteczne lub zmarnowane.
źródło