Czy testy jednostkowe można pomyślnie dodać do istniejącego projektu produkcyjnego? Jeśli tak, to jak i czy warto?

140

Zdecydowanie rozważam dodanie testów jednostkowych do istniejącego projektu, który jest w produkcji. Zaczęło się 18 miesięcy temu, zanim naprawdę mogłem dostrzec jakąkolwiek korzyść z TDD (dłoń twarzowa) , więc teraz jest to dość duże rozwiązanie z wieloma projektami i nie mam najmniejszego pojęcia, od czego zacząć dodawanie testów jednostkowych. To sprawia, że ​​zastanawiam się nad tym, że czasami wydaje się, że stary błąd pojawia się ponownie lub błąd jest sprawdzany jako naprawiony, ale tak naprawdę nie jest naprawiany. Testowanie jednostkowe zmniejszyłoby lub zapobiegłoby występowaniu tych problemów.

Czytając podobne pytania na temat SO, zobaczyłem zalecenia, takie jak rozpoczęcie od śledzenia błędów i napisanie przypadku testowego dla każdego błędu, aby zapobiec regresji. Obawiam się jednak, że przeoczę ogólny obraz i przeoczę podstawowe testy, które zostałyby uwzględnione, gdybym używał TDD od samego początku.

Czy są jakieś procesy / kroki, których należy przestrzegać, aby zapewnić, że istniejące rozwiązania są właściwie testowane jednostkowo, a nie tylko wprowadzane? Jak mogę zapewnić, że testy są dobrej jakości i nie są tylko przypadkiem jakiegokolwiek testu, jest lepsze niż brak testów .

Więc myślę, że to, o co też proszę, to;

  • Czy warto podjąć wysiłek dla istniejącego rozwiązania, które jest w produkcji?
  • Czy lepiej byłoby zignorować testy dla tego projektu i dodać je w możliwym przyszłym przepisaniu?
  • Co będzie bardziej korzystne; spędzasz kilka tygodni na dodawaniu testów lub kilka tygodni na dodawaniu funkcjonalności?

(Oczywiście odpowiedź na trzeci punkt zależy całkowicie od tego, czy rozmawiasz z kierownictwem, czy z programistą)


Powód Bounty

Dodanie nagrody, aby spróbować przyciągnąć szerszą gamę odpowiedzi, które nie tylko potwierdzają moje obecne podejrzenie, że jest to dobra rzecz, ale także kilka dobrych powodów przeciw.

Zamierzam napisać to pytanie później z zaletami i wadami, aby spróbować pokazać kierownictwu, że warto poświęcić godziny na przeniesienie przyszłego rozwoju produktu na TDD. Chcę podejść do tego wyzwania i rozwinąć swoje rozumowanie bez własnego, stronniczego punktu widzenia.

djdd87
źródło
11
Obowiązkowy link do książki Michaela Feathersa na ten temat: amazon.com/Working-Effectively-Legacy-Michael-Feathers/dp/ ...
Mark Rushakoff
1
@Mark - Dzięki, jest w linku, który podałem. Miałem nadzieję, że uzyskam przyzwoitą odpowiedź bez kupowania kolejnej książki ... chociaż nigdy nie możesz mieć zbyt wielu książek (chyba że chcesz wykonać pracę).
djdd87
1
Naprawdę musisz przeczytać tę książkę :) Jest jedną z moich ulubionych i naprawdę pomaga zrozumieć napięcie między refaktoryzacją a automatycznym testowaniem.
manuel aldana

Odpowiedzi:

177

Wprowadziłem testy jednostkowe do baz kodu, które wcześniej go nie miały. Ostatni duży projekt, w którym byłem zaangażowany, kiedy to zrobiłem, produkt był już w produkcji z zerowymi testami jednostkowymi, kiedy dotarłem do zespołu. Kiedy odszedłem - 2 lata później - mieliśmy około 4500+ testów dających około 33% pokrycia kodu w bazie kodu z 230 000 + produkcyjnym LOC (finansowa aplikacja Win-Forms w czasie rzeczywistym). Może to zabrzmieć nisko, ale rezultatem była znaczna poprawa jakości kodu i wskaźnika defektów oraz poprawa morale i rentowności.

Można to zrobić, mając zarówno dokładne zrozumienie, jak i zaangażowanie zaangażowanych stron.

Przede wszystkim ważne jest, aby zrozumieć, że testowanie jednostkowe jest umiejętnością samą w sobie. Możesz być bardzo produktywnym programistą według „konwencjonalnych” standardów i nadal mieć trudności z napisaniem testów jednostkowych w sposób, który skaluje się w większym projekcie.

Ponadto, szczególnie w danej sytuacji, dodawanie testów jednostkowych do istniejącej bazy kodu, która nie ma testów, jest również specjalistyczną umiejętnością samą w sobie. O ile ty lub ktoś z twojego zespołu nie ma pomyślnego doświadczenia we wprowadzaniu testów jednostkowych do istniejącej bazy kodu, powiedziałbym, że przeczytanie książki Feather jest wymogiem (nie jest opcjonalne ani zdecydowanie zalecane).

Przejście na testowanie jednostkowe kodu jest inwestycją w ludzi i umiejętności, tak samo jak w jakość kodu. Zrozumienie tego jest bardzo ważne z punktu widzenia nastawienia i zarządzania oczekiwaniami.

Teraz, jeśli chodzi o komentarze i pytania:

Obawiam się jednak, że przeoczę ogólny obraz i przeoczę podstawowe testy, które zostałyby uwzględnione, gdybym używał TDD od samego początku.

Krótka odpowiedź: Tak, przegapisz testy i tak, początkowo mogą nie wyglądać tak, jak miałyby w sytuacji green field.

Bardziej szczegółowa odpowiedź brzmi: to nie ma znaczenia. Zaczynasz bez testów. Zacznij dodawać testy i refaktoryzuj w miarę postępów. Wraz ze wzrostem poziomu umiejętności zacznij podnosić poprzeczkę dla całego nowo napisanego kodu dodanego do projektu. Kontynuuj ulepszanie itp ...

