Zadanie polegało mi na napisaniu testów jednostkowych dla istniejącej aplikacji. Po zakończeniu pierwszego pliku mam 717 linii kodu testowego na 419 linii oryginalnego kodu.
Czy ten współczynnik stanie się niemożliwy do zarządzania, gdy zwiększymy zasięg naszego kodu?
Moje rozumienie testów jednostkowych polegało na przetestowaniu każdej metody w klasie, aby upewnić się, że każda metoda działa zgodnie z oczekiwaniami. Jednak w prośbie o wycofanie mój lider techniczny zauważył, że powinienem skupić się na testowaniu na wyższym poziomie. Zasugerował przetestowanie 4-5 przypadków użycia, które są najczęściej używane w danej klasie, zamiast wyczerpującego testowania każdej funkcji.
Ufam komentarzowi mojego kierownika technicznego. Ma więcej doświadczenia niż ja i ma lepszy instynkt, jeśli chodzi o projektowanie oprogramowania. Ale w jaki sposób wieloosobowy zespół pisze testy dla tak dwuznacznego standardu; to znaczy, skąd mam poznać moich rówieśników i podzielam ten sam pomysł dotyczący „najczęstszych przypadków użycia”?
Dla mnie 100% pokrycie testem jednostkowym jest wzniosłym celem, ale nawet gdybyśmy osiągnęli tylko 50%, wiedzielibyśmy, że 100% z tych 50% zostało objętych. W przeciwnym razie pisanie testów dla części każdego pliku pozostawia dużo miejsca do oszukiwania.
źródło
Odpowiedzi:
Tak, ze 100% pokryciem napiszesz kilka niepotrzebnych testów. Niestety, jedynym niezawodnym sposobem ustalenia, które testy nie są potrzebne, jest napisanie ich wszystkich, a następnie odczekanie około 10 lat, aby zobaczyć, które z nich nigdy nie zawiodły.
Prowadzenie wielu testów zwykle nie jest problematyczne. Wiele zespołów ma zautomatyzowane testy integracyjne i systemowe oprócz 100% pokrycia testami jednostkowymi.
Nie jesteś jednak w fazie konserwacji testowej, grasz nadrabiając zaległości. O wiele lepiej jest mieć 100% swoich zajęć przy 50% pokryciu testowym niż 50% swoich zajęć przy 100% pokryciu testowym, a twoja szansa wydaje się próbować zmusić cię do odpowiedniego przydzielenia czasu. Po ustaleniu linii bazowej, następnym krokiem jest zazwyczaj naciskanie na 100% w plikach, które są zmieniane w przyszłości.
źródło
Jeśli pracowałeś na dużych bazach kodu utworzonych za pomocą Test Driven Development, już wiesz, że może istnieć coś takiego jak zbyt wiele testów jednostkowych. W niektórych przypadkach większość prac rozwojowych polega na aktualizacji testów niskiej jakości, które najlepiej wdrożyć jako kontrole niezmiennicze, wstępne i dodatkowe w odpowiednich klasach w czasie wykonywania (tj. Testowanie jako efekt uboczny testu wyższego poziomu ).
Innym problemem jest tworzenie projektów niskiej jakości, przy użyciu technik projektowania opartych na kulturze ładunkowej, które powodują rozprzestrzenianie się rzeczy do przetestowania (więcej klas, interfejsów itp.). W takim przypadku wydaje się, że obciążeniem jest aktualizacja kodu testowego, ale prawdziwym problemem jest projekt niskiej jakości.
źródło
Odpowiedzi na twoje pytania
Jasne ... Na przykład możesz mieć wiele testów, które na pierwszy rzut oka wydają się różne, ale naprawdę testują to samo (logicznie zależą od tych samych linii testowanego „ciekawego” kodu aplikacji).
Lub możesz przetestować wewnętrzne elementy swojego kodu, które nigdy nie pojawiają się na zewnątrz (tj. Nie są częścią żadnej umowy dotyczącej interfejsu), gdzie można spierać się o to, czy ma to w ogóle sens. Na przykład dokładne sformułowanie wewnętrznych komunikatów dziennika lub cokolwiek innego.
To wydaje mi się całkiem normalne. Twoje testy poświęcają wiele linii kodu na konfigurację i porzucenie na dodatek do rzeczywistych testów. Współczynnik może się poprawić lub nie. Sam jestem dość obciążony testami i często inwestuję w testy więcej czasu i lokalizacji niż w rzeczywisty kod.
Współczynnik nie uwzględnia tak wiele. Istnieją inne cechy testów, które sprawiają, że nie można nimi zarządzać. Jeśli regularnie dokonujesz refaktoryzacji całej gamy testów, wykonując raczej proste zmiany w kodzie, powinieneś dobrze przyjrzeć się przyczynom. Nie chodzi o to, ile masz linii, ale o to, jak podchodzisz do kodowania testów.
Jest to poprawne w przypadku testów „jednostkowych” w ścisłym tego słowa znaczeniu. Tutaj „jednostka” jest czymś w rodzaju metody lub klasy. Celem testów „jednostkowych” jest przetestowanie tylko jednej konkretnej jednostki kodu, a nie całego systemu. Idealnie byłoby usunąć całą resztę systemu (używając podwójnych lub whatnot).
Potem wpadłeś w pułapkę, zakładając, że ludzie rzeczywiście mieli na myśli testy jednostkowe, kiedy mówili , że testy jednostkowe. Spotkałem wielu programistów, którzy mówią „test jednostkowy”, ale mają na myśli coś zupełnie innego.
Jasne, samo skoncentrowanie się na 80% ważnego kodu również zmniejsza obciążenie ... Doceniam to, że wysoko oceniasz swojego szefa, ale nie wydaje mi się to optymalnym wyborem.
Nie wiem, co to jest „zasięg testu jednostkowego”. Zakładam, że masz na myśli „pokrycie kodu”, tzn. Że po uruchomieniu zestawu testów każda linia kodu (= 100%) została wykonana co najmniej raz.
To niezły wskaźnik, ale zdecydowanie nie najlepszy standard, na jaki można strzelać. Samo wykonanie wierszy kodu nie jest całym obrazem; nie uwzględnia to na przykład różnych ścieżek poprzez skomplikowane, zagnieżdżone gałęzie. Jest to bardziej wskaźnik, który wskazuje palcem na fragmenty kodu, które są testowane zbyt mało (oczywiście, jeśli klasa obejmuje 10% lub 5% pokrycia kodu, coś jest nie tak); z drugiej strony 100% pokrycia nie powie ci, czy przetestowałeś wystarczająco dużo, czy poprawnie.
Testy integracyjne
Denerwuje mnie to znacznie, gdy ludzie domyślnie dzisiaj mówią o testach jednostkowych . Moim zdaniem (i doświadczenie) testy jednostkowe są świetne dla bibliotek / interfejsów API; w obszarach bardziej zorientowanych na biznes (gdzie mówimy o przypadkach użycia, takich jak pytanie), niekoniecznie są one najlepszą opcją.
Dla ogólnego kodu aplikacji i dla przeciętnego biznesu (gdzie ważne jest zarabianie pieniędzy, dotrzymywanie terminów i spełnianie zadowolenia klienta, a przede wszystkim chcesz unikać błędów, które są bezpośrednio w twarz użytkownika lub mogą prowadzić do prawdziwych katastrof - nie jesteśmy mówiąc o uruchomieniu rakiety NASA), testy integracyjne lub funkcjonalne są znacznie bardziej przydatne.
Te idą w parze z rozwojem opartym na zachowaniu lub opracowaniem opartym na cechach; z definicji nie działają one z (ścisłymi) testami jednostkowymi.
Krótko mówiąc, test integracji / funkcji ćwiczy cały stos aplikacji. W aplikacji internetowej działałoby to tak, jakby przeglądarka przeglądała aplikację (i nie, oczywiście nie musi to być takie uproszczone, istnieją bardzo potężne ramy do tego celu - sprawdź http: // ogórek. Io na przykład).
Och, aby odpowiedzieć na ostatnie pytania: cały zespół ma wysoki zasięg testu, upewniając się, że nowa funkcja jest programowana dopiero po jej wdrożeniu i niepowodzeniu. I tak, to oznacza każdą funkcję. To gwarantuje Ci 100% (pozytywny) pokrycie funkcji. Z definicji gwarantuje, że funkcja aplikacji nigdy nie zniknie. Nie gwarantuje 100% pokrycia kodu (na przykład, jeśli aktywnie nie zaprogramujesz funkcji negatywnych, nie będziesz obsługiwał obsługi błędów / obsługi wyjątków).
Nie gwarantuje to aplikacji wolnej od błędów; oczywiście będziesz chciał pisać testy funkcji dla oczywistych lub bardzo niebezpiecznych sytuacji buggy, złych danych wejściowych użytkownika, hakowania (na przykład zarządzania sesjami, bezpieczeństwa itp.) itp .; ale nawet programowanie testów pozytywnych ma ogromne zalety i jest całkiem wykonalne dzięki nowoczesnym, potężnym frameworkom.
Testy funkcji / integracji oczywiście mają swoją własną puszkę robaków (np. Wydajność; testowanie redundantne frameworków stron trzecich; ponieważ zwykle nie używasz dublowania, z mojego doświadczenia też trudniej jest napisać ...), ale ja d weź aplikację przetestowaną w 100% pod kątem dodatnich funkcji w stosunku do aplikacji przetestowanej w 100% pod kątem zasięgu (nie biblioteki!)
źródło
Tak, możliwe jest przeprowadzenie zbyt wielu testów jednostkowych. Jeśli na przykład masz 100% pokrycia testami jednostkowymi i nie masz testów integracyjnych, masz wyraźny problem.
Niektóre scenariusze:
Przeprojektowujesz swoje testy do konkretnej implementacji. Następnie musisz zrezygnować z testów jednostkowych podczas refaktoryzacji, nie mówiąc już o zmianie implementacji (bardzo częsty problem podczas przeprowadzania optymalizacji wydajności).
Dobra równowaga między testami jednostkowymi a testami integracji zmniejsza ten problem bez utraty znacznego zasięgu.
Możesz mieć rozsądne pokrycie każdego zatwierdzenia 20% testów, które masz, pozostawiając pozostałe 80% na integrację lub przynajmniej osobne testy; Głównymi negatywnymi skutkami, które widzisz w tym scenariuszu, są powolne zmiany, ponieważ musisz długo czekać na wykonanie testów.
Zmieniasz zbyt wiele kodu, aby umożliwić Ci jego przetestowanie; na przykład widziałem wiele nadużyć IoC na komponentach, które nigdy nie będą wymagały modyfikacji lub przynajmniej generalizacja ich jest kosztowna i ma niski priorytet, ale ludzie poświęcają dużo czasu na generalizację i refaktoryzację, aby umożliwić ich testowanie jednostkowe .
W szczególności zgadzam się z sugestią uzyskania 50% pokrycia na 100% plików, zamiast 100% pokrycia na 50% plików; skoncentruj swoje początkowe wysiłki na najczęstszych przypadkach pozytywnych i najniebezpieczniejszych przypadkach negatywnych, nie inwestuj zbyt wiele w obsługę błędów i nietypowe ścieżki, nie dlatego, że nie są one ważne, ale dlatego, że masz ograniczony czas i nieskończony wszechświat testowy, więc musisz nadać priorytet każdej sprawie.
źródło
Pamiętaj, że każdy test ma koszt, a także korzyść. Wady obejmują:
Jeśli koszty przewyższają korzyści, lepiej nie pisać testu. Na przykład, jeśli funkcjonalność jest trudna do przetestowania, interfejs API często się zmienia, poprawność jest stosunkowo nieistotna, a szansa na znalezienie usterki w teście jest niska, prawdopodobnie lepiej nie pisać.
Jeśli chodzi o konkretny stosunek testów do kodu, jeśli kod jest wystarczająco gęsty pod względem logicznym, wówczas ten stosunek może być uzasadniony. Jednak prawdopodobnie nie warto utrzymywać tak wysokiego współczynnika w typowej aplikacji.
źródło
Tak, istnieje coś takiego jak zbyt wiele testów jednostkowych.
Chociaż testy są dobre, każdy test jednostkowy to:
Potencjalne obciążenie związane z utrzymaniem, ściśle związane z interfejsem API
Czas, który można poświęcić na coś innego
Mądrze jest dążyć do 100% pokrycia kodu, ale wcale nie oznacza to zestawu testów, z których każdy niezależnie zapewnia 100% pokrycia kodu w określonym punkcie wejścia (funkcja / metoda / wywołanie itp.).
Chociaż biorąc pod uwagę, jak trudno jest osiągnąć dobry zasięg i wyeliminować błędy, prawdą jest prawdopodobnie to, że istnieje coś takiego jak „złe testy jednostkowe” i „zbyt wiele testów jednostkowych”.
Pragmatyka dla większości kodów wskazuje:
Upewnij się, że masz 100% pokrycia punktów wejścia (wszystko jest jakoś testowane) i staraj się być blisko 100% pokrycia kodu ścieżek „bezbłędnych”.
Przetestuj wszystkie odpowiednie wartości min / maks. Lub rozmiary
Przetestuj wszystko, co uważasz za zabawny przypadek specjalny, w szczególności „nieparzyste” wartości.
Gdy znajdziesz błąd, dodaj test jednostkowy, który by go ujawnił i zastanów się, czy należy dodać podobne przypadki.
W przypadku bardziej złożonych algorytmów rozważ również:
Na przykład sprawdź algorytm sortowania z losowymi danymi wejściowymi, a sprawdzenie poprawności danych jest sortowane na końcu przez skanowanie.
Powiedziałbym, że twój lider technologiczny proponuje testy „minimalnej liczby odsłonięcia”. Oferuję „najwyższą jakość testowania jakości”, a pomiędzy nimi jest spektrum.
Być może twój senior wie, że komponent, który budujesz, zostanie osadzony w jakimś większym elemencie i jednostka zostanie bardziej dokładnie przetestowana po zintegrowaniu.
Kluczową lekcją jest dodawanie testów po wykryciu błędów. Co prowadzi mnie do najlepszej lekcji na temat opracowywania testów jednostkowych:
Skoncentruj się na jednostkach, a nie na podjednostkach. Jeśli budujesz jednostkę z podjednostek, napisz bardzo podstawowe testy dla podjednostek, aż będą one wiarygodne i uzyskaj lepszy zasięg, testując podjednostki za pomocą ich jednostek kontrolnych.
Więc jeśli piszesz kompilator i musisz napisać tablicę symboli (powiedzmy). Uruchom tabelę symboli za pomocą podstawowego testu, a następnie pracuj nad (powiedzmy) parserem deklaracji, który wypełnia tabelę. Dalsze testy dodawaj do jednostki autonomicznej tabeli symboli tylko wtedy, gdy znajdziesz w niej błędy. W przeciwnym razie zwiększ zasięg przez testy jednostkowe analizatora składni deklaracji, a później całego kompilatora.
To daje najlepszy zwrot za grosze (jednym testem całości jest testowanie wielu komponentów) i pozostawia większą pojemność na przeprojektowanie i udoskonalenie, ponieważ tylko testy „zewnętrznego” interfejsu są używane w testach, które wydają się być bardziej stabilne.
W połączeniu z warunkami wstępnymi testowania kodu debugowania, warunkami dodatkowymi, w tym niezmiennikami na wszystkich poziomach, uzyskuje się maksymalny zasięg testu od minimalnej implementacji testu.
źródło
Po pierwsze, niekoniecznie jest problemem mieć więcej linii testowych niż kodu produkcyjnego. Kod testowy jest (lub powinien być) liniowy i łatwy do zrozumienia - jego niezbędna złożoność jest bardzo, bardzo niska, niezależnie od tego, czy kod produkcyjny jest. Jeśli złożoność testów zacznie zbliżać się do złożoności kodu produkcyjnego, prawdopodobnie masz problem.
Tak, możliwe jest posiadanie zbyt wielu testów jednostkowych - prosty eksperyment myślowy pokazuje, że możesz kontynuować dodawanie testów, które nie zapewniają dodatkowej wartości, i że wszystkie te dodane testy mogą zahamować przynajmniej niektóre refaktoryzacje.
Rada, by przetestować tylko najczęstsze przypadki, jest, moim zdaniem, wadliwa. Mogą one działać jak testy dymu, aby zaoszczędzić czas na testowanie systemu, ale naprawdę cenne testy wychwytują przypadki, które są trudne do wykonania w całym systemie. Na przykład kontrolowane wstrzykiwanie błędów błędów alokacji pamięci może być wykorzystane do ćwiczenia ścieżek odzyskiwania, które w przeciwnym razie mogłyby być całkowicie nieznanej jakości. Lub przekaż zero jako wartość, o której wiesz, że zostanie wykorzystana jako dzielnik (lub liczba ujemna, która będzie zrootowana do kwadratu) i upewnij się, że nie otrzymasz nieobsługiwanego wyjątku.
Kolejne najcenniejsze testy to te, które wykorzystują ekstremalne granice lub punkty graniczne. Na przykład funkcja, która akceptuje (w oparciu o 1) miesiące w roku, powinna zostać przetestowana na 0, 1, 12 i 13, abyś wiedział, że poprawne-nieprawidłowe przejścia są na właściwym miejscu. Nadmierne testowanie wymaga również użycia 2..11 do tych testów.
Jesteś w trudnej sytuacji, ponieważ musisz pisać testy dla istniejącego kodu. Łatwiej jest zidentyfikować przypadki brzegowe podczas pisania (lub pisania) kodu.
źródło
To zrozumienie jest błędne.
Testy jednostkowe zweryfikować zachowanie na jednostki badanej .
W tym sensie jednostka niekoniecznie jest „metodą w klasie”. Podoba mi się definicja jednostki autorstwa Roy Osherove w The Art of Unit Testing :
Na tej podstawie test jednostkowy powinien zweryfikować każde pożądane zachowanie kodu. Gdzie „pożądanie” mniej więcej pochodzi z wymagań.
Ma rację, ale w inny sposób niż myśli.
Z twojego pytania rozumiem, że jesteś „oddanym testerem” w tym projekcie.
Dużym nieporozumieniem jest to, że oczekuje on od ciebie pisania testów jednostkowych (w przeciwieństwie do „testowania za pomocą frameworka testowego”). Pisanie testów ynit jest obowiązkiem programistów , a nie testerów (w idealnym świecie, wiem ...). Z drugiej strony otagowałeś to pytanie TDD, co implikuje dokładnie to.
Twoim zadaniem jako testera jest napisanie (lub ręczne wykonanie) testów modułu i / lub aplikacji. Tego rodzaju testy powinny przede wszystkim sprawdzać, czy wszystkie jednostki działają płynnie. Oznacza to, że musisz wybrać przypadki testowe, aby każda jednostka została wykonana co najmniej raz . A ta kontrola polega na tym, że się uruchamia. Rzeczywisty wynik jest mniej ważny, ponieważ może ulec zmianie wraz z przyszłymi wymaganiami.
Aby jeszcze raz podkreślić analogię zrzutu samochodu: Ile testów wykonuje się samochodem na końcu linii montażowej? Dokładnie jeden: sam musi jechać na parking ...
Chodzi o to:
Musimy zdawać sobie sprawę z różnicy między „testami jednostkowymi” a „testowaniem automatycznym przy użyciu ram testów jednostkowych”.
Testy jednostkowe są siatką bezpieczeństwa. Dają one pewność byłaby kodu w celu zmniejszenia długu technicznego lub dodać nowe zachowanie nie obawiając się przełamać już wdrożony zachowanie.
Nie potrzebujesz 100% pokrycia kodu.
Ale potrzebujesz 100% pokrycia zachowań. (Tak, zasięg kodu i zasięg zachowania w jakiś sposób korelują, ale ze względu na to nie są identyczne).
Jeśli masz mniej niż 100% pokrycia zachowań, pomyślne uruchomienie zestawu testów nic nie znaczy, ponieważ mogłeś zmienić niektóre z niesprawdzonych zachowań. Zostaniesz zauważony przez twojego klienta następnego dnia po przejściu twojego wydania do sieci ...
Wniosek
Niewiele testów jest lepszych niż brak testu. Bez wątpienia!
Ale nie ma czegoś takiego jak zbyt wiele testów jednostkowych.
Wynika to z faktu, że każdy test jednostkowy weryfikuje jedno oczekiwanie dotyczące zachowania kodów . Nie możesz napisać więcej testów jednostkowych, niż masz oczekiwania względem kodu. A dziura w szelkach bezpieczeństwa to szansa na niechcianą zmianę, która może zaszkodzić systemowi produkcyjnemu.
źródło
Absolutnie tak. Byłem SDET dla dużej firmy programistycznej. Nasz mały zespół musiał utrzymywać kod testowy, który był obsługiwany przez znacznie większy zespół. Co więcej, nasz produkt miał pewne zależności, które stale wprowadzały przełomowe zmiany, co oznacza dla nas stałą konserwację testów. Nie mieliśmy możliwości zwiększenia liczebności zespołu, więc musieliśmy wyrzucić tysiące mniej wartościowych testów, gdy zawiodły. W przeciwnym razie nigdy nie bylibyśmy w stanie nadążyć za wadami.
Zanim odrzucisz to jako zwykły problem z zarządzaniem, zastanów się, że wiele projektów w realnym świecie cierpi z powodu zmniejszonej liczby pracowników, gdy zbliżają się do statusu starszego typu. Czasami zaczyna się nawet dziać zaraz po pierwszym wydaniu.
źródło
Posiadanie większej liczby wierszy kodu testowego niż kodu produktu niekoniecznie stanowi problem, zakładając, że refaktoryzujesz kod testowy, aby wyeliminować kopiowanie-wklejanie.
Problemem są testy, które są odzwierciedleniem twojej implementacji, bez żadnego znaczenia biznesowego - na przykład testy załadowane próbnymi i pośredniczącymi kodami, a jedynie stwierdzenie, że metoda wywołuje inną metodę.
Wielkim cytatem w artykule „dlaczego większość testów jednostkowych jest marnotrawstwem” jest to, że testy jednostkowe powinny mieć „szeroką, formalną, niezależną wyrocznię poprawności i… możliwą do przypisania wartość biznesową”
źródło
Jednej rzeczy, o której nie wspomniałem, to to, że testy muszą być szybkie i łatwe, aby każdy programista mógł je uruchomić w dowolnym momencie.
Nie chcesz się zameldować w celu kontroli źródła i odczekać godzinę lub dłużej (w zależności od rozmiaru bazy kodu), zanim testy zakończą się, aby sprawdzić, czy twoja zmiana coś zepsuła - chcesz to zrobić na własny komputer przed zalogowaniem się do kontroli źródła (a przynajmniej przed wprowadzeniem zmian). Idealnie byłoby, gdybyś mógł uruchomić swoje testy za pomocą jednego skryptu lub naciśnięcia przycisku.
A kiedy uruchamiasz te testy lokalnie, chcesz, aby działały szybko - rzędu sekund. Jakikolwiek wolniejszy, a będziesz miał ochotę nie uruchomić ich wystarczająco dużo lub wcale.
Tak więc posiadanie tak wielu testów, że uruchomienie ich wszystkich zajmuje kilka minut lub kilka zbyt skomplikowanych testów, może stanowić problem.
źródło