Czy to normalne, że spędzam tyle, jeśli nie więcej, czasu na pisaniu testów niż na prawdziwym kodzie?

210

Uważam, że testy są o wiele trudniejsze i trudniejsze do napisania niż rzeczywisty kod, który testują. Nie jest niczym niezwykłym, że spędzam więcej czasu na pisaniu testu niż testowany kod.

Czy to normalne, czy robię coś złego?

Pytania „ Czy warto przeprowadzać testy jednostkowe lub rozwój oparty na testach? ”,„ Spędzamy więcej czasu na wdrażaniu testu funkcjonalnego niż na wdrażaniu samego systemu, czy to normalne? ”, A ich odpowiedzi dotyczą tego, czy testowanie jest tego warte (jak w„ czy powinniśmy całkowicie pomijać pisanie testów? ”). Choć jestem przekonany, że testy są ważne, zastanawiam się, czy spędzam więcej czasu na testach niż na prawdziwym kodzie, czy to tylko ja.

Sądząc po liczbie wyświetleń, odpowiedzi i pozytywnych opinii na moje pytanie, mogę jedynie założyć, że jest to uzasadniony problem, który nie został poruszony w żadnym innym pytaniu na stronie.

sprężynowy
źródło
20
Anegdota, ale uważam, że spędzam mniej więcej tyle samo czasu na pisaniu testów, co na pisaniu kodu, kiedy TDD. To wtedy wymykam się i piszę testy po tym, jak spędzam więcej czasu na testach niż na kodzie.
RubberDuck,
10
Spędzasz także więcej czasu na czytaniu niż na pisaniu kodu.
Thorbjørn Ravn Andersen
27
Testy są również faktycznym kodem. Po prostu nie wysyłasz tej części do klientów.
Thorbjørn Ravn Andersen
5
Idealnie byłoby spędzić więcej czasu na bieganiu niż pisanie kodu. (W przeciwnym razie po prostu wykonasz to zadanie ręcznie).
Joshua Taylor
5
@RubberDuck: Naprzeciwko tutaj. Czasami, gdy piszę testy po fakcie, kod i projekt są już dość uporządkowane, więc nie muszę zbyt dużo przepisywać kodu i testów. Więc napisanie testów zajmuje mniej czasu. To nie jest reguła, ale zdarza mi się dość często.
Giorgio

Odpowiedzi:

204

Pamiętam z kursu inżynierii oprogramowania, że ​​jeden spędza około 10% czasu na pisaniu nowego kodu, a drugi 90% to debugowanie, testowanie i dokumentacja.

Ponieważ testy jednostkowe wychwytują debugowanie i testowanie wysiłku w kodzie (potencjalnie zdolnym do automatyzacji), sensowne byłoby, aby wkładano w nie więcej wysiłku; Rzeczywisty czas nie powinien być dłuższy niż debugowanie i testowanie, które można wykonać bez pisania testów.

Wreszcie testy powinny również służyć jako dokumentacja! Testy jednostkowe należy pisać w sposób, w jaki kod ma być używany; tzn. testy (i użytkowanie) powinny być proste, włóż skomplikowane rzeczy do implementacji.

Jeśli twoje testy są trudne do napisania, kod, który testują, jest prawdopodobnie trudny w użyciu!

