Czy powinniśmy przetestować wszystkie nasze metody?

61

Więc dzisiaj rozmawiałem z kolegą z zespołu na temat testów jednostkowych. Wszystko zaczęło się, gdy zapytał mnie „hej, gdzie są testy dla tej klasy, widzę tylko jeden?”. Cała klasa była menedżerem (lub usługą, jeśli wolisz tak ją nazywać) i prawie wszystkie metody po prostu delegowały rzeczy do DAO, więc było to podobne do:

SomeClass getSomething(parameters) {
    return myDao.findSomethingBySomething(parameters);
}

Rodzaj płyty kotłowej bez logiki (lub przynajmniej nie uważam takiej prostej delegacji za logikę), ale przydatny płytkę kotłową w większości przypadków (separacja warstw itp.). I mieliśmy dość długą dyskusję, czy powinienem to przetestować jednostkowo (myślę, że warto wspomnieć, że w pełni przetestowałem DAO). Jego głównymi argumentami jest to, że nie był to TDD (oczywiście) i że ktoś może chcieć zobaczyć test, aby sprawdzić, co robi ta metoda (nie wiem, jak to może być bardziej oczywiste) lub że w przyszłości ktoś może chcieć zmienić implementacja i dodaj do niej nową (lub bardziej jak „dowolną”) logikę (w takim przypadku chyba ktoś powinien po prostu przetestować tę logikę ).

To jednak zmusiło mnie do myślenia. Czy powinniśmy dążyć do jak najwyższego odsetka pokrycia testowego? A może jest to po prostu sztuka dla samej sztuki? Po prostu nie widzę żadnego powodu do testowania takich rzeczy jak:

  • pobierające i ustawiające (chyba że mają w sobie logikę)
  • kod „płyty kotłowej”

Oczywiście test na taką metodę (z próbami) zająłby mi mniej niż minutę, ale wydaje mi się, że to wciąż zmarnowany czas i milisekunda dłużej dla każdego CI.

Czy istnieją jakieś racjonalne / nie „łatwopalne” powody, dla których należy testować każdą (lub jak najwięcej) linię kodu?

Zenzen
źródło
2
Nadal zastanawiam się nad tym pytaniem, ale oto mowa o kimś, kto zdecydował, że odpowiedź brzmi „nie”. Ian Cooper: TDD, gdzie to wszystko poszło nie tak Podsumowując tę ​​wspaniałą rozmowę, powinieneś przetestować na zewnątrz i przetestować nowe zachowania, a nie nowe metody.
Daniel Kaplan
To naprawdę świetna rozmowa, trzeba zobaczyć, przemawiająca rozmowa dla wielu ludzi, uwielbiam to. Ale myślę, że odpowiedź brzmi „nie”. To „tak, ale pośrednio”. Ian Cooper mówi o architekturze heksagonalnej i testowaniu cech / zachowań kpiących / kipiących porty. W tym przypadku są to porty DAO, a ten „menedżer / usługa” został przetestowany nie z indywidualnym testem jednostkowym tylko dla tej klasy, ale z „testem jednostkowym” (jednostka w definicji Iana Coopera, z którą całkowicie się zgadzam), która testuje niektóre funkcje w Twojej domenie korzystającej z tego menedżera / usługi.
AlfredoCasado
Będzie to w pewnym stopniu zależeć od twojego systemu, jeśli tworzysz system z certyfikatem bezpieczeństwa od średniego do wysokiego, będziesz musiał objąć wszystkie metody bez względu na banalność
jk.

Odpowiedzi:

48

Kieruję się zasadą Kent Kent Beck:

Przetestuj wszystko, co może się zepsuć.