Teraz, czytając między wierszami, odnoszę wrażenie, że wynika to z myślenia o „doskonałości jako wymówce dla niepodejmowania działań”. Lepszym sposobem myślenia jest skupienie się na zaufaniu do siebie. Ponieważ możesz jeszcze nie wiedzieć, jak to zrobić, zorientujesz się, jak to zrobić, i wypełnisz puste pola. Dlatego nie ma powodu do zmartwień.

Ponownie, to umiejętność. Nie można przejść od testów zerowych do doskonałości TDD w jednym „procesie” lub „krok po kroku” podejściu do książki kucharskiej w sposób liniowy. To będzie proces. Twoje oczekiwania muszą dotyczyć stopniowego i narastającego postępu i poprawy. Nie ma magicznej pigułki.

Dobra wiadomość jest taka, że ​​wraz z upływem miesięcy (a nawet lat) Twój kod będzie stopniowo stawał się „prawidłowym”, dobrze podzielonym na czynniki i dobrze przetestowanym kodem.

Na marginesie. Przekonasz się, że główną przeszkodą we wprowadzaniu testów jednostkowych w starej bazie kodu jest brak spójności i nadmierne zależności. Dlatego prawdopodobnie okaże się, że najważniejszą umiejętnością będzie przełamywanie istniejących zależności i oddzielanie kodu, zamiast samodzielnego pisania rzeczywistych testów jednostkowych.

Czy są jakieś procesy / kroki, których należy przestrzegać, aby zapewnić, że istniejące rozwiązania są właściwie testowane jednostkowo, a nie tylko wprowadzane?

Jeśli jeszcze go nie masz, skonfiguruj serwer kompilacji i skonfiguruj kompilację ciągłej integracji, która działa przy każdym sprawdzaniu, w tym wszystkich testach jednostkowych z pokryciem kodu.

Szkol swoich ludzi.

Zacznij gdzieś i zacznij dodawać testy, robiąc postępy z perspektywy klienta (patrz poniżej).

Użyj pokrycia kodu jako przewodniego odniesienia do tego, jaka część kodu produkcyjnego jest testowana.

Czas budowy powinien być zawsze szybki. Jeśli czas kompilacji jest wolny, twoje umiejętności testowania jednostkowego są opóźnione. Znajdź wolne testy i popraw je (oddziel kod produkcyjny i testuj w izolacji). Dobrze napisane, powinieneś być w stanie łatwo przeprowadzić kilka tysięcy testów jednostkowych i nadal ukończyć kompilację w mniej niż 10 minut (~ 1-kilka ms / test to dobra, ale bardzo przybliżona wskazówka, kilka wyjątków może mieć zastosowanie, jak kod wykorzystujący odbicie itp. ).

Sprawdź i dostosuj.

Jak mogę zapewnić, że testy są dobrej jakości i nie są tylko przypadkiem jakiegokolwiek testu, jest lepsze niż brak testów.

Twój własny osąd musi być głównym źródłem rzeczywistości. Nie ma miernika, który mógłby zastąpić umiejętności.

Jeśli nie masz takiego doświadczenia lub osądu, rozważ zatrudnienie kogoś, kto to robi.

Dwa ogólne wskaźniki pomocnicze to całkowite pokrycie kodu i szybkość kompilacji.

Czy warto podjąć wysiłek dla istniejącego rozwiązania, które jest w produkcji?

Tak. Zdecydowana większość pieniędzy wydanych na niestandardowy system lub rozwiązanie jest wydawana po wprowadzeniu go do produkcji. Inwestowanie w jakość, ludzi i umiejętności nigdy nie powinno wychodzić z mody.

Czy lepiej byłoby zignorować testy dla tego projektu i dodać je w możliwym przyszłym przepisaniu?

Musiałbyś wziąć pod uwagę nie tylko inwestycję w ludzi i umiejętności, ale przede wszystkim całkowity koszt posiadania i spodziewany czas życia systemu.

Moja osobista odpowiedź brzmiałaby „tak, oczywiście” w większości przypadków, ponieważ wiem, że jest o wiele lepiej, ale zdaję sobie sprawę, że mogą być wyjątki.

Co będzie bardziej korzystne; spędzasz kilka tygodni na dodawaniu testów lub kilka tygodni na dodawaniu funkcjonalności?

Ani. Twoje podejście powinno polegać na dodawaniu testów do bazy kodu, GDY robisz postęp w zakresie funkcjonalności.

Ponownie, jest to inwestycja w ludzi, umiejętności ORAZ jakość bazy kodu i jako taka będzie wymagała czasu. Członkowie zespołu muszą nauczyć się, jak przełamywać zależności, pisać testy jednostkowe, uczyć się nowych nawyków, poprawiać dyscyplinę i świadomość jakości, jak lepiej projektować oprogramowanie itp. Ważne jest, aby zrozumieć, że kiedy zaczynasz dodawać testy, członkowie zespołu prawdopodobnie tego nie zrobią. mają te umiejętności na wymaganym poziomie, aby to podejście odniosło sukces, więc zatrzymywanie postępów w celu spędzenia całego czasu na dodaniu wielu testów po prostu nie zadziała.

Ponadto dodawanie testów jednostkowych do istniejącej bazy kodu o dowolnej wielkości projektu jest DUŻYM przedsięwzięciem, które wymaga zaangażowania i wytrwałości. Nie możesz zmienić czegoś fundamentalnego, spodziewaj się po drodze dużej wiedzy i poproś swojego sponsora, aby nie oczekiwał zwrotu z inwestycji poprzez zatrzymanie przepływu wartości biznesowej. To nie poleci i szczerze mówiąc nie powinno.

Po trzecie, chcesz zaszczepić w swoim zespole solidne wartości biznesowe. Jakość nigdy nie odbywa się kosztem klienta, a bez jakości nie można działać szybko. Ponadto klient żyje w zmieniającym się świecie, a Twoim zadaniem jest ułatwienie mu adaptacji. Dostosowanie do klienta wymaga zarówno jakości, jak i przepływu wartości biznesowej.