ezoterik
źródło
4
To może być dobry moment, aby sprawdzić, dlaczego kod jest tak trudny do przetestowania :) Spróbuj „rozwinąć” złożone linie wielofunkcyjne, które nie są absolutnie konieczne, takie jak masowo zagnieżdżone operatory binarne / trójskładnikowe ... Naprawdę nienawidzę niepotrzebnych plików binarnych / operator trójskładnikowy, który również ma operator binarny / trójskładnikowy jako jedną ze ścieżek ...
Nelson
53
Zaczynam się nie zgadzać z ostatnią częścią. Jeśli chcesz uzyskać bardzo wysoki zasięg testów jednostkowych, musisz uwzględnić przypadki użycia, które są rzadkie, a czasem wręcz niezgodne z zamierzonym użyciem twojego kodu. Pisanie testów dla tych narożnych skrzynek może być najbardziej czasochłonną częścią całego zadania.
otto
Powiedziałem to gdzie indziej, ale testy jednostkowe mają tendencję do działania znacznie dłużej, ponieważ większość kodu ma tendencję do podążania za wzorcem „zasady Pareto”: możesz pokryć około 80% swojej logiki za pomocą około 20% kodu potrzebnego do pokrycie 100% logiki (tj. pokrycie wszystkich przypadków brzegowych zajmuje około pięć razy więcej kodu testu jednostkowego). Oczywiście, w zależności od frameworka, możesz zainicjować środowisko dla wielu testów, zmniejszając ogólny wymagany kod, ale nawet to wymaga dodatkowego planowania. Zbliżanie się do 100% pewności wymaga znacznie więcej czasu niż po prostu testowanie głównych ścieżek.
phyrfox
2
@phyrfox Myślę, że to zbyt ostrożne, to bardziej jak „pozostałe 99% kodu to przypadki skrajne”. Co oznacza, że ​​pozostałe 99% testów dotyczy przypadków skrajnych.
Móż
@Nelson Zgadzam się, że zagnieżdżone operatory trójskładnikowe są trudne do odczytania, ale nie sądzę, że sprawiają, że testowanie jest szczególnie trudne (dobre narzędzie pokrycia powie ci, jeśli przegapiłeś jedną z możliwych kombinacji). IMO, oprogramowanie jest trudne do przetestowania, gdy jest zbyt mocno sprzężone lub zależy od danych przewodowych lub danych nieprzekazanych jako parametr (np. Gdy warunek zależy od bieżącego czasu i nie jest przekazywany jako parametr). Nie ma to bezpośredniego związku z tym, jak „czytelny” kod jest, oczywiście, wszystkie inne rzeczy są równe, kod czytelny jest lepszy!
Andres F.,
96

To jest.

Nawet jeśli przeprowadzasz tylko testy jednostkowe, nie jest niczym niezwykłym posiadanie w testach większej ilości kodu niż faktycznie testowany. Nie ma w tym nic złego.

Rozważ prosty kod:

public void SayHello(string personName)
{
    if (personName == null) throw new NullArgumentException("personName");

    Console.WriteLine("Hello, {0}!", personName);
}

Jakie byłyby testy? Istnieją co najmniej cztery proste przypadki do przetestowania tutaj:

  1. Imię osoby to null. Czy wyjątek jest rzeczywiście zgłaszany? To co najmniej trzy wiersze kodu testowego do napisania.

  2. Imię osoby to "Jeff". Czy otrzymamy "Hello, Jeff!"odpowiedź? To cztery linie kodu testowego.

  3. Imię osoby jest pustym ciągiem. Jakiej wydajności oczekujemy? Jaka jest rzeczywista wydajność? Pytanie poboczne: czy spełnia wymagania funkcjonalne? Oznacza to kolejne cztery wiersze kodu dla testu jednostkowego.

  4. Imię osoby jest wystarczająco krótkie, aby napisać, ale jest zbyt długie, aby można je było połączyć z "Hello, "wykrzyknikiem. Co się dzieje?

Wymaga to dużo kodu testowego. Co więcej, najbardziej elementarne fragmenty kodu często wymagają kodu instalacyjnego, który inicjuje obiekty potrzebne do testowania kodu, co często prowadzi również do napisania kodów pośredniczących i próbnych itp.