Oczywiście jest to do pewnego stopnia subiektywne. Dla mnie trywialne gettery / settery i one-linery takie jak twoje powyżej zwykle nie są tego warte. Ale z drugiej strony spędzam większość czasu na pisaniu testów jednostkowych starszego kodu, tylko marzę o fajnym projekcie TDD od podstaw ... W takich projektach reguły są inne. W przypadku starszego kodu głównym celem jest objęcie jak największej liczby obszarów przy jak najmniejszym wysiłku, więc testy jednostkowe są zwykle na wyższym poziomie i są bardziej złożone, bardziej jak testy integracyjne, jeśli ktoś jest pedantyczny w zakresie terminologii. A kiedy walczysz o zwiększenie zasięgu kodu z 0% lub po prostu udało Ci się go podbić o ponad 25%, moduły pobierające i ustawiające testy jednostkowe są najmniejszymi zmartwieniami.

OTOH w projekcie TDD typu greenfield, może być bardziej rzeczowym pisanie testów nawet dla takich metod. Zwłaszcza, że ​​napisałeś już test, zanim zaczniesz zastanawiać się „czy ta jedna linia jest warta dedykowanego testu?”. Przynajmniej te testy są trywialne do napisania i szybkie w uruchomieniu, więc i tak nie jest to wielka sprawa.

Péter Török
źródło
Ach, zupełnie zapomniałem tego cytatu! Myślę, że użyję go jako mojego głównego argumentu, bo szczerze mówiąc - co tu może się załamać? Nie bardzo. Jedyne, co może zepsuć, to wywołanie metody, a jeśli tak się stanie, oznacza to, że stało się coś naprawdę złego. Dzięki!
Zenzen,
5
@Zenzen: „co tu może się zepsuć? Naprawdę niewiele”. - Więc może się złamać. Tylko mała literówka. Albo ktoś doda kod. Lub miesza zależność. Naprawdę uważam, że Beck twierdziłby, że twój główny przykład kwalifikuje się jako łamliwy. Gettery i setery, tym mniej, chociaż złapałem się na błędzie kopiowania / wklejania, nawet wtedy. Prawdziwe pytanie brzmi: jeśli napisanie testu jest zbyt trywialne, dlaczego w ogóle istnieje?
pdr
1
Ilość czasu, który spędziłeś na zastanawianiu się nad tym, mógłbyś napisać test. mówię: napisz test, nie wychodź, kiedy nie napiszesz testu jako szary obszar, pojawi się więcej uszkodzonych okien.
kett_chup
1
Dodam, że moje ogólne doświadczenie jest takie, że testowanie programów pobierających i ustawiających jest nieco cenne w perspektywie długoterminowej, ale o niskim priorytecie. Powodem jest to, że ma teraz „zerową” szansę na znalezienie błędu, nie możesz zagwarantować, że inny programista nie doda czegoś w ciągu trzech miesięcy („zwykłe stwierdzenie, jeśli”), które będzie miało szansę złamania . Przeprowadzenie testu jednostkowego chroni przed tym. Jednocześnie nie jest to zbyt wysoki priorytet, ponieważ wkrótce nie znajdziesz niczego w ten sposób.
dclements
7
Ślepe testowanie wszystkiego, co mogłoby się zepsuć, nie ma sensu. Musi istnieć strategia, w której najpierw zostaną przetestowane elementy wysokiego ryzyka.
CodeART
12

Istnieje kilka rodzajów testów jednostkowych:

  • Na podstawie stanu. Działasz, a następnie potwierdzasz stan obiektu. Np. Dokonuję wpłaty. Następnie sprawdzam, czy saldo wzrosło.
  • Zwracana wartość. Działasz i dochodzisz wartości zwracanej.
  • Na podstawie interakcji. Sprawdzasz, czy Twój obiekt wywołał inny obiekt. To wydaje się być tym, co robisz w swoim przykładzie.

Gdyby najpierw napisać test, miałoby to większy sens - tak jak można by się spodziewać nazywania warstwy dostępu do danych. Test początkowo się nie powiedzie. Następnie napiszesz kod produkcyjny, aby test przeszedł pomyślnie.

Idealnie powinieneś testować kod logiczny, ale interakcje (obiekty wywołujące inne obiekty) są równie ważne. W twoim przypadku zrobiłbym to

  • Sprawdź, czy wywołałem warstwę dostępu do danych z dokładnym parametrem, który został przekazany.
  • Sprawdź, czy został wywołany tylko raz.
  • Sprawdź, czy zwracam dokładnie to, co otrzymałem od warstwy dostępu do danych. W przeciwnym razie równie dobrze mogę zwrócić wartość null.