To, co robisz, to spłacanie długu technicznego. Robisz to, jednocześnie obsługując stale zmieniające się potrzeby klientów. Stopniowo, gdy dług jest spłacany, sytuacja poprawia się i łatwiej jest lepiej obsługiwać klienta i dostarczać więcej wartości. Itd. Ten pozytywny rozmach jest tym, do czego powinieneś dążyć, ponieważ podkreśla on zasady zrównoważonego tempa i utrzyma i poprawi moralność - zarówno dla zespołu programistów, klienta, jak i interesariuszy.

Mam nadzieję, że to pomoże

Mahol25
źródło
Odzwierciedla ten sam sposób myślenia, co ja. Obecnie jestem w trakcie prezentowania dokładnego procesu / metody wprowadzania przypadków testowych w dużej aplikacji JAVA. :)
Darshan Joshi
3
To najlepsza odpowiedź na każdy temat, jaką kiedykolwiek czytałem w Stackoverflow. Dobra robota! Jeśli tego nie zrobiłeś, zachęcam do rozważenia napisania książki na temat odpowiedzi na ostatnie pytanie.
Yermo Lamers
Dziękuję Yermo. Nie jestem pewien, czy mam czas na napisanie książki. Ale może mógłbym napisać artykuł na blogu lub dwa. Właśnie zaczynam nowy blog, więc może to trochę potrwać.
Mahol
2
Ta rada dotycząca testów jednostkowych jest ogólną poradą na życie. Poważnie.
Wjdavis5
24
  • Czy warto podjąć wysiłek dla istniejącego rozwiązania, które jest w produkcji?

Tak!

  • Czy lepiej byłoby zignorować testy dla tego projektu i dodać je w możliwym przyszłym przepisaniu?

Nie!

  • Co będzie bardziej korzystne; spędzasz kilka tygodni na dodawaniu testów lub kilka tygodni na dodawaniu funkcjonalności?

Dodanie testów (zwłaszcza testów automatycznych) znacznie ułatwia utrzymanie projektu w przyszłości i znacznie zmniejsza prawdopodobieństwo, że dostarczysz użytkownikowi głupie problemy.

Testy, które należy umieścić a priori, to te, które sprawdzają, czy według Ciebie publiczny interfejs twojego kodu (i każdego modułu w nim zawartego) działa tak, jak myślisz. Jeśli możesz, spróbuj również wywołać każdy tryb awarii izolowanej, który powinien mieć twoje moduły kodu (pamiętaj, że może to być nietrywialne i powinieneś uważać, aby nie sprawdzać zbyt dokładnie, jak coś zawodzi, np. Naprawdę nie chcesz na przykład zliczanie komunikatów dziennika generowanych w przypadku awarii, ponieważ wystarczy zweryfikować, czy w ogóle są zarejestrowane).

Następnie przeprowadź test dla każdego aktualnego błędu w bazie danych błędów, który wywołuje dokładnie błąd i który przejdzie, gdy błąd zostanie naprawiony. Następnie napraw te błędy! :-)

Dodanie testów kosztuje z góry czas, ale otrzymujesz zwrot pieniędzy wiele razy na zapleczu, ponieważ kod kończy się o wiele wyższej jakości. Ma to ogromne znaczenie, gdy próbujesz wysłać nową wersję lub przeprowadzić konserwację.

Donal Fellows
źródło
Dzięki za wyczerpującą odpowiedź. Potwierdził to, co czułem. Zanim oddam głosy, zobaczę, jakie inne odpowiedzi zostaną opublikowane, i zaakceptuję.
djdd87
15

Problem z modernizacją testów jednostkowych polega na tym, że zdasz sobie sprawę, że nie pomyślałeś o wstrzyknięciu zależności tutaj lub użyciu tam interfejsu, a wkrótce będziesz przepisać cały komponent. Jeśli masz na to czas, zbudujesz sobie ładną siatkę bezpieczeństwa, ale po drodze mogłeś wprowadzić subtelne błędy.

Byłem zaangażowany w wiele projektów, które naprawdę wymagały testów jednostkowych od pierwszego dnia i nie ma łatwego sposobu na ich wprowadzenie, poza całkowitym przepisaniem, co zwykle nie może być uzasadnione, gdy kod działa i już zarabia. Ostatnio uciekłem się do pisania skryptów PowerShell, które ćwiczą kod w sposób, który odtwarza defekt, gdy tylko zostanie on zgłoszony, a następnie zachowuje te skrypty jako zestaw testów regresji w celu dalszych zmian. W ten sposób możesz przynajmniej zacząć budować kilka testów dla aplikacji bez zbytniej zmiany jej, jednak są to bardziej testy regresji typu end-to-end niż właściwe testy jednostkowe.

fletcher
źródło
Jeśli kod jest dość dobrze podzielony od samego początku, testowanie go jest dość łatwe. Problem pojawia się, gdy masz kod, który jest niechlujnie zszyty razem i gdzie jedyne udostępnione punkty testowe służą do testów pełnej integracji. (Mam taki kod, w którym testowalność jest prawie zerowa, ponieważ opiera się na innych komponentach, które nie są łatwe do wyszydzenia i gdzie wdrożenie wymaga ponownego uruchomienia serwera. Testowalne ... ale trudne do wykonania)
Donal Fellows
13

Zgadzam się z tym, co powiedział większość innych. Dodanie testów do istniejącego kodu jest cenne. Nigdy nie zgodzę się z tym punktem, ale chciałbym dodać jedno zastrzeżenie.

Chociaż dodawanie testów do istniejącego kodu jest cenne, wiąże się to z kosztami. Jest to kosztem braku tworzenia nowych funkcji. To, jak te dwie rzeczy się równoważą, zależy całkowicie od projektu i istnieje wiele zmiennych.

  • Jak długo zajmie Ci przetestowanie całego tego kodu? Dni? Tygodni? Miesięcy? Lat?
  • Dla kogo piszesz ten kod? Płacisz klientom? Profesor? Projekt open source?
  • Jaki jest Twój harmonogram? Masz twarde terminy, których musisz dotrzymać? Czy masz w ogóle jakieś terminy?