Jeśli stosunek jest bardzo duży, w takim przypadku możesz sprawdzić kilka rzeczy:

  • Czy w testach występuje duplikacja kodu? Fakt, że jest to kod testowy, nie oznacza, że ​​kod powinien być duplikowany (kopiowany-wklejany) między podobnymi testami: takie kopiowanie utrudni utrzymanie tych testów.

  • Czy istnieją zbędne testy? Zasadniczo, jeśli usuniesz test jednostkowy, zasięg gałęzi powinien się zmniejszyć. Jeśli nie, może to oznaczać, że test nie jest potrzebny, ponieważ ścieżki są już objęte innymi testami.

  • Czy testujesz tylko kod, który powinieneś przetestować? Nie oczekuje się, że przetestujesz podstawowe biblioteki bibliotek zewnętrznych, ale wyłącznie kod samego projektu.

Dzięki testom dymu, testom systemowym i integracyjnym, testom funkcjonalnym i akceptacyjnym oraz testom obciążeniowym i obciążeniowym dodajesz jeszcze więcej kodu testowego, więc posiadanie czterech lub pięciu LOC testów dla każdego LOC rzeczywistego kodu nie jest czymś, o co powinieneś się martwić.

Uwaga na temat TDD

Jeśli martwisz się czasem, jaki zajmuje testowanie kodu, być może robisz to źle, to znaczy najpierw kod, a później testujesz. W takim przypadku TDD może pomóc, zachęcając cię do pracy w iteracjach 15-45 sekund, przełączając między kodem a testami. Według zwolenników TDD przyspiesza proces programowania, zmniejszając zarówno liczbę testów, które musisz wykonać, a co ważniejsze, ilość kodu biznesowego do napisania, a zwłaszcza przepisania na potrzeby testów.


¹ Niech n będzie maksymalną długością łańcucha . Możemy wywoływać SayHelloi przekazywać przez referencję ciąg o długości n - 1, który powinien działać dobrze. Teraz na Console.WriteLineetapie formatowanie powinno kończyć się ciągiem o długości n + 8, co spowoduje wyjątek. Być może ze względu na ograniczenia pamięci nawet ciąg znaków zawierający n / 2 znaków prowadzi do wyjątku. Pytanie, które należy zadać, brzmi: czy ten czwarty test jest testem jednostkowym (wygląda jak jeden, ale może mieć znacznie większy wpływ pod względem zasobów w porównaniu do średnich testów jednostkowych) i czy testuje rzeczywisty kod lub podstawową strukturę.

Arseni Mourzenko
źródło
5
Nie zapominaj, że osoba może mieć również null. stackoverflow.com/questions/4456438/…
psatek
1
@JacobRaihle Zakładam, że @MainMa oznacza wartość personNamepasuje do a string, ale wartość personNameplus połączone wartości przepełnia się string.
woz
@JacobRaihle: Zredagowałem moją odpowiedź, aby wyjaśnić ten punkt. Zobacz przypis.
Arseni Mourzenko
4
As a rule of thumb, if you remove a unit test, the branch coverage should decrease.Jeśli napiszę wszystkie cztery testy, o których wspomniałeś powyżej, a następnie usunę trzeci test, czy zasięg się zmniejszy?
Vivek
3
„wystarczająco długo” → „za długo” (w punkcie 4)?
Paŭlo Ebermann
59

Uważam, że ważne jest rozróżnienie dwóch rodzajów strategii testowych: testowanie jednostkowe i testowanie integracji / akceptacji.