Obecnie nie ma tam logiki, ale nie zawsze tak będzie.

Jeśli jednak masz pewność, że w tej metodzie nie będzie logiki i prawdopodobnie pozostanie taka sama, rozważę wywołanie warstwy dostępu do danych bezpośrednio od konsumenta. Zrobiłbym to tylko, jeśli reszta zespołu jest na tej samej stronie. Nie chcesz wysyłać niewłaściwej wiadomości do zespołu, mówiąc: „Cześć, można zignorować warstwę domeny, wystarczy bezpośrednio wywołać warstwę dostępu do danych”.

Skoncentrowałbym się również na testowaniu innych komponentów, gdyby istniał test integracji dla tej metody. Ale jeszcze nie widziałem firmy z solidnymi testami integracji.

Powiedziawszy to wszystko - nie będę ślepo testować wszystkiego. Ustanowiłbym gorące punkty (komponenty o dużej złożoności i wysokim ryzyku pęknięcia). Następnie skoncentrowałbym się na tych elementach. Nie ma sensu posiadanie podstawy kodu, w której 90% podstawy kodu jest dość proste i jest objęte testami jednostkowymi, podczas gdy pozostałe 10% reprezentuje podstawową logikę systemu i nie są objęte testami jednostkowymi ze względu na ich złożoność.

Wreszcie, jaka jest korzyść z przetestowania tej metody? Jakie są konsekwencje, jeśli to nie zadziała? Czy są katastroficzne? Nie staraj się uzyskać wysokiego zasięgu kodu. Pokrycie kodu powinno być wynikiem dobrego zestawu testów jednostkowych. Na przykład możesz napisać jeden test, który przejdzie drzewo i da ci 100% pokrycia tą metodą, lub możesz napisać trzy testy jednostkowe, które dadzą ci również 100% pokrycia. Różnica polega na tym, że pisząc trzy testy testujesz przypadki skrajne, a nie tylko chodzisz po drzewie.

CodeART
źródło
Dlaczego sprawdzasz, czy Twój DAL został wywołany tylko raz?
Marjan Venema
9

Oto dobry sposób, aby pomyśleć o jakości oprogramowania:

  1. sprawdzanie typu obsługuje część problemu.
  2. testy zajmą się resztą

W przypadku funkcji typu bojler i trywialnych zadań możesz polegać na sprawdzaniu typu, wykonując swoje zadanie, a dla reszty potrzebujesz przypadków testowych.

tp1
źródło
Oczywiście sprawdzanie typów działa tylko wtedy, gdy używasz określonych typów w kodzie i pracujesz w skompilowanym języku lub w inny sposób upewniasz się, że sprawdzanie analizy statycznej jest często uruchamiane, np. Jako część CI.
bdsl,
6

Moim zdaniem złożoność cykliczna jest parametrem. Jeśli metoda nie jest wystarczająco złożona (np. Pobierające i ustawiające). Testy jednostkowe nie są potrzebne. Poziom złożoności cyklicznej McCabe'a powinien być większy niż 1. Innym słowem powinno być minimum 1 instrukcja blokowa.

Fırat KÜÇÜK
źródło
Pamiętaj, że niektóre osoby pobierające lub ustawiające mają skutki uboczne (chociaż w większości przypadków jest to odradzane i uważane za złą praktykę), więc zmiana kodu źródłowego może również na to wpływać.
Andrzej Bobak,
3

Rozległy TAK z TDD (i kilkoma wyjątkami)

Kontrowersyjne w porządku, ale twierdzę, że każdemu, kto odpowie „nie” na to pytanie, brakuje podstawowej koncepcji TDD.

Dla mnie odpowiedź brzmi tak, jeśli postępujesz zgodnie z TDD. Jeśli nie, to nie jest wiarygodną odpowiedzią.