Ponownie, podkreślę, testy są cenne i powinieneś popracować nad przetestowaniem starego kodu. To bardziej zależy od tego, jak do tego podejdziesz. Jeśli możesz sobie pozwolić na porzucenie wszystkiego i przetestowanie całego starego kodu, zrób to. Jeśli to nie jest realistyczne, oto co powinieneś zrobić przynajmniej

  • Każdy nowy kod, który napiszesz, powinien zostać całkowicie poddany testom jednostkowym
  • Każdy stary kod, którego dotkniesz (naprawa błędów, rozszerzenie itp.), Powinien zostać poddany testowi jednostkowemu

Nie jest to również propozycja „wszystko albo nic”. Jeśli masz zespół składający się, powiedzmy, z czterech osób i możesz dotrzymać terminów, zlecając jedną lub dwie osoby na dotychczasowe obowiązki testowe, zrób to.

Edytować:

Zamierzam napisać to pytanie później z zaletami i wadami, aby spróbować pokazać kierownictwu, że warto poświęcić godziny na przeniesienie przyszłego rozwoju produktu na TDD.

To jak pytanie „Jakie są wady i zalety korzystania z kontroli źródła?” lub „Jakie są wady i zalety przeprowadzania wywiadów z ludźmi przed ich zatrudnieniem?” lub „Jakie są wady i zalety oddychania?”

Czasami jest tylko jedna strona sporu. Musisz mieć zautomatyzowane testy w jakiejś formie dla każdego projektu o dowolnej złożoności. Nie, testy nie piszą się same i tak, zajmie trochę więcej czasu, zanim wszystko wyjdzie na jaw. Ale na dłuższą metę naprawienie błędów po fakcie zajmie więcej czasu i będzie kosztować więcej pieniędzy niż napisanie testów z góry. Kropka. To wszystko.

haydenmuhl
źródło
9

Kiedy zaczęliśmy dodawać testy, był to dziesięcioletni kod, zawierający około miliona linii, ze zbyt dużą logiką w interfejsie użytkownika i kodzie raportowania.

Jedną z pierwszych rzeczy, które zrobiliśmy (po skonfigurowaniu serwera ciągłej kompilacji) było dodanie testów regresji. To były testy od początku do końca.

  • Każdy zestaw testów rozpoczyna się od zainicjowania bazy danych w znanym stanie. W rzeczywistości mamy dziesiątki zestawów danych regresji, które przechowujemy w Subversion (w oddzielnym repozytorium od naszego kodu, ze względu na sam rozmiar). FixtureSetUp każdego testu kopiuje jeden z tych zestawów danych regresji do tymczasowej bazy danych, a następnie jest stamtąd uruchamiany.
  • Następnie konfiguracja urządzenia testowego uruchamia proces, którego wyniki nas interesują (ten krok jest opcjonalny - niektóre testy regresyjne istnieją tylko w celu testowania raportów).
  • Następnie każdy test uruchamia raport, wyprowadza raport do pliku .csv i porównuje zawartość tego pliku .csv z zapisaną migawką. Te pliki .csvs zrzutu są przechowywane w Subversion obok każdego zestawu danych regresji. Jeśli wynik raportu nie pasuje do zapisanej migawki, test kończy się niepowodzeniem.

Celem testów regresji jest stwierdzenie, czy coś się zmieni. Oznacza to, że zawodzą, jeśli coś zepsułeś, ale również zawodzą, jeśli coś zmieniłeś celowo (w takim przypadku poprawka polega na zaktualizowaniu pliku migawki). Nie wiesz, że pliki migawek są nawet poprawne - w systemie mogą być błędy (a następnie, gdy naprawisz te błędy, testy regresji zakończą się niepowodzeniem).

Niemniej jednak testy regresji były dla nas ogromną wygraną. Prawie wszystko w naszym systemie ma raport, więc spędzając kilka tygodni na testowaniu raportów, byliśmy w stanie uzyskać pewien poziom pokrycia w dużej części naszej bazy kodu. Napisanie równoważnych testów jednostkowych zajęłoby miesiące lub lata. (Testy jednostkowe zapewniłyby nam znacznie lepsze pokrycie i byłyby znacznie mniej kruche, ale wolałbym mieć coś teraz, zamiast czekać latami na doskonałość).

Następnie wróciliśmy i zaczęliśmy dodawać testy jednostkowe, gdy naprawialiśmy błędy, dodawaliśmy ulepszenia lub potrzebowaliśmy zrozumieć kod. Testy regresji w żaden sposób nie eliminują potrzeby testów jednostkowych; są tylko siatką bezpieczeństwa pierwszego poziomu, dzięki czemu szybko uzyskasz pewien poziom pokrycia testu. Następnie możesz rozpocząć refaktoryzację w celu zerwania zależności, dzięki czemu możesz dodać testy jednostkowe; a testy regresji dają pewność, że refaktoryzacja niczego nie psuje.

Testy regresyjne mają problemy: są powolne i istnieje zbyt wiele powodów, dla których mogą się zepsuć. Ale przynajmniej dla nas były tego warte. W ciągu ostatnich pięciu lat złapali niezliczone błędy i złapali je w ciągu kilku godzin, zamiast czekać na cykl kontroli jakości. Wciąż mamy te oryginalne testy regresyjne, rozłożone na siedem różnych maszyn z ciągłą kompilacją (oddzielnych od tej, która wykonuje szybkie testy jednostkowe), a nawet dodajemy do nich od czasu do czasu, ponieważ wciąż mamy tyle kodu, że nasze 6000 + testy jednostkowe nie obejmują.

Joe White
źródło
8

To jest absolutnie tego warte. Nasza aplikacja ma złożone reguły weryfikacji krzyżowej, a ostatnio musieliśmy wprowadzić istotne zmiany w regułach biznesowych. Skończyło się na konfliktach, które uniemożliwiły użytkownikowi zapisywanie. Zdałem sobie sprawę, że rozwiązanie tego problemu w aplikacji zajmie wieczność (wystarczy kilka minut, aby dojść do punktu, w którym wystąpiły problemy). Chciałem wprowadzić zautomatyzowane testy jednostkowe i miałem zainstalowany framework, ale nie zrobiłem nic poza kilkoma testami pozorowanymi, aby upewnić się, że wszystko działa. Mając w ręku nowe zasady biznesowe, zacząłem pisać testy. Testy szybko zidentyfikowały warunki, które spowodowały konflikty, i udało nam się wyjaśnić zasady.

