Jak głębokie są twoje testy jednostkowe?

88

W przypadku TDD odkryłem, że przygotowanie testów zajmuje trochę czasu i będąc naturalnie leniwym, zawsze chcę napisać jak najmniej kodu. Wydaje mi się, że pierwszą rzeczą, jaką robię, jest sprawdzenie, czy mój konstruktor ustawił wszystkie właściwości, ale czy to przesada?

Moje pytanie brzmi: na jakim poziomie szczegółowości piszesz testy jednostkowe?

..i czy jest przypadek testowania za dużo?

Johnno Nolan
źródło

Odpowiedzi:

221

Otrzymuję wynagrodzenie za działający kod, a nie za testy, więc moją filozofią jest testowanie jak najmniej, aby osiągnąć dany poziom pewności (podejrzewam, że ten poziom pewności jest wysoki w porównaniu ze standardami branżowymi, ale to może być po prostu pycha) . Jeśli zazwyczaj nie popełniam jakiegoś błędu (jak ustawienie niewłaściwych zmiennych w konstruktorze), nie testuję tego. Mam tendencję do nadawania sensu błędom testowym, więc jestem bardzo ostrożny, gdy mam logikę ze skomplikowanymi warunkami. Pisząc w zespole, modyfikuję swoją strategię, aby dokładnie testować kod, który wspólnie mylimy.

Różni ludzie będą mieli różne strategie testowania oparte na tej filozofii, ale wydaje mi się to rozsądne, biorąc pod uwagę niedojrzały stan zrozumienia, jak testy najlepiej pasują do wewnętrznej pętli kodowania. Za dziesięć lub dwadzieścia lat prawdopodobnie będziemy mieć bardziej uniwersalną teorię dotyczącą tego, które testy napisać, których nie napisać i jak odróżnić. W międzyczasie wydaje się, że trzeba eksperymentować.

Kent Beck
źródło
40
Świat nie myśli, że Kent Beck to powiedział! Jest mnóstwo programistów, którzy sumiennie dążą do 100% pokrycia, ponieważ myślą, że właśnie to zrobiłby Kent Beck! Powiedziałem wielu, że w swojej książce XP powiedziałeś, że nie zawsze religijnie trzymasz się Test First. Ale ja też jestem zaskoczony.
Charlie Flowers
6
Właściwie nie zgadzam się, ponieważ kod, który tworzy programista, nie jest jego własnym, a przy następnym sprincie ktoś inny zmieni go i popełnia błędy, o których „wiesz, że nie”. Również TDD myślisz najpierw o testach. Więc jeśli robisz TDD zakładając przetestowanie części kodu, robisz to źle
Ricardo Rodrigues
2
Nie interesuje mnie relacja. Bardzo mnie interesuje, jak często pan Beck wprowadza kod, który nie został napisany w odpowiedzi na niezaliczony test.
sheldonh
1
@RicardoRodrigues, nie możesz pisać testów obejmujących kod, który inni ludzie napiszą później. To ich odpowiedzialność.
Kief,
2
Nie o tym napisałem, przeczytaj uważnie; Napisałem, że jeśli piszesz testy, które obejmują tylko część TWOJEGO kodu, pozostawiając odkryte części, w których „wiedziałeś, że nie popełniasz błędów”, a te części się zmieniają i nie masz odpowiednich testów, masz problem właśnie tam, i to wcale nie jest TDD.
Ricardo Rodrigues,
20

Napisz testy jednostkowe dla rzeczy, które spodziewasz się zepsuć, i dla przypadków skrajnych. Następnie przypadki testowe powinny być dodawane w miarę pojawiania się zgłoszeń błędów - przed napisaniem poprawki błędu. Deweloper może wtedy mieć pewność, że:

  1. Błąd został naprawiony;
  2. Błąd nie pojawi się ponownie.