DDD w TDD

TDD jest często cytowany jako mający główne zalety.

  • Obrona
    • Zapewnienie, że kod może się zmienić, ale nie jego zachowanie .
    • Pozwala to na bardzo ważną praktykę refaktoryzacji .
    • Zyskujesz to TDD, czy nie.
  • Projekt
    • Ci określić, co należy zrobić coś, jak należy to zachowuje się przed wprowadzeniem go.
    • Często oznacza to bardziej świadome decyzje wdrożeniowe .
  • Dokumentacja
    • Zestaw testowy powinien służyć jako dokumentacja specyfikacji (wymagań).
    • Zastosowanie testów do tego celu oznacza, że ​​dokumentacja i implementacja są zawsze w spójnym stanie - zmiana na jedną oznacza zmianę na drugą. Porównaj z zachowaniem wymagań i projektu na osobnym dokumencie Word.

Oddziel odpowiedzialność od wdrożenia

Jako programiści strasznie kusi myślenie o atrybutach jako o czymś istotnym, a getters i seter o czymś w rodzaju kosztów ogólnych.

Ale atrybuty są szczegółami implementacji, podczas gdy setery i gettery są umownym interfejsem, który faktycznie powoduje, że programy działają.

O wiele ważniejsze jest przeliterowanie, że obiekt powinien:

Pozwól swoim klientom zmienić swój stan

i

Pozwól swoim klientom zapytać o jego stan

następnie w jaki sposób ten stan jest faktycznie przechowywany (dla którego atrybut jest najczęstszy, ale nie jedyny sposób).

Test taki jak

(The Painter class) should store the provided colour

jest ważny dla części dokumentacji TDD.

Pisząc test, fakt, że ostateczna implementacja jest trywialna (atrybut) i nie przynosi żadnej korzyści obronnej, nie powinien być dla ciebie nieznany.

Brak inżynierii w obie strony ...

Jednym z kluczowych problemów w świecie rozwoju systemu jest brak inżynierii w obie strony 1 - proces rozwoju systemu jest podzielony na niepowiązane podprocesy, których artefakty (dokumentacja, kod) są często niespójne.

1 Brodie, Michael L. „John Mylopoulos: szycie nasion modelowania koncepcyjnego”. Modelowanie koncepcyjne: podstawy i zastosowania. Springer Berlin Heidelberg, 2009. 1-9.

... i jak TDD to rozwiązuje

Jest to część dokumentacji TDD, która zapewnia, że ​​specyfikacje systemu i jego kodu są zawsze spójne.

Najpierw zaprojektuj, a później zaimplementuj

W TDD najpierw piszemy test akceptacji zakończony niepowodzeniem, a dopiero potem kod, który pozwala im przejść.

W BDD wyższego poziomu najpierw piszemy scenariusze, a następnie je przekazujemy.

Dlaczego warto wykluczyć seterów i getterów?

Teoretycznie jedna osoba może napisać test w TDD, a druga wdrożyć kod, który go zdaje.

Więc zadaj sobie pytanie:

Czy osoba pisząca testy dla klasy wspomina getters i setter.

Ponieważ metody pobierające i ustawiające są publicznym interfejsem dla klasy, odpowiedź brzmi oczywiście tak , lub nie będzie możliwości ustawienia ani zapytania o stan obiektu.

Oczywiście, jeśli najpierw napiszesz kod, odpowiedź może nie być tak jednoznaczna.

Wyjątki

Istnieją pewne oczywiste wyjątki od tej reguły - funkcje, które są wyraźnymi szczegółami implementacyjnymi i wyraźnie nie są częścią projektu systemu.

Na przykład metoda lokalna „B ()”:

function A() {

    // B() will be called here    

    function B() {
        ...
    }
} 

Lub funkcja prywatna square()tutaj:

class Something {
private:
    square() {...}
public:
    addAndSquare() {...}
    substractAndSquare() {...}
}

Lub dowolna inna funkcja, która nie jest częścią publicinterfejsu wymagającego pisowni w projekcie komponentu systemu.

