Jak radykalnie poprawić zasięg kodu?

21

Zadanie polega na poddaniu starszej aplikacji testowi jednostkowemu. Najpierw podstawowe informacje o aplikacji: jest to baza kodów RCP Java LOC o wielkości 600 000 LOC z tymi głównymi problemami

  • masowe powielanie kodu
  • bez enkapsulacji, większość danych prywatnych jest dostępna z zewnątrz, niektóre dane biznesowe również stały się singletonami, więc można je zmieniać nie tylko z zewnątrz, ale także zewsząd.
  • brak abstrakcji (np. brak modelu biznesowego, dane biznesowe są przechowywane w Object [] i podwójnie [] []), więc nie ma OO.

Istnieje dobry zestaw testów regresji, a sprawny zespół kontroli jakości testuje i znajduje błędy. Znam techniki sprawdzania tego z klasycznych książek, np. Michael Feathers, ale to jest zbyt wolne. Ponieważ istnieje działający system testów regresji, nie boję się agresywnego refaktoryzacji systemu, aby umożliwić pisanie testów jednostkowych.

Jak powinienem zacząć atakować problem, aby szybko uzyskać zasięg , aby móc pokazać postępy w zarządzaniu (i faktycznie zacząć zarabiać z siatki bezpieczeństwa testów JUnit)? Nie chcę używać narzędzi do generowania zestawów testów regresji, np. AgitarOne, ponieważ testy te nie sprawdzają, czy coś jest poprawne.

Peter Kofler
źródło
Dlaczego nie utworzyć automatycznie testów regresji i zweryfikować każdy z nich osobno? Musisz być szybszy niż pisanie ich wszystkich ręcznie.
Robert Harvey,
Nazywanie wszystkiego, co napisano w spuściźnie Java, brzmi trochę zabawnie, ale zgodziło się, że to na pewno spuścizna. Wspominasz, że nie boisz się refaktoryzować systemu, aby umożliwić pisanie testów jednostkowych, ale czy nie powinieneś pisać testów jednostkowych w systemie, zanim zostaną podjęte jakiekolwiek próby refaktoryzacji? Czy Twoje refaktoryzacja może być przeprowadzona przez te same testy jednostek, aby upewnić się, że nic nie jest zepsute?
dodgy_coder
1
@dodgy_coder Zwykle się zgadzam, ale mam nadzieję, że tradycyjna kontrola jakości, która działa skutecznie, uratuje mi trochę czasu.
Peter Kofler
1
@dodgy_coder Michael C. Feathers, autor książki Working Effective with Legacy, definiuje starszy kod jako „kod bez testów”. Służy jako przydatna definicja.
StuperUser

Odpowiedzi:

10

Uważam, że istnieją dwie główne osie, wzdłuż których można umieścić kod, jeśli chodzi o wprowadzenie testów jednostkowych: A) jak testowalny jest kod? i B) jak stabilny jest (tj. jak pilnie potrzebuje testów)? Patrząc tylko na skrajności, daje to 4 kategorie:

  1. Kod, który jest łatwy do przetestowania i kruchy
  2. Kod, który jest łatwy do przetestowania i stabilny
  3. Kod trudny do przetestowania i łamliwy
  4. Kod trudny do przetestowania i stabilny

Kategoria 1 to oczywiste miejsce na start, w którym możesz uzyskać wiele korzyści przy stosunkowo niewielkiej pracy. Kategoria 2 pozwala szybko poprawić statystyki pokrycia (dobre dla morale) i uzyskać więcej doświadczenia z bazą kodu, podczas gdy kategoria 3 to więcej (często frustrujące) prace, ale także przynosi więcej korzyści. To, co powinieneś zrobić najpierw, zależy od tego, jak ważne są dla ciebie statystyki morale i zasięgu. Kategoria 4 prawdopodobnie nie jest warta zawracania sobie głowy.

Michael Borgwardt
źródło
Doskonały. Mam pomysł, jak ustalić, czy łatwo jest to sprawdzić za pomocą analizy statycznej, np. Liczba zależności / Eksplorator testowalności. Ale jak mogę ustalić, czy kod jest kruchy? Nie mogę dopasować defektów do konkretnych jednostek (np. Klas), a jeśli jest to oczywiście numer 3 (boskie klasy / singletony). Więc może liczba meldowań (hotspotów)?
Peter Kofler
1
@Peter Kofler: zatwierdzanie punktów aktywnych jest dobrym pomysłem, ale najcenniejszym źródłem tego rodzaju wiedzy byliby programiści, którzy pracowali z kodem.
Michael Borgwardt,
1
@Peter - jak powiedział Michael, programiści, którzy pracowali z kodem. Każdy, kto pracował przez długi czas z dużą bazą kodu, będzie wiedział, które części pachną. Lub, jeśli całość pachnie, które części naprawdę śmierdzą .
Carson63000
15