Jeśli napiszesz testy obejmujące funkcje, które dodajesz lub modyfikujesz, uzyskasz natychmiastowe korzyści. Jeśli czekasz na przepisanie, możesz nigdy nie mieć testów automatycznych.

Nie powinieneś spędzać dużo czasu na pisaniu testów istniejących rzeczy, które już działają. W większości przypadków nie masz specyfikacji istniejącego kodu, więc najważniejszą rzeczą, którą testujesz, jest umiejętność inżynierii wstecznej. Z drugiej strony, jeśli zamierzasz coś zmodyfikować, musisz pokryć tę funkcjonalność testami, aby wiedzieć, że zmiany zostały wprowadzone poprawnie. Oczywiście dla nowej funkcjonalności napisz testy, które kończą się niepowodzeniem, a następnie zaimplementuj brakującą funkcjonalność.

Hugh Brackett
źródło
6

Dodam swój głos i powiem tak, zawsze się przyda!

Jest jednak kilka różnic, o których należy pamiętać: czarna skrzynka vs biała skrzynka oraz jednostka vs funkcjonalność. Ponieważ definicje są różne, oto, co mam na myśli:

  • Czarna skrzynka = testy napisane bez specjalnej wiedzy na temat implementacji, zwykle zaglądające do skrajnych przypadków, aby upewnić się, że wszystko dzieje się tak, jak oczekiwałby naiwny użytkownik.
  • Biała skrzynka = testy napisane ze znajomością implementacji, które często próbują sprawdzić dobrze znane punkty awarii.
  • Testy jednostkowe = testy poszczególnych jednostek (funkcji, oddzielnych modułów itp.). Na przykład: upewnienie się, że klasa tablicy działa zgodnie z oczekiwaniami, a funkcja porównująca ciągi zwraca oczekiwane wyniki dla szerokiego zakresu danych wejściowych.
  • Testy funkcjonalne = testy całego systemu na raz. Testy te sprawdzą jednocześnie dużą część systemu. Na przykład: init, otwórz połączenie, zrób coś w świecie rzeczywistym, zamknij, zakończ. Lubię rozróżniać te testy od testów jednostkowych, ponieważ służą one do innego celu.

Kiedy dodałem testy do dostarczanego produktu w późnej fazie gry, stwierdziłem, że uzyskałem najwięcej korzyści z testów białej skrzynki i testów funkcjonalnych . Jeśli jakakolwiek część kodu, o której wiesz, jest szczególnie delikatna, napisz testy białoskrzynkowe, aby omówić przypadki problemów, aby upewnić się, że nie psuje się dwa razy w ten sam sposób. Podobnie testy funkcjonalne całego systemu są użyteczną metodą sprawdzania poprawności, która pomaga upewnić się, że nigdy nie złamiesz 10 najczęstszych przypadków użycia.

Przydatne są również testy czarnoskrzynkowe i testy jednostkowe małych jednostek, ale jeśli masz ograniczony czas, lepiej dodać je wcześniej. Przed wysyłką na ogół znalazłeś (na własnej skórze) większość skrajnych przypadków i problemów, które wykryłyby te testy.

Podobnie jak inni przypomnę Ci dwie najważniejsze rzeczy dotyczące TDD:

  1. Tworzenie testów to praca ciągła. To się nigdy nie kończy. Powinieneś próbować dodawać nowe testy za każdym razem, gdy piszesz nowy kod lub modyfikujesz istniejący kod.
  2. Twój zestaw testów nigdy nie jest nieomylny! Nie pozwól, aby fakt, że masz testy, uśpił Cię w fałszywym poczuciu bezpieczeństwa. To, że przeszedł pomyślnie zestaw testów, nie oznacza, że ​​działa poprawnie lub że nie wprowadziłeś subtelnej regresji wydajności itp.
Drew Thaler
źródło
4

To, czy warto dodawać testy jednostkowe do aplikacji, która jest w produkcji, zależy od kosztu utrzymania aplikacji. Jeśli aplikacja zawiera kilka błędów i próśb o ulepszenia, być może nie jest to warte wysiłku. OTOH, jeśli aplikacja zawiera błędy lub jest często modyfikowana, testy jednostkowe będą bardzo korzystne.

W tym miejscu pamiętaj, że mówię o dodawaniu testów jednostkowych w sposób selektywny, a nie o generowaniu zestawu testów podobnych do tych, które istniałyby, gdybyś ćwiczył TDD od początku. Dlatego w odpowiedzi na drugą połowę twojego drugiego pytania: zwróć uwagę na użycie TDD w następnym projekcie, niezależnie od tego, czy jest to nowy projekt, czy przepisanie (przepraszam, ale tutaj jest link do innej książki, którą naprawdę powinieneś przeczytać : Rozwijające się oprogramowanie zorientowane obiektowo na podstawie testów )

Moja odpowiedź na twoje trzecie pytanie jest taka sama jak na pierwsze: zależy to od kontekstu twojego projektu.

W Twoim poście osadzone jest kolejne pytanie dotyczące upewnienia się, że wszelkie testy modernizacji zostały wykonane prawidłowo . Ważne jest, aby upewnić się, że testy jednostkowe naprawdę są testami jednostkowymi , a to (częściej niż nie) oznacza, że ​​modernizacja testów wymaga refaktoryzacji istniejącego kodu, aby umożliwić rozdzielenie warstw / komponentów (por. Iniekcja zależności; odwrócenie kontroli; stubbing; kpiny). Jeśli tego nie wymusisz, testy staną się testami integracyjnymi, które są przydatne, ale mniej ukierunkowane i bardziej kruche niż prawdziwe testy jednostkowe.

Seb Rose
źródło
4