Zgodnie z załączonym komentarzem - wydaje mi się, że takie podejście do pisania testów jednostkowych mogłoby powodować problemy, gdyby z czasem wykryto wiele błędów w danej klasie. Jest to prawdopodobnie pomocne, gdy dyskrecja jest pomocna - dodawanie testów jednostkowych tylko dla błędów, które prawdopodobnie wystąpią ponownie lub gdy ich ponowne wystąpienie spowodowałoby poważne problemy. Odkryłem, że miara testów integracyjnych w testach jednostkowych może być pomocna w tych scenariuszach - testowanie kodu na wyższych ścieżkach kodu może obejmować ścieżki kodowania niżej.

Dominic Rodger
źródło
Przy ilości błędów, które piszę, może to stać się anty-wzorcem. Przy setkach testów kodu, w którym coś się zepsuło, może to oznaczać, że testy staną się nieczytelne, a kiedy nadejdzie czas na przepisanie tych testów, może to stać się narzutem.
Johnno Nolan,
@JohnNolan: Czy czytelność testów jest taka ważna? IMHO tak nie jest, przynajmniej w przypadku tych testów regresji specyficznych dla błędów. Jeśli często przepisujesz testy, możesz testować je na zbyt niskim poziomie - najlepiej, by twoje interfejsy pozostały względnie stabilne, nawet jeśli twoje implementacje się zmieniają, i powinieneś testować na poziomie interfejsu (chociaż zdaję sobie sprawę, że rzeczywisty świat często nie jest w ten sposób ...: - /) Jeśli twoje interfejsy zmienią się w poważny sposób, wolałbym wyrzucić większość lub wszystkie te testy specyficzne dla błędów, zamiast je przepisać.
j_random_hacker
@j_random_hacker Tak, czytelność jest oczywiście ważna. Testy są formą dokumentacji i są równie ważne jak kod produkcyjny. Zgadzam się, że odrzucanie testów na większe zmiany jest dobrą rzeczą (tm) i że testy należy wykonywać na poziomie interfejsu.
Johnno Nolan
19

Wszystko powinno być tak proste, jak to tylko możliwe, ale nie prostsze. - A. Einstein

Jedną z najbardziej niezrozumianych rzeczy w TDD jest pierwsze słowo w nim. Test. Dlatego pojawił się BDD. Ponieważ ludzie tak naprawdę nie rozumieli, że pierwsze D było ważne, a mianowicie Driven. Wszyscy mamy tendencję do myślenia trochę za dużo o testowaniu, a trochę za mało o kierowaniu projektowaniem. Wydaje mi się, że jest to niejasna odpowiedź na twoje pytanie, ale prawdopodobnie powinieneś rozważyć, jak sterować kodem, zamiast tego, co faktycznie testujesz; to jest coś, w czym może Ci pomóc narzędzie Pokrycie. Projekt jest znacznie większym i bardziej problematycznym zagadnieniem.

kitofr
źródło
Tak, to jest niejasne ... Czy to oznacza, że ​​skoro konstruktor nie jest częścią zachowania, nie powinniśmy go testować. Ale powinienem testować MyClass.DoSomething ()?
Johnno Nolan,
Cóż, zależy od: P ... test konstrukcji jest często dobrym początkiem podczas testowania starszego kodu. Ale prawdopodobnie (w większości przypadków) zostawiłbym test konstrukcji, kiedy zaczynałbym projektować coś od zera.
kitofr
To napędzany rozwój, a nie napędzany projekt. To znaczy, uzyskaj działającą podstawę, napisz testy, aby zweryfikować funkcjonalność, idź naprzód z rozwojem. Prawie zawsze piszę testy tuż przed tym, jak po raz pierwszy uwzględniam kod.
Evan Plaice
Powiedziałbym, że ostatnie D, Design, to słowo, o którym ludzie zapominają, przez co tracą koncentrację. W projektowaniu opartym na testach piszesz kod w odpowiedzi na niepowodzenie testów. Jeśli projektujesz oparty na testach, ile nieprzetestowanego kodu otrzymasz?
sheldonh
15