Mam duże doświadczenie w pracy na starszych systemach (choć nie Java), znacznie większych niż to. Nienawidzę być nosicielem złych wiadomości, twój problem jest wielkości twojego problemu. Podejrzewam, że nie doceniłeś tego.

Dodanie testów regresji do starszego kodu jest powolnym, kosztownym procesem. Wiele wymagań nie jest dobrze udokumentowanych - tutaj naprawiono błąd, poprawkę, a zanim się zorientujesz, oprogramowanie określa własne zachowanie. Brak testów oznacza, że ​​wystarczy przejść na wdrożenie, nie ma testów, które mogłyby „zakwestionować” ukryte wymagania zaimplementowane w kodzie.

Jeśli spróbujesz szybko uzyskać ochronę, prawdopodobnie spiesz się z pracą, upiecz ją na pół i zawiedzie. Testy zapewnią częściowe omówienie oczywistych rzeczy, a słabe - brak pokrycia prawdziwych problemów. Przekonasz tych menedżerów, których próbujesz sprzedać, że Testowanie Jednostkowe nie jest tego warte, że jest to kolejna srebrna kula, która nie działa.

IMHO, najlepszym podejściem jest skierowanie cię na testy. Skorzystaj z miar, raportów jelita i raportów błędów, aby zidentyfikować 1% lub 10% kodu, który powoduje najwięcej problemów. Uderz mocno w te moduły i zignoruj ​​resztę. Nie próbuj robić zbyt wiele, mniej znaczy więcej.

Realistycznym celem jest „Od czasu wdrożenia UT, wstawianie defektów w testowanych modułach spadło do x% tych, które nie są pod UT” (idealnie x jest liczbą <100).

mattnz
źródło
+1, nie możesz przetestować czegoś skutecznie bez silniejszego standardu niż kod.
dan_waterworth
Wiem i zgadzam się. Różnica polega na tym, że mamy test, tradycyjne testy regresji, pracując w miejscu kontroli jakości, więc istnieje pewna siatka bezpieczeństwa. Po drugie, zdecydowanie popieram testy jednostkowe, więc na pewno nie będzie to kolejna rzecz, która nie zadziałała. Dobra wskazówka, na co najpierw celować. Dzięki.
Peter Kofler
1
i nie zapominaj, że samo dążenie do „pokrycia” nie poprawi jakości, ponieważ utkniesz w bagnie wadliwych i trywialnych testów (i testów dla trywialnego kodu, który nie wymaga jawnego testowania, ale są dodawane tylko w celu zwiększenia zasięgu). W końcu stworzysz testy, aby zadowolić narzędzie pokrycia, nie dlatego, że są one przydatne, i prawdopodobnie zmienisz sam kod, aby zwiększyć zasięg testu bez pisania testów (np. Wycinanie komentarzy i definicji zmiennych, które niektóre narzędzia pokrycia wywoła nieodkryty kod).
jwenting
2

Przypomniało mi się to powiedzenie o tym, że nie martwię się o drzwi stodoły, kiedy koń już pędzi.

Rzeczywistość jest taka, że ​​naprawdę nie ma opłacalnego sposobu na uzyskanie dobrego pokrycia testowego dla starszego systemu, z pewnością nie w krótkim czasie. Jak wspomniał MattNz, będzie to bardzo czasochłonny proces, a ostatecznie wyjątkowo kosztowny. Moja intuicja mówi mi, że jeśli spróbujesz zrobić coś, aby zrobić wrażenie na zarządzaniu, prawdopodobnie stworzysz nowy koszmar konserwacji, ponieważ próbujesz zbyt szybko pokazywać zbyt wiele, bez pełnego zrozumienia wymagań, które próbujesz przetestować.

Realistycznie, po napisaniu kodu, jest już prawie za późno, aby napisać testy skutecznie bez ryzyka pominięcia czegoś istotnego. Z drugiej strony można powiedzieć, że niektóre testy są lepsze niż brak testów, ale w takim przypadku same testy muszą wykazać, że wnoszą wartość dodaną do systemu jako całości.