Izhaki
źródło
1

W obliczu filozoficznego pytania powróć do wymagań dotyczących jazdy.

Czy Twoim celem jest wytwarzanie oprogramowania w miarę niezawodnego po konkurencyjnych kosztach?

A może ma na celu wytwarzanie oprogramowania o najwyższej możliwej niezawodności prawie niezależnie od kosztów?

Do pewnego stopnia dwa cele: jakość i szybkość rozwoju / koszty są wyrównane: spędzasz mniej czasu na pisaniu testów niż na naprawianiu wad.

Ale poza tym nie robią tego. Nie jest tak trudno dostać, powiedzmy, jeden zgłoszony błąd na programistę miesięcznie. Zmniejszenie tego o połowę do jednego na dwa miesiące zwalnia budżet na dzień lub dwa, a dodatkowe testy prawdopodobnie nie zmniejszą o połowę wskaźnika defektów. Nie jest to więc zwykła wygrana / wygrana; musisz to uzasadnić na podstawie kosztów wady dla klienta.

Koszt ten będzie się różnił (a jeśli chcesz być zły, ich zdolność do zwrócenia Ci tych kosztów, czy to przez rynek, czy przez pozew). Nie chcesz być zły, więc odliczasz te koszty w całości; czasami niektóre testy wciąż globalnie pogarszają świat z powodu ich istnienia.

Krótko mówiąc, jeśli spróbujesz ślepo zastosować te same standardy do wewnętrznej witryny internetowej, co oprogramowanie do lotów samolotów pasażerskich, skończysz albo z działalnością, albo w więzieniu.

soru
źródło
0

Twoja odpowiedź na to zależy od twojej filozofii (wierzysz, że to Chicago vs Londyn? Jestem pewien, że ktoś to sprawdzi). Jury jest nadal przy tym w kwestii najbardziej efektywnego czasowo podejścia (ponieważ w końcu jest to największy czynnik, który poświęca mniej czasu na poprawki).

Niektóre podejścia mówią, że testuj tylko interfejs publiczny, inne - testuj kolejność każdego wywołania funkcji w każdej funkcji. Stoczono wiele świętych wojen. Radzę wypróbować oba podejścia. Wybierz jednostkę kodu i zrób to jak X, a drugą jak Y. Po kilku miesiącach testów i integracji wróć i sprawdź, który z nich lepiej pasuje do twoich potrzeb.

zaraz
źródło
0

To trudne pytanie.

Ściśle mówiąc, powiedziałbym, że nie jest to konieczne. Lepiej jest pisać testy w stylu BDD i testy poziomu systemu, które zapewniają, że wymagania biznesowe funkcjonują zgodnie z założeniami w scenariuszach pozytywnych i negatywnych.

To powiedziawszy, jeśli twoja metoda nie jest objęta tymi przypadkami testowymi, musisz najpierw zapytać, dlaczego ona istnieje i czy jest potrzebna, lub czy w kodzie są ukryte wymagania, które nie są odzwierciedlone w twojej dokumentacji lub historiach użytkowników, które powinien być zakodowany w walizce testowej typu BDD.

Osobiście lubię utrzymywać zasięg według linii na poziomie około 85-95% i odprawy bramkowe do linii głównej, aby zapewnić, że istniejące pokrycie testem jednostkowym na linię osiągnie ten poziom dla wszystkich plików kodu i że nie zostaną odkryte żadne pliki.

Zakładając, że przestrzegane są najlepsze praktyki testowania, daje to duży zasięg, nie zmuszając deweloperów do marnowania czasu na próby uzyskania dodatkowego pokrycia trudnego do wykonania kodu lub trywialnego kodu tylko ze względu na zasięg.

Keith przynosi
źródło
-1

Problemem jest samo pytanie: nie musisz testować wszystkich „metod” ani wszystkich „klas”, których potrzebujesz, aby przetestować wszystkie funkcje swoich systemów.