Do tych, którzy proponują testowanie „wszystkiego”: zdajcie sobie sprawę, że „pełne testowanie” takiej metody int square(int x)wymaga około 4 miliardów przypadków testowych w popularnych językach i typowych środowiskach.

W rzeczywistości, to nawet gorzej: metoda void setX(int newX)jest również zobowiązany nie zmieniać wartości wszelkich innych członków oprócz x- czy badania, które obj.y, obj.zitp wszystko pozostaje bez zmian po wywołaniu obj.setX(42);?

Testowanie podzbioru „wszystkiego” jest praktyczne. Gdy to zaakceptujesz, łatwiej będzie rozważyć rezygnację z testowania niewiarygodnie podstawowego zachowania. Każdy programista ma rozkład prawdopodobieństwa lokalizacji błędów; inteligentnym podejściem jest skupienie energii na testowaniu regionów, w których szacuje się, że prawdopodobieństwo błędu jest wysokie.

j_random_hacker
źródło
9

Klasyczna odpowiedź brzmi: „przetestuj wszystko, co mogłoby się zepsuć”. Interpretuję to jako oznaczające, że testowanie seterów i metod pobierających, które nie robią nic poza set lub get, jest prawdopodobnie zbyt dużym testowaniem, nie ma potrzeby poświęcania czasu. Jeśli twoje IDE nie napisze ich dla ciebie, możesz równie dobrze.

Jeśli konstruktor nie ustawiając właściwości może później doprowadzić do błędów, testowanie, czy są ustawione, nie jest przesadą.

Dennis S.
źródło
tak i jest to powiązanie dla klasy z wieloma właściwościami i wieloma konstruktorami.
Johnno Nolan,
Im bardziej trywialny jest problem (jak zapomnienie o inicjalizacji elementu do zera), tym więcej czasu zajmie jego debugowanie.
Lew
5

Piszę testy obejmujące założenia zajęć, które napiszę. Testy wymuszają spełnienie wymagań. Zasadniczo, jeśli na przykład x nigdy nie może wynosić 3, upewnię się, że istnieje test, który obejmuje to wymaganie.

Niezmiennie, jeśli nie napiszę testu obejmującego stan, pojawi się on później podczas testów „ludzkich”. Na pewno wtedy napiszę, ale wolałbym je wcześnie złapać. Myślę, że chodzi o to, że testowanie jest żmudne (być może), ale konieczne. Piszę wystarczająco dużo testów, aby były kompletne, ale nic więcej.

itsmatt
źródło
5

Część problemu z pomijaniem prostych testów teraz polega na tym, że w przyszłości refaktoryzacja może bardzo skomplikować tę prostą właściwość z dużą ilością logiki. Myślę, że najlepszym pomysłem jest użycie Testów do weryfikacji wymagań dla modułu. Jeśli zdasz X, powinieneś odzyskać Y, to właśnie chcesz przetestować. Następnie, gdy zmienisz kod później, możesz sprawdzić, czy X daje Y, i możesz dodać test dla A daje B, gdy to wymaganie zostanie dodane później.

Zauważyłem, że czas, który spędzam podczas początkowego pisania testów, opłaca się przy pierwszej lub drugiej naprawie błędu. Możliwość pobrania kodu, którego nie oglądałeś od 3 miesięcy i upewnienia się, że poprawka obejmuje wszystkie przypadki, a „prawdopodobnie” niczego nie psuje, jest niezwykle cenna. Przekonasz się również, że testy jednostkowe pomogą w segregowaniu błędów znacznie wykraczających poza ślad stosu itp. Obserwowanie, jak poszczególne elementy aplikacji działają i zawodzą, daje ogromny wgląd w to, dlaczego działają lub zawodzą jako całość.

Matt
źródło
4

W większości przypadków powiedziałbym, że jeśli istnieje logika, przetestuj ją. Obejmuje to konstruktory i właściwości, zwłaszcza gdy we właściwości ustawiono więcej niż jedną rzecz.