Nie wspominasz o języku implementacji, ale w Javie możesz wypróbować następujące podejście:

  1. W osobnym drzewie źródłowym zbuduj testy regresji lub „dymu”, używając narzędzia do ich generowania, co może zapewnić pokrycie prawie 80%. Te testy wykonują wszystkie ścieżki logiki kodu i od tego momentu sprawdzają, czy kod nadal robi dokładnie to, co obecnie (nawet jeśli występuje błąd). Daje to zabezpieczenie przed nieumyślną zmianą zachowania podczas przeprowadzania niezbędnej refaktoryzacji, aby kod był łatwy do ręcznego testowania jednostkowego.

  2. Dla każdego błędu, który naprawisz lub dodanej funkcji od teraz, użyj podejścia TDD, aby upewnić się, że nowy kod jest zaprojektowany tak, aby był testowalny i umieść te testy w normalnym drzewie testów.

  3. Istniejący kod będzie prawdopodobnie wymagał zmiany lub refaktoryzacji, aby był możliwy do przetestowania w ramach dodawania nowych funkcji; Twoje testy dymu zapewnią Ci ochronę przed regresją lub niezamierzonymi, subtelnymi zmianami w zachowaniu.

  4. Podczas wprowadzania zmian (poprawek błędów lub funkcji) za pośrednictwem TDD, po zakończeniu prawdopodobnie test dymu towarzyszącego kończy się niepowodzeniem. Sprawdź, czy awarie są zgodne z oczekiwaniami z powodu wprowadzonych zmian i usuń mniej czytelny test dymny, ponieważ Twój odręczny test jednostkowy obejmuje cały ulepszony element. Upewnij się, że pokrycie testu nie spada, tylko pozostaje na tym samym poziomie lub wzrasta.

  5. Podczas naprawiania błędów napisz zakończony niepowodzeniem test jednostkowy, który jako pierwszy ujawnia błąd.

Chris B.
źródło
3

Chciałbym rozpocząć tę odpowiedź od stwierdzenia, że ​​testowanie jednostkowe jest naprawdę ważne, ponieważ pomoże Ci zatrzymać błędy, zanim wkroczą do produkcji.

Zidentyfikuj obszary projektów / modułów, w których ponownie wprowadzono błędy. zacznij od tych projektów, aby napisać testy. Bardzo sensowne jest pisanie testów pod kątem nowej funkcjonalności i poprawiania błędów.

Czy warto podjąć wysiłek dla istniejącego rozwiązania, które jest w produkcji?

Tak. Zobaczysz efekt schodzenia błędów i łatwiejszą konserwację

Czy lepiej byłoby zignorować testy dla tego projektu i dodać je w możliwym przyszłym przepisaniu?

Poleciłbym zacząć od teraz.

Co będzie bardziej korzystne; spędzasz kilka tygodni na dodawaniu testów lub kilka tygodni na dodawaniu funkcjonalności?

Zadajesz złe pytanie. Zdecydowanie funkcjonalność jest ważniejsza niż cokolwiek innego. Ale raczej powinieneś zapytać, czy spędzenie kilku tygodni na dodaniu testu sprawi, że mój system będzie bardziej stabilny. Czy pomoże to mojemu użytkownikowi końcowemu? Czy pomoże to nowemu programistowi w zespole zrozumieć projekt, a także upewnić się, że nie wprowadzi błędu z powodu braku zrozumienia ogólnego wpływu zmiany.

PK
źródło
3

Bardzo lubię Refactor the Low-wiszący owoc jako odpowiedź na pytanie, od czego zacząć refaktoryzację. To sposób na łatwiejsze projektowanie bez odgryzania więcej niż możesz przeżuć.

Myślę, że ta sama logika odnosi się do TDD - lub po prostu testów jednostkowych: napisz testy, których potrzebujesz, tak jak ich potrzebujesz; pisać testy dla nowego kodu; pisz testy pod kątem pojawiających się błędów. Martwisz się, że zaniedbasz trudniej dostępne obszary bazy kodu i na pewno jest to ryzyko, ale jako sposób na rozpoczęcie: zacznij! Możesz zmniejszyć ryzyko w przyszłości za pomocą narzędzi do pokrywania kodu, a ryzyko nie jest (moim zdaniem) tak duże: jeśli pokrywasz błędy, opisujesz nowy kod, obejmujesz kod, na który patrzysz , to omawiasz kod, który ma największe zapotrzebowanie na testy.

Carl Manaster
źródło
2
  • Tak to jest. gdy zaczniesz dodawać nową funkcjonalność, może to spowodować modyfikację starego kodu i w rezultacie jest źródłem potencjalnych błędów.
  • (zobacz pierwszy) zanim zaczniesz dodawać nową funkcjonalność, cały (lub prawie) kod (najlepiej) powinien być objęty testami jednostkowymi.
  • (patrz pierwsza i druga) :). nowa wspaniała funkcjonalność może „zniszczyć” stary działający kod.
garik
źródło
2

Tak, to możliwe: po prostu spróbuj się upewnić, że cały kod, który piszesz, ma teraz test.

Jeśli kod, który już istnieje, wymaga modyfikacji i można go przetestować, zrób to, ale lepiej nie być zbyt energicznym w próbach wprowadzenia testów dla stabilnego kodu. Takie rzeczy mają zwykle efekt domina i mogą wymknąć się spod kontroli.

graham.reeds
źródło
2

Czy warto podjąć wysiłek dla istniejącego rozwiązania, które jest w produkcji?
Tak. Ale nie musisz pisać wszystkich testów jednostkowych, aby rozpocząć. Po prostu dodaj je jeden po drugim.

Czy lepiej byłoby zignorować testy dla tego projektu i dodać je w możliwym przyszłym przepisaniu?
Nie. Po pierwszym dodaniu kodu, który psuje funkcjonalność, pożałujesz.

Co będzie bardziej korzystne; spędzasz kilka tygodni na dodawaniu testów lub kilka tygodni na dodawaniu funkcjonalności?
Dla nowej funkcjonalności (kodu) jest to proste. Najpierw piszesz test jednostkowy, a następnie funkcjonalność. W przypadku starego kodu Ty decydujesz o drodze. Nie musisz mieć wszystkich testów jednostkowych na miejscu ... Dodaj te, które boli cię najbardziej, nie mając ... Czas (i błędy) pokażą, na którym musisz się skupić;)