Kluczowe myślenie w kategoriach cech / zachowań zamiast myślenia w kategoriach metod i klas. Oczywiście jest tutaj metoda zapewniająca obsługę jednej lub więcej funkcji, na końcu cały kod jest testowany, a przynajmniej cały kod ma znaczenie w bazie kodu.

W twoim scenariuszu prawdopodobnie ta klasa „menedżera” jest zbędna lub niepotrzebna (jak wszystkie klasy o nazwie zawierającej słowo „menedżer”), a może nie, ale wydaje się, że jest to szczegół implementacyjny, prawdopodobnie ta klasa nie zasługuje na jednostkę test, ponieważ ta klasa nie ma żadnej logiki biznesowej. Prawdopodobnie potrzebujesz tej klasy, aby jakaś funkcja działała, test tej funkcji obejmuje tę klasę, w ten sposób możesz refaktoryzować tę klasę i mieć test sprawdzający, czy rzecz, która ma znaczenie, twoje cechy, nadal działa po refaktorze.

Pomyśl o cechach / zachowaniach, a nie o klasach metod, nie mogę powtórzyć tego wystarczająco dużo razy.

AlfredoCasado
źródło
-4

To jednak zmusiło mnie do myślenia. Czy powinniśmy dążyć do jak najwyższego odsetka pokrycia testowego?

Tak, najlepiej 100%, ale niektórych rzeczy nie da się przetestować jednostkowo.

pobierające i ustawiające (chyba że mają w sobie logikę)

Getters / Setters są głupi - po prostu ich nie używaj. Zamiast tego umieść zmienną członka w sekcji publicznej.

kod „płyty kotłowej”

Pobierz wspólny kod i przetestuj go. To powinno być tak proste.

Czy istnieją jakieś racjonalne / nie „łatwopalne” powody, dla których należy testować każdą (lub jak najwięcej) linię kodu?

Nie robiąc tego, możesz przegapić kilka bardzo oczywistych błędów. Testy jednostkowe są jak bezpieczna sieć do przechwytywania określonych rodzajów błędów i należy z niej korzystać tak często, jak to możliwe.

I ostatnia rzecz: jestem przy projekcie, w którym ludzie nie chcieli tracić czasu na pisanie testów jednostkowych dla jakiegoś „prostego kodu”, ale później postanowili w ogóle nie pisać. Na końcu fragmenty kodu zamieniły się w wielką kulę błota .

BЈовић
źródło
Cóż, wyjaśnijmy jedno: nie miałem na myśli, że nie używam testów TDD / write. Wręcz przeciwnie. Wiem, że testy mogą znaleźć błąd, o którym nie myślałem, ale co tu jest do przetestowania? Po prostu uważam, że taka metoda jest jedną z metod „nie do przetestowania jednostkowego”. Jak powiedział Péter Török (cytując Kent Beck), powinieneś przetestować rzeczy, które mogą się zepsuć. Co może tu się zepsuć? Nie bardzo (w tej metodzie jest tylko prosta delegacja). MOGĘ napisać test jednostkowy, ale będzie on po prostu naśladował DAO i zapewnił, niewiele testów. Jeśli chodzi o getters / setters, niektóre ramy tego wymagają.
Zenzen,
1
Ponadto, ponieważ nie zauważyłem, że „Pobierz wspólny kod i przetestuj go. To powinno być tak proste.”. Co przez to rozumiesz? Jest to klasa usług (w warstwie usług między GUI a DAO), jest wspólna dla całej aplikacji. Nie można tak naprawdę uczynić go bardziej ogólnym (ponieważ akceptuje niektóre parametry i wywołuje określoną metodę w DAO). Jedynym powodem jest przestrzeganie warstwowej architektury aplikacji, aby GUI nie wywoływało bezpośrednio DAO.
Zenzen,
20
-1 dla „Getters / Setters są głupi - po prostu ich nie używaj. Zamiast tego umieść zmienną członka w sekcji publicznej”. - Bardzo źle. Zostało to kilkakrotnie omówione na SO . Używanie pól publicznych wszędzie jest w rzeczywistości gorsze niż używanie getterów i seterów wszędzie.
Péter Török,