Jeśli chodzi o zbyt wiele testów, jest to dyskusyjne. Niektórzy twierdzą, że wszystko powinno być testowane pod kątem solidności, inni twierdzą, że dla skutecznego testowania należy testować tylko rzeczy, które mogą się zepsuć (tj. Logikę).

Skłoniłbym się bardziej do drugiego obozu, tylko z własnego doświadczenia, ale gdyby ktoś zdecydował się przetestować wszystko, nie powiedziałbym, że to za dużo ... może trochę przesada dla mnie, ale nie za dużo dla nich.

Więc nie - powiedziałbym, że nie ma czegoś takiego jak „zbyt dużo” testów w ogólnym sensie, tylko dla jednostek.

Smażyć
źródło
3

Programowanie sterowane testami oznacza, że ​​po przejściu wszystkich testów przestajesz kodować.

Jeśli nie masz testu dla właściwości, to dlaczego miałbyś go wdrożyć? Jeśli nie przetestujesz / nie zdefiniujesz oczekiwanego zachowania w przypadku „nielegalnej” cesji, co powinna zrobić nieruchomość?

Dlatego jestem całkowicie za testowaniem każdego zachowania, które powinna wykazywać klasa. W tym właściwości „prymitywne”.

Aby ułatwić to testowanie, stworzyłem prosty NUnit, TestFixturektóry zapewnia punkty rozszerzeń do ustawiania / pobierania wartości i pobiera listy prawidłowych i nieprawidłowych wartości oraz ma pojedynczy test, aby sprawdzić, czy właściwość działa poprawnie. Testowanie pojedynczej właściwości mogłoby wyglądać następująco:

[TestFixture]
public class Test_MyObject_SomeProperty : PropertyTest<int>
{

    private MyObject obj = null;

    public override void SetUp() { obj = new MyObject(); }
    public override void TearDown() { obj = null; }

    public override int Get() { return obj.SomeProperty; }
    public override Set(int value) { obj.SomeProperty = value; }

    public override IEnumerable<int> SomeValidValues() { return new List() { 1,3,5,7 }; }
    public override IEnumerable<int> SomeInvalidValues() { return new List() { 2,4,6 }; }

}

Używając lambd i atrybutów, można to nawet napisać bardziej zwięźle. Rozumiem, że MBUnit ma nawet natywne wsparcie dla takich rzeczy. Chodzi jednak o to, że powyższy kod oddaje intencję właściwości.

PS: Prawdopodobnie PropertyTest powinien mieć również sposób sprawdzenia, czy inne właściwości obiektu nie uległy zmianie. Hmm ... wracając do deski kreślarskiej.

David Schmitt
źródło
Poszedłem na prezentację na mbUnit. Wygląda świetnie.
Johnno Nolan
Ale David, pozwól, że cię zapytam: czy byłeś zaskoczony powyższą odpowiedzią Kenta Becka? Czy jego odpowiedź sprawia, że ​​zastanawiasz się, czy powinieneś ponownie przemyśleć swoje podejście? Oczywiście nie dlatego, że ktoś ma „odpowiedzi z góry”. Ale Kent jest najpierw uważany za jednego z głównych zwolenników testów. Grosik za twoje myśli!
Charlie Flowers
@Charly: Odpowiedź Kenta jest bardzo pragmatyczna. Pracuję „tylko” nad projektem, w którym będę integrował kod z różnych źródeł i chciałbym zapewnić bardzo wysoki poziom pewności.
David Schmitt,
To powiedziawszy, staram się mieć testy, które są prostsze niż testowany kod, a ten poziom szczegółowości może być tego warty tylko w testach integracyjnych, w których spotykają się wszystkie generatory, moduły, reguły biznesowe i walidatory.
David Schmitt
1

Wykonuję testy jednostkowe, aby osiągnąć maksymalne możliwe pokrycie. Jeśli nie mogę dotrzeć do jakiegoś kodu, dokonuję refaktoryzacji, aż pokrycie będzie maksymalnie pełne