Chociaż w niektórych przypadkach konieczne jest testowanie jednostkowe, często jest ono beznadziejnie przepełnione. Sytuację pogarszają bezsensowne wskaźniki wymuszane na programistach, takie jak „100% pokrycia”. http://www.rbcs-us.com/documents/Why-Most-Unit-Testing-is-Waste.pdf przedstawia przekonujący argument. Rozważ następujące problemy z agresywnym testowaniem jednostkowym:

  • Mnóstwo bezcelowych testów, które nie dokumentują wartości biznesowej, ale po prostu istnieją, aby zbliżyć się do tego 100% zasięgu. Tam, gdzie pracuję, musimy pisać testy jednostkowe dla fabryk, które nie robią nic poza tworzeniem nowych instancji klas. Nie dodaje żadnej wartości. Lub te długie metody .equals () generowane przez Eclipse - nie ma potrzeby ich testowania.
  • Aby ułatwić testowanie, programiści podzielą złożone algorytmy na mniejsze testowalne jednostki. Brzmi jak wygrana, prawda? Nie, jeśli musisz mieć otwarte 12 klas, aby podążać wspólną ścieżką kodu. W takich przypadkach testowanie jednostkowe może faktycznie zmniejszyć czytelność kodu. Innym problemem jest to, że jeśli posiekasz swój kod na zbyt małe części, powstanie mnóstwo klas (lub kawałków kodu), które wydają się nie mieć uzasadnienia poza byciem podzbiorem innego fragmentu kodu.
  • Refaktoryzacja kodu wysoce coveraged może być trudne, ponieważ trzeba także zachować mnóstwo testów jednostkowych, które zależą od jego pracy właśnie tak . Sytuację pogarszają behawioralne testy jednostkowe, w których część testu weryfikuje również interakcję współpracowników klasy (zwykle wyśmiewanych).

Z drugiej strony testowanie integracji / akceptacji jest niezwykle ważną częścią jakości oprogramowania, i z mojego doświadczenia powinieneś poświęcić znaczną ilość czasu na ich poprawne działanie.

Wiele sklepów upijało pomoc kool TDD, ale jak pokazuje powyższy link, wiele badań pokazuje, że jego korzyść jest niejednoznaczna.

firtydank
źródło
10
+1 za punkt refaktoryzacji. Pracowałem nad starszym produktem, który był łatany i klugowany przez ponad dekadę. Próba ustalenia zależności dla konkretnej metody może zająć większą część dnia, a następnie próba wymyślenia, jak się z nich kpić, może potrwać jeszcze dłużej. Nie było niczym niezwykłym, że pięcioliniowa zmiana wymagała ponad 200 linii kodu testowego i zajęła większą część tygodnia.
TMN
3
To. W odpowiedzi MainMa test 4 nie powinien być wykonywany (poza kontekstem akademickim), ponieważ zastanów się, jak to by się stało w praktyce ... jeśli imię osoby jest zbliżone do maksymalnego rozmiaru łańcucha, coś poszło nie tak. Nie testuj, w większości przypadków nie ma ścieżki kodu do jej wykrycia. Odpowiednią odpowiedzią jest umożliwienie ramce wyrzucenia bazowego wyjątku z pamięci, ponieważ taki jest rzeczywisty problem.
Móż
3
Dopinguję cię, dopóki „nie musisz testować tych długich .equals()metod generowanych przez Eclipse”. Napisałem uprząż testową dla equals()i compareTo() github.com/GlenKPeterson/TestUtils Brakowało prawie każdej implementacji, którą kiedykolwiek testowałem. Jak używać zbiory razie equals()i hashCode()nie współpracować poprawnie i efektywnie? Jeszcze raz wiwatuję za resztę twojej odpowiedzi i głosowałem za nią. Przyznaję nawet, że niektóre automatycznie generowane metody equals () mogą nie wymagać testowania, ale miałem tak wiele błędów ze słabymi implementacjami, że denerwuję się.
GlenPeterson
1
@GlenPeterson Zgadzam się. Mój kolega napisał w tym celu EqualsVerifier. Zobacz github.com/jqno/equalsverifier
Tohnmeister
@ Ᶎσᶎ nie, wciąż musisz przetestować niedopuszczalne dane wejściowe, w ten sposób ludzie znajdują exploity bezpieczeństwa.
gbjbaanb,
11

Nie można uogólniać.