udo
źródło
2

Aktualizacja

6 lat po pierwotnej odpowiedzi mam nieco inne podejście.

Myślę, że sensowne jest dodawanie testów jednostkowych do całego nowego kodu, który piszesz - a następnie refaktoryzacja miejsc, w których wprowadzasz zmiany, aby były testowalne.

Pisanie testów za jednym razem dla całego istniejącego kodu nie pomoże - ale nie pisanie testów dla nowego kodu, który piszesz (lub obszarów, które modyfikujesz) również nie ma sensu. Dodawanie testów podczas refaktoryzacji / dodawania elementów jest prawdopodobnie najlepszym sposobem na dodanie testów i uczynienie kodu łatwiejszym w utrzymaniu w istniejącym projekcie bez testów.

Wcześniejsza odpowiedź

Mam zamiar unieść kilka brwi :)

Po pierwsze, jaki jest twój projekt - jeśli jest to kompilator, język, framework lub cokolwiek innego, co nie zmieni funkcjonalnie przez długi czas, to myślę, że dodawanie testów jednostkowych jest absolutnie fantastyczne.

Jeśli jednak pracujesz nad aplikacją, która prawdopodobnie będzie wymagać zmian w funkcjonalności (ze względu na zmieniające się wymagania), nie ma sensu podejmować dodatkowego wysiłku.

Czemu?

  1. Testy jednostkowe obejmują tylko testy kodu - niezależnie od tego, czy kod robi to, do czego jest przeznaczony - nie zastępuje testów ręcznych, które i tak trzeba wykonać (aby odkryć błędy funkcjonalne, problemy z użytecznością i wszystkie inne rodzaje problemów)

  2. Testy jednostkowe kosztują czas! Skąd pochodzę, to cenny towar - a biznes generalnie wybiera lepszą funkcjonalność niż kompletny zestaw testów.

  3. Jeśli Twoja aplikacja jest nawet zdalnie użyteczna dla użytkowników, będą żądać zmian - więc będziesz mieć wersje, które będą robić rzeczy lepiej, szybciej i prawdopodobnie będą robić nowe rzeczy - może być również wiele refaktoryzacji w miarę rozwoju kodu. Utrzymywanie w pełni rozwiniętego zestawu testów jednostkowych w dynamicznym środowisku to ból głowy.

  4. Testy jednostkowe nie wpłyną na postrzeganą jakość Twojego produktu - jakość, którą widzi użytkownik. Jasne, Twoje metody mogą działać dokładnie tak samo, jak pierwszego dnia, interfejs między warstwą prezentacji a warstwą biznesową może być nieskazitelny - ale wiecie co? Użytkownik się tym nie przejmuje! Skorzystaj z pomocy prawdziwych testerów, którzy przetestują Twoją aplikację. I najczęściej te metody i interfejsy i tak muszą się zmienić, wcześniej czy później.

Co będzie bardziej korzystne; spędzasz kilka tygodni na dodawaniu testów lub kilka tygodni na dodawaniu funkcjonalności? - Jest piekło wiele rzeczy, które można zrobić lepiej niż pisanie testów - Pisanie nowych funkcji, zwiększanie wydajności, zwiększanie użyteczności, pisanie lepszych podręczników pomocy, rozwiązywanie oczekujących błędów itp.

Nie zrozum mnie źle - jeśli jesteś absolutnie przekonany, że nic się nie zmieni przez następne 100 lat, śmiało, ogłusz się i napisz te testy. Testy automatyczne to również świetny pomysł dla interfejsów API, w których absolutnie nie chcesz łamać kodu stron trzecich. Wszędzie indziej jest to po prostu coś, co sprawia, że ​​wysyłam później!

Roopesh Shenoy
źródło
Zachęcam do przeczytania tego - joelonsoftware.com/items/2009/01/31.html
Roopesh Shenoy
Czy wolisz „naprawić oczekujące błędy”, czy zapobiec ich powstawaniu? Prawidłowe testowanie jednostkowe oszczędza czas, minimalizując ilość czasu spędzanego na naprawianiu błędów.
Ihor Kaharlichenko
To jest mit. Jeśli mówisz mi, że automatyczne testy jednostkowe zastępują testowanie ręczne, to poważnie się mylisz. A co rejestrują testerzy ręczni, jeśli nie błędy?
Roopesh Shenoy
I tak, nie zrozum mnie źle - nie mówię, że testy jednostkowe to absolutna strata - chodzi o to, aby wziąć pod uwagę czas potrzebny na ich napisanie i powody, dla których mogą się zmienić, gdy zmienisz swój produkt, czy naprawdę się opłacają. , dla mnie próbowałem obu stron i odpowiedź brzmi: nie, nie zwracają się wystarczająco szybko.
Roopesh Shenoy
1

Jest mało prawdopodobne, że kiedykolwiek będziesz mieć znaczący zakres testów, więc musisz taktycznie określać, gdzie dodajesz testy:

  • Jak wspomniałeś, kiedy znajdziesz błąd, to dobry moment na napisanie testu (aby go odtworzyć), a następnie naprawienie błędu. Jeśli zobaczysz, że test odtwarza błąd, możesz być pewien, że jest to dobry, prawidłowy test. Biorąc pod uwagę, że tak duża część błędów to regresje (50%?), Prawie zawsze warto pisać testy regresji.
  • Kiedy zagłębisz się w obszar kodu, aby go zmodyfikować, to dobry czas na napisanie testów wokół niego. W zależności od charakteru kodu odpowiednie są różne testy. Jeden dobry zestaw porad znajduje się tutaj .

OTOH, nie warto po prostu siedzieć i pisać testów wokół kodu, z którego ludzie są zadowoleni - zwłaszcza jeśli nikt nie zamierza go modyfikować. Po prostu nie dodaje wartości (może poza zrozumieniem zachowania systemu).

Powodzenia!

ndp
źródło
1

Gdybym był na twoim miejscu, prawdopodobnie wybrałbym podejście zewnętrzne, zaczynając od testów funkcjonalnych, które sprawdzają cały system. Chciałbym ponownie udokumentować wymagania systemowe przy użyciu języka specyfikacji BDD, takiego jak RSpec, a następnie napisać testy, aby zweryfikować te wymagania poprzez automatyzację interfejsu użytkownika.