Moją sugestią byłoby przyjrzenie się kluczowym obszarom, w których czujesz, że coś jest „zepsute”. Rozumiem przez to, że może to być bardzo nieefektywny kod lub że można wykazać, że jego utrzymanie było wcześniej kosztowne. Udokumentuj problemy, a następnie wykorzystaj to jako punkt wyjścia do wprowadzenia poziomu testowania, który pomoże Ci ulepszyć system, bez podejmowania ogromnych wysiłków związanych z przeprojektowaniem. Chodzi tutaj o to, aby uniknąć nadrabiania zaległości w testach, a zamiast tego wprowadzić testy, które pomogą Ci rozwiązać określone problemy. Po pewnym czasie sprawdź, czy możesz zmierzyć i rozróżnić poprzedni koszt utrzymania tej sekcji kodu oraz bieżące wysiłki związane z poprawkami, które zastosowałeś w ich testach pomocniczych.

Należy pamiętać, że kierownictwo jest bardziej zainteresowane kosztem / korzyściami i tym, jak bezpośrednio wpływa to na ich klientów, a ostatecznie na ich budżet. Nigdy nie są zainteresowani robieniem czegoś po prostu dlatego, że jest to najlepsza rzecz, chyba że możesz udowodnić, że zapewni im to interesującą ich korzyść. Jeśli jesteś w stanie wykazać, że poprawiasz system i otrzymujesz dobre pokrycie testowe dla pracy, którą obecnie wykonujesz, kierownictwo jest bardziej skłonne postrzegać to jako skuteczne zastosowanie twoich wysiłków. Może to pozwolić ci na argumentowanie za rozszerzeniem swoich wysiłków na inne kluczowe obszary, bez wymagania albo całkowitego wstrzymania rozwoju produktu, albo, co gorsza, prawie niemożliwego do argumentowania za przepisaniem!

S.Robins
źródło
1

Jednym ze sposobów na poprawę zasięgu jest napisanie większej liczby testów.

Innym sposobem jest zmniejszenie nadmiarowości w kodzie, tak aby istniejące testy faktycznie obejmowały nadmiarowy kod, który nie jest obecnie objęty.

Wyobraź sobie, że masz 3 bloki kodu, a, b i b ', gdzie b' to duplikat (dokładna lub prawie brakująca kopia) B i że masz zasięg na aib, ale nie b 'z testem T.

Jeśli zmienimy podstawę kodu, aby wyeliminować b ', wyodrębniając podobieństwo zb i b' jako B, podstawa kodu wygląda teraz jak a, b0, B, b'0, przy czym b0 zawiera nieudostępniony kod z b'0 i odwrotnie versa, a zarówno b0, jak i b'0 są znacznie mniejsze niż B i wywoływanie / używanie B.

Teraz funkcjonalność programu się nie zmieniła, podobnie jak test T, więc możemy uruchomić T ponownie i oczekiwać, że przejdzie. Kod objęty teraz to a, b0 i B, ale nie b'0. Baza kodu stała się mniejsza (b'0 jest mniejsza niż b '!) I nadal pokrywamy to, co pierwotnie pokryliśmy. Nasz wskaźnik zasięgu wzrósł.

Aby to zrobić, musisz znaleźć b, b 'i idealnie B, aby umożliwić refaktoryzację. Nasz CloneDR narzędzie może to zrobić w wielu językach, zwłaszcza w Javie. Mówisz, że twoja baza kodu ma dużo zduplikowanego kodu; może to być dobry sposób na rozwiązanie tego z korzyścią.

Co dziwne, czynność znajdowania b i b 'często poszerza Twoje słownictwo na temat abstrakcyjnych pomysłów realizowanych przez program. Chociaż narzędzie nie ma pojęcia, co robią b i b ', sam fakt odizolowania ich od kodu, umożliwiając proste skupienie się na treści b i b', często daje programistom bardzo dobre wyobrażenie o tym, jaką abstrakcję B_abstract implementuje sklonowany kod . To poprawia zrozumienie kodu. Upewnij się, że nadasz B dobre imię, gdy go odrzucisz, a dostaniesz lepszy zasięg testu i łatwiejszy w utrzymaniu program.

Ira Baxter
źródło