Po ukończeniu oślepiającego testu pisania, zwykle piszę jeden przypadek testowy odtwarzający każdy błąd

Jestem przyzwyczajony do oddzielania testów kodu od testowania integracji. Podczas testów integracyjnych (które są również testami jednostkowymi, ale na grupach komponentów, więc nie dokładnie do czego służą testy jednostkowe) będę testować, czy wymagania zostały poprawnie zaimplementowane.

Lorenzo Boccaccia
źródło
1

Więc im bardziej kieruję swoim programowaniem pisząc testy, tym mniej martwię się o stopień szczegółowości testów. Patrząc wstecz, wydaje mi się, że robię najprostszą możliwą rzecz, aby osiągnąć swój cel, jakim jest walidacja zachowania . Oznacza to, że generuję warstwę pewności, że mój kod robi to, o co proszę, jednak nie jest to uważane za absolutną gwarancję, że mój kod jest wolny od błędów. Uważam, że właściwą równowagą jest przetestowanie standardowego zachowania i być może jednego lub dwóch przypadków skrajnych, a następnie przejście do następnej części mojego projektu.

Zgadzam się, że to nie obejmie wszystkich błędów i używam innych tradycyjnych metod testowania, aby je wychwycić.

Johnno Nolan
źródło
0

Generalnie zaczynam od małych, z wejściami i wyjściami, o których wiem, że muszą działać. Następnie, kiedy naprawiam błędy, dodaję więcej testów, aby upewnić się, że rzeczy, które naprawiłem, zostały przetestowane. Jest organiczny i dobrze mi pasuje.

Czy możesz przetestować za dużo? Prawdopodobnie, ale prawdopodobnie lepiej jest ogólnie zachować ostrożność, chociaż będzie to zależeć od tego, jak krytyczna jest Twoja aplikacja.

Tim Sullivan
źródło
0

Myślę, że musisz przetestować wszystko w swoim „rdzeniu” logiki biznesowej. Getter i Setter również, ponieważ mogą akceptować wartość ujemną lub wartość null, której możesz nie chcieć zaakceptować. Jeśli masz czas (zawsze zależy to od swojego szefa), dobrze jest przetestować inną logikę biznesową i cały kontroler, który wywołuje te obiekty (powoli przechodzisz od testu jednostkowego do testu integracji).

Patrick Desjardins
źródło
0

Nie wykonuję testów jednostkowych prostych metod ustawiających / pobierających, które nie mają skutków ubocznych. Ale wykonuję testy jednostkowe każdej innej metody publicznej. Próbuję tworzyć testy dla wszystkich warunków brzegowych w moich algorytmach i sprawdzam pokrycie moich testów jednostkowych.

To dużo pracy, ale myślę, że warto. Wolałbym pisać kod (nawet testować kod) niż przechodzić przez kod w debugerze. Wydaje mi się, że cykl tworzenia kodu, wdrażania i debugowania jest bardzo czasochłonny, a im bardziej wyczerpujące są testy jednostkowe, które zintegrowałem z moją kompilacją, tym mniej czasu spędzam na przechodzeniu przez cykl tworzenia kodu, wdrażania i debugowania.

Nie powiedziałeś, dlaczego również kodujesz architekturę. Ale w przypadku Javy używam Maven 2 , JUnit , DbUnit , Cobertura i EasyMock .

Brian Matthews
źródło
Nie powiedziałem, które to pytanie jest dość niezależne od języka.
Johnno Nolan
Testowanie jednostkowe w TDD nie tylko obejmuje Cię podczas pisania kodu, ale także chroni przed osobą, która dziedziczy Twój kod, a następnie uważa, że ​​ma sens formatowanie wartości wewnątrz metody pobierającej!
Paxic
0

Im więcej o tym czytam, tym bardziej wydaje mi się, że niektóre testy jednostkowe są jak niektóre wzorce: Zapach niewystarczających języków.