Jeśli muszę zaimplementować formułę lub algorytm z fizycznego renderowania, to może być tak, że spędzam 10 godzin na paranoicznych testach jednostkowych, ponieważ wiem, że najmniejszy błąd lub niedokładność może prowadzić do błędów, które są prawie niemożliwe do zdiagnozowania, kilka miesięcy później .

Jeśli chcę tylko logicznie pogrupować niektóre wiersze kodu i nadać mu nazwę, używaną tylko w zakresie plików, mogę go w ogóle nie przetestować (jeśli nalegasz na napisanie testów dla każdej funkcji, bez wyjątku, programiści mogą się wycofać do pisania jak najmniejszej liczby funkcji).

fresnel
źródło
To naprawdę cenny punkt widzenia. Pytanie wymaga więcej kontekstu, aby można było uzyskać pełną odpowiedź.
CLF
3

Tak, to normalne, jeśli mówisz o TDDing. Po przeprowadzeniu automatycznych testów zabezpieczasz pożądane zachowanie kodu. Kiedy piszesz testy najpierw, określasz, czy istniejący kod ma już pożądane zachowanie.

To znaczy że:

  • Jeśli napiszesz test, który się nie powiedzie, poprawienie kodu za pomocą najprostszej rzeczy, która działa, jest krótsze niż napisanie testu.
  • Jeśli napiszesz test, który przejdzie pomyślnie, nie masz dodatkowego kodu do napisania, efektywnie spędzając więcej czasu na pisaniu testu niż kod.

(Nie uwzględnia to refaktoryzacji kodu, która ma na celu poświęcenie mniej czasu na pisanie kolejnego kodu. Jest zrównoważona przez refaktoryzację testów, która ma na celu poświęcenie mniej czasu na pisanie kolejnych testów.)

Tak, jeśli mówisz o pisaniu testów po fakcie, będziesz spędzać więcej czasu:

  • Określenie pożądanego zachowania.
  • Określenie sposobów testowania pożądanego zachowania.
  • Wypełnianie zależności kodu, aby móc pisać testy.
  • Poprawianie kodu dla testów, które się nie powiodły.

Niż wydasz na pisanie kodu.

Tak, jest to oczekiwany środek.

Laurent LA RIZZA
źródło
3

Uważam, że jest to najważniejsza część.

Testowanie jednostkowe nie zawsze polega na „sprawdzeniu, czy działa poprawnie”, ale na uczeniu się. Gdy przetestujesz coś wystarczająco, staje się ono „zakodowane” na stałe w mózgu i ostatecznie skracasz czas testów jednostkowych i możesz znaleźć pisanie całych klas i metod bez testowania czegokolwiek, dopóki nie skończysz.

Dlatego jedna z pozostałych odpowiedzi na tej stronie wspomina, że ​​na „kursie” zrobili 90% testów, ponieważ wszyscy musieli nauczyć się ograniczeń swojego celu.

Testy jednostkowe to nie tylko bardzo cenne wykorzystanie twojego czasu, ponieważ dosłownie poprawia twoje umiejętności, jest to dobry sposób na ponowne przejrzenie własnego kodu i znalezienie logicznej pomyłki.

Jesse
źródło
2

Może być dla wielu osób, ale to zależy.

Jeśli najpierw piszesz testy (TDD), być może czas nakładania się testu na siebie nakłada się na pisanie kodu. Rozważać:

  • Określanie wyników i danych wejściowych (parametrów)
  • Konwencje nazewnictwa
  • Struktura - gdzie umieścić rzeczy.
  • Proste, stare myślenie

Pisząc testy po napisaniu kodu, możesz odkryć, że Twój kod nie jest łatwy do przetestowania, więc pisanie testów jest trudniejsze / trwa dłużej.

Większość programistów pisze kod dużo dłużej niż testy, więc spodziewałbym się, że większość z nich nie będzie tak płynna. Dodatkowo możesz dodać czas potrzebny na zrozumienie i wykorzystanie swojego środowiska testowego.

