Pisanie testów do starego kodu - jak?

1

Mojej bolączki z testami ciąg dalszy. Ale teraz obawiam się, że mogę być naprawdę w kropce.

Nie przeczę - emocjonalnie nie cierpię testów. Pewnie wynika to z małego doświadczenia - zamiast ze znajdowaniem problemów zanim pójdą na produkcję i z ułatwianiem sobie pracy kojarzą mi się obecnie z upierdliwym i żmudnym zajęciem, które trzeba odfajkować.

Gdy piszę kod sam dla siebie to nie daję testów; nie odczułem jak dotąd ich braku, mimo iż tym zajmuję się z przerwami już od jakiegoś półtora roku. (prawdopodobnie bierze się to stąd, że znam i rozumiem własny kod; a jeśli nie rozumiem i nie mogę łatwo wprowadzać zmian, to zgrzytam zębami na to, jak mogłem to dawniej tak pokomplikować niepotrzebnie i przepisuję - tzn źródła problemu upatruję gdzie indziej, niż w braku testów).

Gdy piszę kod w pracy... Tam ode mnie żądają testów. Oto więc, co robię: 1. Piszę kod bez testów; 2. Gdy skończę moduł, piszę testy ale dla całego modułu, a nie każdej pojedynczej metody. Przy czym: Lubię punkt 1, zgrzytam zębami przy 2.

Dlaczego testuję całe moduły, a nie każdą pojedynczą funkcję? Moje rozumowanie: Testy mają w założeniu umożliwić refaktoryzację kodu; ale jeśli testy są powiązane z każdą metodą, to do (...) nie da się refaktoryzować kodu bez ponownego babrania się w testach! (W zasadzie: Bez pisania na nowo testów do tych fragmentów kodu, które się refaktoryzuje). Nawet Uncle Bob krytykuje pomysł, by testować każdą metodę; choć chce on pełnego pokrycia testami, jednak testy mają wg niego nie mieć odpowiedniości 1-1 z kodem

Mimo to: i tak testy nie są na zasadzie napisz - zapomnij. Za każdym razem, gdy zmieniają się wymogi (trzeba dodać funkcjonalność do kodu) testy trzeba przeglądać i się z nimi pier(...). Nie tylko pisać nowe - także poprawiać stare. Dlatego mi się kojarzą na razie ze stałą uciążliwością, nie z pomocą.

Jak dotąd, kiedy stare testy się waliły, to za niemal każdym razem oznaczało to, że należy dostosować stare testy do zmian w kodzie, nie zaś, że kod ma bug. Niemal zawsze: bowiem muszę uczciciwie przyznać, że pokrycie kodu testami wykryło jak dotąd 1 (słownie: jeden) bug. W kontraście do wielu innych bugów, które wykryłem w swoim kodzie w inny sposób.

W przeciwieństwie do siebie sprzed półtora roku uznaję jednak, że moja powyższa mentalność wynika z mojego braku umiejętności i wiedzy, a nie z tego, że testy są do d... Zaś mój brak umiejętności i wiedzy wynika z tego właśnie, że przez długi czas (czyli czas studiów) niczym zbuntowany nastolatek odrzucałem dobre praktyki pisania kodu i za punkt honoru postawiłem sobie pisanie kodu tak, jak ja chcę i nie przyjmowanie żadnych "dobrych praktyk", reguł, etc, dopóki na własnym kodzie i własnym doświadczeniu nie odczuję braku stosowania tych praktyk. Wskutek czego kiedy nadszedł czas na pracę, gdzie tych dobrych praktyk się wymaga, to.... no właśnie.

Dobra, koniec wstępu, problem właściwy.

Dlaczego w pracy żądają ode mnie testów? Bo twierdzą, że na braku testów już się przejechali. Obecnie pozornie niewinna zmiana w kodzie wywołuje bugi w zupełnie innych elementach aplikacji, dalekich od tego, co było zmieniane. Żądają więc obecnie testów do każdego nowego kodu i... chcą pokryć testami kod stary.

Właśnie otrzymałem zadanie pokrycia testami kodu starego.

I nie wiem, jak się za to zabrać!

Mało! Kiedy przeszukuję SoftwareEngineering.stackexchange.com w poszukiwaniu wiedzy na ten temat, natrafiłem na taki artykuł: Writing tests for existing code - gdzie doradzają wręcz, by nie pokrywać testami legacy code'u dla samego pokrycia bo to się mija z celem, tylko pokrywać testami w miarę wprowadzania zmian i nowego kodu! Gdzie indziej piszą odmiennie: 1 2