Następnie wykonywałbym programowanie oparte na defektach dla nowo odkrytych błędów, pisząc testy jednostkowe w celu odtworzenia problemów i pracowałem nad błędami, dopóki testy nie przejdą.

W przypadku nowych funkcji pozostałbym przy podejściu z zewnątrz: zacznij od funkcji udokumentowanych w RSpec i zweryfikowanych przez zautomatyzowanie interfejsu użytkownika (co oczywiście początkowo zakończy się niepowodzeniem), a następnie dodaj bardziej szczegółowe testy jednostkowe w miarę postępu implementacji.

Nie jestem ekspertem w tym procesie, ale z niewielkiego doświadczenia, jakie mam, mogę powiedzieć, że BDD za pomocą automatycznego testowania interfejsu użytkownika nie jest łatwe, ale myślę, że jest to warte wysiłku i prawdopodobnie przyniosłoby najwięcej korzyści w twoim przypadku.

Mhmmd
źródło
1

W żadnym wypadku nie jestem doświadczonym ekspertem w dziedzinie TDD, ale oczywiście powiedziałbym, że niezwykle ważne jest testowanie jednostkowe tak dużo, jak tylko możesz. Ponieważ kod jest już na miejscu, zacząłbym od wprowadzenia jakiejś automatyzacji testów jednostkowych. Używam TeamCity do wykonywania wszystkich testów w moich projektach i daje to ładne podsumowanie tego, jak wypadły komponenty.

Mając to na miejscu, przejdę do tych naprawdę krytycznych komponentów podobnych do logiki biznesowej, które nie mogą zawieść. W moim przypadku jest kilka podstawowych problemów z trygometrią, które należy rozwiązać dla różnych danych wejściowych, więc testuję je do cholery. Powodem, dla którego to robię, jest to, że kiedy spalam olej o północy, bardzo łatwo jest tracić czas na kopanie w głąb kodu, którego naprawdę nie trzeba dotykać, ponieważ wiesz, że są testowane pod kątem wszystkich możliwych wejść (w moim przypadku jest skończona liczba wejść).

Ok, więc mam nadzieję, że teraz czujesz się lepiej z tymi krytycznymi fragmentami. Zamiast siadać i walić w wszystkie testy, atakowałbym je, gdy się pojawiały. Jeśli trafisz na błąd, który jest prawdziwym PITA do naprawienia, napisz dla niego testy jednostkowe i usuń je z drogi.

Są przypadki, w których okaże się, że testowanie jest trudne, ponieważ nie możesz utworzyć instancji określonej klasy z testu, więc musisz z niej kpić. Och, ale może nie możesz łatwo z tego kpić, ponieważ nie napisałeś do interfejsu. Traktuję te scenariusze „ups” jako okazję do zaimplementowania wspomnianego interfejsu, ponieważ to dobra rzecz.

Stamtąd dostałbym twój serwer kompilacji lub jakąkolwiek automatyzację, którą masz skonfigurowaną za pomocą narzędzia do pokrycia kodu. Tworzą paskudne wykresy słupkowe z dużymi czerwonymi strefami, w których masz słabe pokrycie. Teraz 100% pokrycia nie jest twoim celem, ani też 100% pokrycia niekoniecznie oznaczałoby, że twój kod jest kuloodporny, ale czerwony pasek zdecydowanie motywuje mnie, gdy mam wolny czas. :)

Dave
źródło
1

Jest tak wiele dobrych odpowiedzi, że nie będę ich powtarzać. Sprawdziłem Twój profil i wygląda na to, że jesteś programistą C # .NET. Z tego powodu dodaję odniesienie do projektu Microsoft PEX i Moles , które mogą pomóc w automatycznym generowaniu testów jednostkowych dla starszego kodu. Wiem, że autogeneracja nie jest najlepszym sposobem, ale przynajmniej jest sposobem na rozpoczęcie. Sprawdź ten bardzo interesujący artykuł z magazynu MSDN o używaniu PEX do starszego kodu .

Ladislav Mrnka
źródło
1

Proponuję przeczytać genialny artykuł inżyniera TopTal, który wyjaśnia, od czego zacząć dodawać testy: zawiera dużo matematyki, ale podstawowa idea to:

1) Zmierz łączenie aferentne kodu (CA) (ile klasa jest używana przez inne klasy, co oznacza, że ​​złamanie jej spowodowałoby rozległe szkody)

2) Zmierz złożoność cyklomatyczną (CC) swojego kodu (wyższa złożoność = wyższa zmiana łamania)

Musisz zidentyfikować klasy z wysokim CA i CC, tj. Mieć funkcję f (CA, CC), a klasom z najmniejszymi różnicami między dwiema metrykami należy nadać najwyższy priorytet pokrycia testowego.

Czemu? Ponieważ wysoki poziom CA, ale bardzo niskie klasy CC są bardzo ważne, ale mało prawdopodobne, aby się zepsuły. Z drugiej strony, niski CA, ale wysoki CC prawdopodobnie się zepsuje, ale spowoduje mniej szkód. Więc chcesz zrównoważyć.

Andrejs
źródło
0

To zależy ...
Wspaniale jest mieć testy jednostkowe, ale musisz wziąć pod uwagę, kim są Twoi użytkownicy i co są skłonni tolerować, aby uzyskać produkt bardziej wolny od błędów. Nieuchronnie przez refaktoryzację kodu, który obecnie nie ma testów jednostkowych, wprowadzisz błędy i wielu użytkownikom będzie trudno zrozumieć, że tymczasowo sprawiasz, że produkt jest bardziej wadliwy, aby na dłuższą metę był mniej wadliwy. Ostatecznie to użytkownicy będą mieli ostatnie słowo ...

trendl
źródło
-2

Tak. Nie. Dodawanie testów.

Podejście w kierunku bardziej TDD będzie w rzeczywistości lepiej informować o wysiłkach zmierzających do dodania nowej funkcjonalności i znacznie ułatwi testowanie regresji. Sprawdź to!

indra
źródło