Myślę, że musimy zmienić sposób myślenia o tym, ile czasu zajmuje kodowanie i w jaki sposób przeprowadzane są testy jednostkowe. Nigdy nie patrz na to w perspektywie krótkoterminowej i nigdy nie porównuj całkowitego czasu na dostarczenie konkretnej funkcji, ponieważ musisz wziąć pod uwagę nie tylko to, czy piszesz lepszy / mniej błędny kod, ale także kod, który jest łatwiejszy do zmiany i wciąż go czyni lepiej / mniej buggy.

W pewnym momencie wszyscy jesteśmy w stanie pisać tak dobry kod, więc niektóre z narzędzi i technik mogą zaoferować tylko tyle w ulepszaniu naszych umiejętności. To nie tak, że mógłbym zbudować dom, gdybym tylko miał laserową piłę.

JeffO
źródło
2

Czy to normalne, że spędzam tyle, jeśli nie więcej, czasu na pisaniu testów niż na prawdziwym kodzie?

Tak to jest. Z pewnymi zastrzeżeniami.

Po pierwsze, jest to „normalne” w tym sensie, że większość dużych sklepów działa w ten sposób, więc nawet jeśli ten sposób był całkowicie błędny i głupi, to fakt, że większość dużych sklepów działa w ten sposób, czyni go „normalnym”.

Nie chcę przez to sugerować, że testowanie jest złe. Pracowałem w środowiskach bez testów oraz w środowiskach z testami obsesyjno-kompulsyjnymi i wciąż mogę powiedzieć, że nawet testy obsesyjno-kompulsywne były lepsze niż żadne testy.

I jeszcze nie robię TDD (kto wie, może w przyszłości), ale wykonuję zdecydowaną większość moich cykli debugowania i edycji poprzez uruchamianie testów, a nie rzeczywistej aplikacji, więc oczywiście dużo pracuję na moich testach, aby w jak największym stopniu uniknąć konieczności uruchamiania właściwej aplikacji.

Należy jednak pamiętać, że nadmierne testowanie wiąże się z niebezpieczeństwami, a zwłaszcza z poświęcaniem czasu na utrzymanie testów. (Piszę tę odpowiedź przede wszystkim w celu wyraźnego zaznaczenia tego).

We wstępie do The Art of Unit Testing Roya Osherove'a (Manning, 2009) autor przyznaje, że brał udział w projekcie, który nie został w dużej mierze spowodowany ogromnym obciążeniem programistycznym narzuconym przez źle zaprojektowane testy jednostkowe, które musiały być utrzymywane przez cały czas czas trwania wysiłku rozwojowego. Jeśli więc spędzasz zbyt dużo czasu na robieniu nic poza utrzymywaniem testów, nie musi to oznaczać, że jesteś na dobrej drodze, ponieważ jest to „normalne”. Twój wysiłek rozwojowy mógł wejść w niezdrowy tryb, w którym radykalne przemyślenie metodologii testowania może być konieczne w celu uratowania projektu.

Mike Nakis
źródło
0

Czy to normalne, że spędzam tyle, jeśli nie więcej, czasu na pisaniu testów niż na prawdziwym kodzie?

  • Tak do pisania testów jednostkowych (Testuj moduł osobno), jeśli kod jest silnie sprzężony (starsza wersja , niewystarczająca separacja problemów , brak wstrzyknięcia zależności , nie opracowano TDD )
  • Tak w przypadku pisania testów integracji / akceptacji, jeśli testowana logika jest osiągalna tylko za pomocą kodu GUI
  • Nie do pisania testów integracyjnych / akceptacyjnych, o ile kod GUI i logika biznesowa są oddzielone (test nie wymaga interakcji z GUI)
  • Nie do pisania testów jednostkowych, jeśli istnieje osobna obawa, wstrzykiwanie zależności, opracowano kod testdriven (tdd)
k3b
źródło