Ale tak czy siak, z tego co czytam tego rodzaju zadanie jest b. trudne... biorąc pod uwagę, że dopiero zaczynam się uczyć, jak poprawnie pisać kod... (poprawnie w sensie: zgodnie z prawidłami a nie bez bugów)... czuję się troche wrzucony na zbyt głęboką wodę na razie. (tak wiem, wina jest moja, pisałem o tym wyżej).

Co więcej: Firma chyba oczekuje ode mnie "systemu testowania aplikacji" - a zgodnie z tym, co dotąd wyczytałem, to chyba ze starym kodem postępuje się odmiennie? Tj. wybiera się z niego żmudnie małe kawałki, testuje je, łączy z resztą kodu. A nie od razu testuje całość!

Odnoszę wrażenie, że być może szef sam nie umie ocenić tego zadania, skoro oczekuje ode mnie panaceum na ich bolączki w tej materii i to w stosunkowo krótkim czasie! (z tego co czytałem to jest to zadanie żmudne i czasochłonne)

Poważnie: nie wiem, co z tym teraz zrobić. Rady?

2

Jak nie masz testów to skąd wiesz, że Twój kod działa? Może źle Ci się pisze testy właśnie dlatego, że nie piszesz testów? ;) Jeśli najpierw napiszesz testy a dopiero potem kod, zobaczysz, że dzięki temu kod będzie też lepszej "jakości"

Co do kodu legacy. Potraktuj stary kod jako czarną skrzynkę i testuj całościowo bez wnikania co tam w środku jest

9
  1. Nie testuje sie klas czy metod, tylko funkcje systemu. Testy na poziomie klasy czy metody i ogólnie "unit testy" pisze sie kiedy masz jakiś złożony kawałek logiki i koniecznie chcesz go testować w izolacji. Robienie tego dla większości klas nie ma najmniejszego sensu.
  2. Pisz testy integracyjne na poziomie konkretnego serwisu. Tzn wszystko co zewnętrzne obstawiasz jakimiś httpmockami i bazami danych in memory, albo najlepiej za pomocą jakiegoś testcontainters dockeryzowanymi komponentami. Następnie piszesz testy funkcji systemu, tzn stukasz w teście w "entry point" aplikacji i potem sprawdzasz czy wyniki są te dobre. Jeśli aplikacja ma X endpointów to musisz pokryć każdy z nich, najlepiej możliwie wszystkimi kombinacjami parametrów.
  3. Takie testy pisze się bardzo fajnie, bo one robią dokładnie to samo co ty robisz "ręcznie" żeby się upewnic ze to co zrobiłeś działa.
2

Jest taka technika, nie wiem jak się nazywa ale polega na tym, że:

  1. Bierzesz dany fragment kodu legacy który ma wejście i wyjście, może to być pojedyńcza funkcja, żądanie HTTP czy coś innego.
  2. Przepisujesz ten fragment na nowo, pokrywając wszystko testami.
  3. Na produkcji odpalasz jednocześnie kod legacy, oraz ten nowy (możesz go odpalać np. w nowym wątku, lub zapisywać wejście i odpalać na jakiejś kolejce/cronie, lub jak wykonuje się szybko to odpasz równocześnie 2) - użytkownik dostaje zwrotke z kodu legacy, zapisujesz sobie diff na zwrotce z legacy i z nowego kodu dla każdego wejścia.
  4. Zbierasz np. przez dzień, tydzień, miesiąc (zależnie od sytuacji) logi, a dokładnie diffy (jak się pojawia diff czyli różnica w zachowaniu nowego i starego kodu, to poprawiasz swój nowy kod, oczywiście przy okazji pisząc taki przypadek testowy, lub olewasz zapisując sobie, że taki przypadek jest błędny i twój nowy kod działa lepiej).
  5. Jak już masz wystarczającą ilość przypadków które sprawdziłeś, usuwasz stary kod, zastępujesz nowym i bierzesz się za kolejny fragment.

To bardzo czasochłonna ale też bardzo bezpieczna metoda na pracę z ulepszaniem kodu legacy.

Ogólnie bardzo dużo się nauczyłem właśnie pracując z systemem legacy który powstawał przez 10-15 lat, dowiesz się jak nie pisać kodu, jakie konstrukcje sprawiają, że długoterminowo staje się ciężki do utrzymania itd. przy okazji tracąc kilka włosów (oby nie zębów) ;)

4

Widzisz, powiem Ci coś z perspektywy odoby która uwielbia testy i dla której testy w same w sobie są częścią danej funkcjonalności.