Kiedy musisz sprawdzić, czy twój trywialny sposób pobierający faktycznie zwraca właściwą wartość, dzieje się tak, ponieważ możesz wymieszać nazwę metody pobierającej i nazwę zmiennej składowej. Wpisz „attr_reader: name” ruby, a to już się nie może zdarzyć. Po prostu niemożliwe w Javie.

Jeśli twój getter kiedykolwiek stanie się nietrywialny, nadal możesz dodać do niego test.


źródło
Zgadzam się, że testowanie gettera jest trywialne. Jednak mogę być na tyle głupi, że zapomnę ustawić go w konstruktorze. Dlatego potrzebny jest test. Moje myśli się zmieniły, odkąd zadałem pytanie. Zobacz moją odpowiedź stackoverflow.com/questions/153234/how-deep-are-your-unit-tests/…
Johnno Nolan
1
Właściwie twierdzę, że w pewnym sensie testy jednostkowe jako całość są zapachem problemu językowego. Języki obsługujące umowy (warunki przed / po metodach), takie jak Eiffel, nadal wymagają niektórych testów jednostkowych, ale potrzebują ich mniej. W praktyce nawet proste kontrakty bardzo ułatwiają zlokalizowanie błędów: kiedy kontrakt metody zrywa się, błąd jest zwykle w tej metodzie.
Damien Pollet
@Damien: Być może testy jednostkowe i kontrakty to tak naprawdę to samo w przebraniu? Chodzi mi o to, że język, który „obsługuje” kontrakty w zasadzie po prostu ułatwia pisanie fragmentów kodu - testów - które są (opcjonalnie) wykonywane przed i po innych fragmentach kodu, prawda? Jeśli gramatyka jest dość prosta, język, który natywnie nie obsługuje umów, można łatwo rozszerzyć, aby je obsługiwał, pisząc preprocesor, prawda? A może są rzeczy, których jedno podejście (kontrakty lub testy jednostkowe) może zrobić, a drugie nie?
j_random_hacker,
0

Przetestuj kod źródłowy, który Cię niepokoi.

Nie jest przydatne do testowania fragmentów kodu, w przypadku których jesteś bardzo pewny, o ile nie popełnisz w nim błędów.

Przetestuj poprawki błędów, aby był to pierwszy i ostatni raz, kiedy naprawiasz błąd.

Testuj, aby uzyskać pewność co do niejasnych fragmentów kodu, aby stworzyć wiedzę.

Przetestuj przed ciężką i średnią refaktoryzacją, aby nie uszkodzić istniejących funkcji.

zamek 1971
źródło
0

Ta odpowiedź służy raczej określeniu, ile testów jednostkowych należy użyć dla danej metody, o której wiesz, że chcesz przeprowadzić test jednostkowy ze względu na jej krytyczność / znaczenie. Używając techniki Basis Path Testing firmy McCabe, możesz wykonać następujące czynności, aby ilościowo uzyskać większą pewność pokrycia kodu niż proste „pokrycie instrukcji” lub „pokrycie gałęzi”:

  1. Określ wartość złożoności cyklomatycznej metody, którą chcesz przetestować jednostkowo (na przykład Visual Studio 2010 Ultimate może obliczyć to za Ciebie za pomocą narzędzi do analizy statycznej; w przeciwnym razie możesz obliczyć ją ręcznie za pomocą metody flowgraph - http://users.csc. calpoly.edu/~jdalbey/206/Lectures/BasisPathTutorial/index.html )
  2. Wypisz podstawowy zestaw niezależnych ścieżek, które przepływają przez twoją metodę - zobacz link powyżej, aby zobaczyć przykład wykresu przepływu
  3. Przygotuj testy jednostkowe dla każdej niezależnej ścieżki bazowej określonej w kroku 2
JD
źródło
Zamierzasz to zrobić dla każdej metody? Poważnie?
Kristopher Johnson,