Jak dotąd, kiedy stare testy się waliły, to za niemal każdym razem oznaczało to, że należy dostosować stare testy do zmian w kodzie, nie zaś, że kod ma bug. Niemal zawsze: bowiem muszę uczciciwie przyznać, że pokrycie kodu testami wykryło jak dotąd 1 (słownie: jeden) bug.

I dobrze gdyż znajdowanie bugów to nie domena unittestów. Właśnie to wywalanie sie po zmianach w kodzie to jest ich główna zaleta. Bo przez poprawianie testów potwierdzasz że zapoznałeś się z efektem swoich zmian i są to zmiany świadome. Ryzyko nieprzewidzianych skutków ubocznych z powodu nowych zmian mocno maleje. To jest pomocne. I szczerze jeszcze się zdziwisz jak szybko można stracić kontrolę nad swoim własnym kodem.

Dlaczego w pracy żądają ode mnie testów? Bo twierdzą, że na braku testów już się przejechali. Obecnie pozornie niewinna zmiana w kodzie wywołuje bugi w zupełnie innych elementach aplikacji, dalekich od tego, co było zmieniane. Żądają więc obecnie testów do każdego nowego kodu i... chcą pokryć testami kod stary.

Polecam Ci pracę z czyimś kodem, na podstawie wzorca ASAP i to nietestowanym, cudowne uczucie.

W przeciwieństwie do siebie sprzed półtora roku uznaję jednak, że moja powyższa mentalność wynika z mojego braku umiejętności i wiedzy, a nie z tego, że testy są do d... Zaś mój brak umiejętności i wiedzy wynika z tego właśnie, że przez długi czas (czyli czas studiów) niczym zbuntowany nastolatek odrzucałem dobre praktyki pisania kodu i za punkt honoru postawiłem sobie pisanie kodu tak, jak ja chcę i nie przyjmowanie żadnych "dobrych praktyk", reguł, etc, dopóki na własnym kodzie i własnym doświadczeniu nie odczuję braku stosowania tych praktyk. Wskutek czego kiedy nadszedł czas na pracę, gdzie tych dobrych praktyk się wymaga, to.... no właśnie.

Nic nie uczy tak jak życie. Te wszystkie dobre zasady powstały właśnie przez masę fuckupów jakich doświadczyli inni programiści. Nie miałeś jeszcze okazji zagłębić się w programistyczym bagienku najwidoczniej. W końcu trafisz i zrozumiesz, kto był ten wie #pdk :D

U mnie jest tak, że ja w ogóle nie uznaje kodu za produkcyjny bez testów. I nie interesuje mnie czy czy to kod stażysty czy wymiatacza 15+ lat expa.

0
cmd napisał(a):

U mnie jest tak, że ja w ogóle nie uznaje kodu za produkcyjny bez testów. I nie interesuje mnie czy czy to kod stażysty czy wymiatacza 15+ lat expa.

TDD? Bo po fakcie, jak kod napisany i jak nie muszę, to nie biorę się za "tą papierkową robotę".

4

Co to znaczy po fakcie? Co wy klepiecie jakieś stronki dla janusza na zasadzie zaklepać i zapomnieć? Przecież system cały czas się rozwija i nie ma czegos takiego jak "po fakcie". WTF.
Zresztą u mnie też kod bez testów = task nie skończony. W ogóle w jakim świecie ktoś takie coś puszcza na produkcje i na jakiej podstawie? :D Developer mówi eee no chyba działa, tak mi sie wydaje i heja na proda? To chyba tylko w generic CRUDach to działa.
Jak masz system, gdzie fuckup = miliony monet albo w ogóle zagrożenie życia ludzi, to się tak po prostu nie da.

0
Shalom napisał(a):

Co to znaczy po fakcie? Co wy klepiecie jakieś stronki dla janusza

Po fakcie to znaczy w dla dużego banku inwestycyjnego, gdzie w projekcie rządzi Iwan. Nie zmyślam.

BTW, to są bardzo dziwne układy, w całości relokowane do Polski.

Edit: dlaczego system pracy działa? Bo gasi i cięgi za Iwana zbiera Marek.

3

Pisze ostatnio często testy do starego kodu.
Na ogôł to wygląda to tak

  1. Dostaje do zrobienia bugfix lub mały feature w starym kodzie. Kodu nawet nie jest dużo, ale biznes skomplikowany, każdy komponencik wymienia dane 5 innymi serwisami i 3 bazami danych (niech pies j....e te wszystkie mikroserwisy). Nie wiem nawet przeważnie gdzie jest input, a gdzie output.

  2. Wchodze w kod... Ło panie nie powiem, że to sabotaž, bo widać, że deweloperzy sie starali. Nazwy nawet ludzkie. ..ale macie tu 30 ksiażek o java ee i springu i spodziewałbym sie, że ktoś kiedyś do nich zajrzał. (W kodzie klasyczny random annotation driven development).

  3. Znajduje coś drogą bólu - zwykle szybko mi idzie. Dowalam kolejnego ifa do bagna. Może jakiš debug - i am here.
    (Czasem udaje sie lokalnie serwis postawić, ale samo pokonfigurowanie tuneli do serwisów itp. to tydzien walki z firewallami).

  4. Wpuszczam na testy i prosze testerów, żeby sprawdzili czy totolotek bugfixowy był dla mnie łaskawy.

  5. Nie trafiłem - goto 3.

  6. Czyli wiem co sie dzieje - na podstawie zebranych informacji szukam gdzie w zasadzie był input i gdzie output.
    Wbijam to jako given i oczekiwane w test. Mockuje zewnetrzne serwisy (zwykle mam debug na cały ruch - wiec proste).
    Testy tak zrobione są typu integracyjnego i dość blackboxowe. Stawiam kontekst springowy, a jak to dziecko gorszego boga to i arquilianem jade.

  7. I jak już to mam to robie teraz refaktor tego kawałka kodu, który tknąłem.
    Całość walki to zwykle 3 dni do 2 tygodni dla małego ifa...

Fajnie, że coraz częściej mam ficzery (a czasem kolejne bugi) w rzeczach już objetych testami i zrefaktorowanych. Wtedy kolejna zmiana to minuty. To mnie motywuje.

I tak idzie do przodu...

Kluczowe:

  • raczej nie ingeruje w kod, przed pokryciem testami.
  • Mam w debugu cały input - output z aplikacji,
  • testy raczej e2e.
  • Dopiero jak rozbijam kod na moduły to czasem te kawałki dostają własne unitowe testy (jeśli widze, ze moga byc w wiecej miejscach użyte).

EDIT:
Porada:

  • jak system to jakiś REST to znajdź główne funkcje i na tym poziomie testuj HTTP / albo wirtualne http, duże i sensowne pokrycie, nie tak dużym kosztem.

  • jak to jakiś serwer side rendering to płacz. poszukaj warstwy serwisów / logiki i je wołaj. Tu może być ciężko, w takiej architekturze normą są zblaszczenia, które powodują, ze kod GUI i logiki jest wymieszany

  • jakies w ogóle GUI okienkowe - dramat.

5

Kod nie pokryty testami, to kod nieprodukcyjny +1

Nawet jeśli sam rozumiesz co robi kod i nawet dajmy na to umiesz go przeklinać za każdym razem czy działa, to Twój kolega z zespołu niekoniecznie. Ty za 2 lata też nie. Testy to inwestycja.

Testy również wpływają na kod produkcyjny, o ile robisz TDD. Chociaż bardziej wpływają na jakość samych testów niż produkcji. Ja nie wyobrażam sobie pracować bez pisania „testów z przodu” - czy to e2e jeśli nie da się inaczej, czy unittesty na poziomie modułu.

Anegdota. Dzisiaj znalazłem w logach dziwny request do swojej usługi i nie pamiętałem, dlaczego walidacja nie odbija tego błędem. Znalazłem piękny testcase, który opisuje ten przypadek. Testy to również dokumentacja.

3

Nie musisz pisać testów ani do istniejącego, ani do nowego kodu. Musisz jedynie upewnić się (i pracodawcę), że ten kod działa. Nie znam prostszej metody na sprawdzenie, czy jakiś kawałek kodu dostarcza rezultatów zgodnych z oczekiwaniami, niż właśnie pokrycie go testami.
To, że jak do tej pory wykryłeś jeden bug, nie świadczy o słabości metody, tylko najprawdopodobniej o twoim braku doświadczenia w pisaniu tych testów.

Z tego co piszesz, to błędem w twoim projekcie jest powierzenie pisania testów tobie. W przypadku starego kodu niskiej jakości (a jak jest bez testów to i na ogół nie jest specjalnie czytelny) sprawa nie jest prosta i robienie tego zadania juniorami to raczej kiepskie podejście. Prawdziwą wartością wynikającą z napisania testów do istniejącego kodu, jest doprowadzenie (długie i bolesne) tego kodu do poziomu, na którym pokrycie go testami jest możliwe, czyli doprowadzenie go do stanu przyzwoitości. Oczywiście na początku takiej drogi jesteś w głębokiej dupie, bo masz kod bez testów, więc jakikolwiek refaktor jest bardzo ryzykowny, a jednocześnie bez tego refaktoru nie jesteś w stanie napisać porządnych testów.

1 użytkowników online, w tym zalogowanych: 0, gości: 1