Rodzaje testów jednostkowych opartych na użyteczności

13

Z wartościowego punktu widzenia w mojej praktyce widzę dwie grupy testów jednostkowych:

  1. Testy, które testują nietrywialną logikę. Napisanie ich (przed wdrożeniem lub po nim) ujawnia pewne problemy / potencjalne błędy i pomaga zachować pewność w przypadku zmiany logiki w przyszłości.
  2. Testy testujące bardzo trywialną logikę. Testy te bardziej przypominają kod dokumentu (zwykle z próbą) niż testowanie. Proces obsługi tych testów nie polega na tym, że „zmieniono logikę, test stał się czerwony - dzięki Bogu napisałem ten test”, ale „zmieniono jakiś trywialny kod, test stał się fałszywie ujemny - muszę utrzymać (przepisać) test bez żadnego zysku” . Przez większość czasu testy te nie są warte utrzymywania (z wyjątkiem powodów religijnych). I zgodnie z moim doświadczeniem w wielu systemach testy te stanowią około 80% wszystkich testów.

Próbuję dowiedzieć się, co myślą inni faceci na temat rozdziału testów jednostkowych według wartości i jak to odpowiada mojej separacji. Ale to, co przede wszystkim widzę, to albo propaganda TDD w pełnym wymiarze godzin, albo propaganda testów są bezużyteczni, wystarczy napisać kod. Interesuje mnie coś pośrodku. Mile widziane są twoje przemyślenia lub odniesienia do artykułów / artykułów / książek.

SiberianGuy
źródło
3
Testy jednostkowe sprawdzam pod kątem znanych (specyficznych) błędów - które kiedyś przeszły przez oryginalny zestaw testów jednostkowych - jako osobną grupę, której zadaniem jest zapobieganie błędom regresji.
Konrad Morawski
6
Ten drugi rodzaj testów to coś, co postrzegam jako rodzaj „tarcia zmiany”. Nie pomijaj ich przydatności. Zmiana nawet drobiazgów kodu ma tendencję do wywoływania efektów falowania w całej bazie kodu, a wprowadzenie tego rodzaju tarcia stanowi przeszkodę dla programistów, dlatego zmieniają tylko rzeczy, które naprawdę tego potrzebują, zamiast opierać się na kapryśnych lub osobistych preferencjach.
Telastyn
3
@Telastyn - Wszystko w twoim komentarzu wydaje mi się całkowicie szalone. Kto celowo utrudniłby zmianę kodu? Po co zniechęcać programistów do zmiany kodu według własnego uznania - nie ufasz im? Czy są złymi programistami?
Benjamin Hodgson,
2
W każdym razie, jeśli zmiana kodu ma tendencję do „efektu falowania”, to twój kod ma problem z projektem - w takim przypadku deweloperów należy zachęcać do refaktoryzacji tak daleko, jak to możliwe. Kruche testy aktywnie zniechęcają do refaktoryzacji (test kończy się niepowodzeniem; kogo można się zastanowić, czy ten test był jednym z 80% testów, które tak naprawdę nic nie robią? Po prostu znajdziesz inny, bardziej skomplikowany sposób, aby to zrobić). Ale wydaje ci się, że postrzegasz to jako pożądaną cechę ... W ogóle tego nie rozumiem.
Benjamin Hodgson,
2
W każdym razie OP może uznać ten post na blogu od twórcy Railsów za interesujący. Aby znacznie uprościć jego punkt widzenia, prawdopodobnie powinieneś spróbować wyrzucić te 80% testów.
Benjamin Hodgson,

Odpowiedzi:

14

Myślę, że to naturalne, że w testach jednostkowych napotyka się podział. Istnieje wiele różnych opinii na temat tego, jak zrobić to właściwie i oczywiście wszystkie inne opinie są z natury błędne . Ostatnio jest sporo artykułów na temat DrDobbs, które badają ten problem, do którego odsyłam na końcu mojej odpowiedzi.

Pierwszym problemem, jaki widzę w testach, jest to, że łatwo je pomylić. W mojej klasie C ++ w college'u byliśmy narażeni na testy jednostkowe w pierwszym i drugim semestrze. W żadnym z semestrów nic nie wiedzieliśmy o programowaniu - staraliśmy się poznać podstawy programowania przez C ++. Teraz wyobraź sobie, że mówisz uczniom: „O, hej, napisałeś mały roczny kalkulator podatkowy! Teraz napisz kilka testów jednostkowych, aby upewnić się, że działa poprawnie”. Wyniki powinny być oczywiste - wszystkie były okropne, w tym moje próby.

Gdy przyznasz, że nie lubisz pisać testów jednostkowych i chcesz się poprawić, wkrótce staniesz przed modnymi stylami testowania lub różnymi metodami. Testując metodologie, odnoszę się do praktyk takich jak test-first lub do tego, co robi Andrew Binstock z DrDobbs, czyli do pisania testów obok kodu. Oba mają swoje zalety i wady, a ja nie chcę wchodzić w żadne subiektywne szczegóły, ponieważ spowoduje to wojnę z płomieniami. Jeśli nie masz wątpliwości co do tego, która metodologia programowania jest lepsza, być może styl testowania wystarczy. Czy powinieneś używać TDD, BDD, testów opartych na właściwościach? JUnit ma zaawansowane koncepcje zwane teoriami, które zacierają granicę między TDD a testowaniem opartym na właściwościach. Którego użyć, kiedy?

tl; dr Łatwo popełnisz błąd w testowaniu, jest on niezwykle opiniotwórczy i nie sądzę, aby jakakolwiek jedna metodologia testowania była z natury lepsza, o ile są one stosowane rzetelnie i profesjonalnie w kontekście, w którym są odpowiednie. Ponadto testowanie jest moim zdaniem rozszerzenie do twierdzeń lub testów poczytalności, które służyło zapewnieniu szybkiego i bezproblemowego podejścia do rozwoju, które jest teraz o wiele, wiele łatwiejsze.

Dla subiektywnej opinii wolę pisać „fazy” testów, z powodu braku lepszego wyrażenia. Piszę testy jednostkowe, które testują klasy w izolacji, używając w razie potrzeby próbnych. Prawdopodobnie zostaną one wykonane przy użyciu JUnit lub czegoś podobnego. Następnie piszę testy integracyjne lub akceptacyjne, które są uruchamiane osobno i zwykle tylko kilka razy dziennie. To są twoje nietrywialne przypadki użycia. Zwykle używam BDD, ponieważ miło jest wyrażać funkcje w języku naturalnym, czego JUnit nie może łatwo zapewnić.

Wreszcie zasoby. Przedstawią one sprzeczne opinie skupione głównie na testach jednostkowych w różnych językach i różnych ramach. Powinny one przedstawiać podział na ideologię i metodologię, pozwalając jednocześnie na podejmowanie własnych decyzji, o ile nie manipulowałem już zbytnio twoimi opiniami :)

[1] The Corruption of Agile autorstwa Andrew Binstocka

[2] Odpowiedź na odpowiedzi z poprzedniego artykułu

[3] Odpowiedź na zepsucie Agile przez wujka Boba

[4] Odpowiedź na Corruption of Agile Roba Myersa

[5] Po co zawracać sobie głowę testowaniem ogórków?

[6] Cuking to źle

[7] Krok od narzędzi

[8] Komentarz do „Liczb rzymskich Kata z komentarzem”

[9] Cyfry rzymskie Kata z komentarzem

IAE
źródło
1
Jedną z moich przyjaznych uwag jest to, że jeśli piszesz test w celu przetestowania funkcji rocznego kalkulatora podatkowego, to nie piszesz testu jednostkowego. To test integracyjny. Twój kalkulator powinien być podzielony na dość proste jednostki wykonawcze, a następnie testy jednostkowe testują te jednostki. Jeśli jedna z tych jednostek przestanie działać poprawnie (test zaczyna się nie powieść), to jest to jak wybicie części ściany fundamentowej i trzeba naprawić kod (ogólnie nie test). Albo to, albo zidentyfikowałeś trochę kodu, który nie jest już potrzebny i powinien zostać odrzucony.
Craig
1
@Craig: Dokładnie! To właśnie miałem na myśli, nie wiedząc, jak napisać odpowiednie testy. Jako student college'u poborca ​​podatków był jedną dużą klasą napisaną bez właściwego zrozumienia SOLID. Masz całkowitą rację, sądząc, że jest to bardziej test integracji niż cokolwiek innego, ale był to dla nas nieznany termin. Profesor wystawił nas na testy „jednostkowe”.
IAE
5

Uważam, że ważne jest posiadanie testów obu typów i stosowanie ich w stosownych przypadkach.

Jak powiedziałeś, istnieją dwie skrajności i szczerze mówiąc, nie zgadzam się z żadną z nich.

Kluczem jest to, że testy jednostkowe muszą obejmować zasady i wymagania biznesowe . Jeśli istnieje wymóg, że system musi śledzić wiek osoby, napisz „trywialne” testy, aby upewnić się, że wiek jest nieujemną liczbą całkowitą. Testujesz domenę danych wymaganych przez system: choć trywialny, ma wartość, ponieważ wymusza parametry systemu .

Podobnie w przypadku bardziej złożonych testów muszą one przynosić wartość. Jasne, możesz napisać test, który potwierdzi coś, co nie jest wymogiem, ale powinno być egzekwowane gdzieś w wieży z kości słoniowej, ale jest to czas, który lepiej spędzić na pisaniu testów, które potwierdzają wymagania, za które klient ci płaci. Na przykład, dlaczego napisać test, który potwierdzi kod, może poradzić sobie ze strumieniem wejściowym, który przekroczy limit czasu, gdy jedyne strumienie pochodzą z plików lokalnych, a nie z sieci?

Mocno wierzę w testowanie jednostkowe i używam TDD tam, gdzie ma to sens. Testy jednostkowe z pewnością przynoszą wartość w postaci podwyższonej jakości i zachowania „szybkiego niepowodzenia” przy zmianie kodu. Należy jednak pamiętać o starej zasadzie 80/20 . W pewnym momencie osiągniesz malejące zwroty podczas pisania testów i musisz przejść do bardziej produktywnej pracy, nawet jeśli istnieje pewna wymierna wartość z pisania większej liczby testów.


źródło
Pisanie testu w celu upewnienia się, że system śledzi wiek danej osoby, nie jest testem jednostkowym, IMO. To test integracyjny. Test jednostkowy przetestowałby ogólną jednostkę wykonawczą (inaczej „procedurę”), która, powiedzmy, oblicza wartość wieku z, powiedzmy, daty bazowej i przesunięcia w dowolnych jednostkach (dniach, tygodniach itp.). Chodzi mi o to, że kawałek kodu nie powinien mieć żadnych dziwnych zależności od reszty systemu. PO PROSTU oblicza wiek na podstawie kilku wartości wejściowych, aw takim przypadku test jednostkowy może potwierdzić prawidłowe zachowanie, które prawdopodobnie spowoduje wyjątek, jeśli przesunięcie spowoduje ujemny wiek.
Craig,
Nie miałem na myśli żadnych obliczeń. Jeśli model przechowuje fragment danych, może sprawdzić, czy dane należą do właściwej domeny. W tym przypadku domena jest zbiorem nieujemnych liczb całkowitych. Obliczenia powinny odbywać się w kontrolerze (w MVC), aw tym przykładzie obliczenie wieku byłoby osobnym testem.
4

Oto moje zdanie: wszystkie testy mają koszty:

  • początkowy czas i wysiłek:
    • zastanów się, co przetestować i jak to przetestować
    • zaimplementuj test i upewnij się, że testuje to, co powinien
  • trwający serwis
    • upewniając się, że test nadal wykonuje to, co powinien, ponieważ kod ewoluuje naturalnie
  • uruchomienie testu
    • czas egzekucji
    • analizowanie wyników

Zamierzamy również, aby wszystkie testy zapewniały korzyści (i z mojego doświadczenia wynika, że ​​prawie wszystkie testy zapewniają korzyści):

  • specyfikacja
  • zaznacz skrzynie narożne
  • zapobiegać regresji
  • automatyczna weryfikacja
  • przykłady użycia API
  • kwantyfikacja określonych właściwości (czas, przestrzeń)

Łatwo więc zauważyć, że jeśli napiszesz wiele testów, prawdopodobnie będą one miały jakąś wartość. To się komplikuje, gdy zaczynasz porównywać tę wartość (której, nawiasem mówiąc, możesz nie wiedzieć z góry - jeśli wyrzucisz kod, testy regresji tracą swoją wartość) do kosztu.

Teraz twój czas i wysiłek są ograniczone. Chcesz robić rzeczy, które zapewniają największe korzyści przy jak najmniejszych kosztach. I myślę, że jest to bardzo trudna rzecz, zwłaszcza dlatego, że może wymagać wiedzy, której nie ma się lub byłoby drogie.

I to jest prawdziwy rozziew między tymi różnymi podejściami. Uważam, że wszyscy zidentyfikowali strategie testowania, które są korzystne. Jednak każda strategia generalnie ma inne koszty i korzyści. Ponadto koszty i korzyści każdej strategii będą prawdopodobnie w dużym stopniu zależne od specyfiki projektu, dziedziny i zespołu. Innymi słowy, może istnieć wiele najlepszych odpowiedzi.

W niektórych przypadkach wypompowywanie kodu bez testów może zapewnić najlepsze korzyści / koszty. W innych przypadkach dokładniejszy zestaw testów może być lepszy. W jeszcze innych przypadkach poprawa projektu może być najlepszym rozwiązaniem.


źródło
2

Co jest jednostka testy, naprawdę? I czy w grze jest tak duża dychotomia?

Pracujemy w dziedzinie, w której czytanie dosłownie jednego bitu poza końcem bufora może całkowicie spowodować awarię programu lub spowodować, że da on całkowicie niedokładny wynik, lub jak dowodzi niedawny błąd TLS „HeartBleed”, rozłożyć rzekomo bezpieczny system na cały system otwarte bez przedstawienia żadnych bezpośrednich dowodów wady.

Niemożliwe jest wyeliminowanie całej złożoności tych systemów. Ale naszym zadaniem jest, w możliwym zakresie, zminimalizowanie tej złożoności i zarządzanie nią.

Czy test jednostkowy jest testem, który potwierdza na przykład, że rezerwacja została pomyślnie wysłana w trzech różnych systemach, tworzony jest wpis do dziennika i wysyłane jest potwierdzenie e-mailem?

Powiem nie . To test integracyjny . A te zdecydowanie mają swoje miejsce, ale są też innym tematem.

Test integracji działa w celu potwierdzenia ogólnej funkcji całej „funkcji”. Ale kod stojący za tą funkcją powinien zostać podzielony na proste, sprawdzalne elementy składowe, zwane także „jednostkami”.

Test jednostkowy powinien więc mieć bardzo ograniczony zakres.

Co oznacza, że ​​kod testowany przez test jednostkowy powinien mieć bardzo ograniczony zakres.

Co dodatkowo oznacza, że ​​jednym z filarów dobrego projektu jest rozbicie złożonego problemu na mniejsze, jednozadaniowe części (w możliwym zakresie), które można przetestować we względnej izolacji od siebie.

W rezultacie otrzymujesz system wykonany z niezawodnych komponentów fundamentowych i wiesz, czy którakolwiek z podstawowych jednostek kodu się psuje, ponieważ napisałeś proste, małe testy o ograniczonym zakresie, aby dokładnie to powiedzieć.

W wielu przypadkach prawdopodobnie powinieneś mieć wiele testów na jednostkę. Same testy powinny być proste, testując jedno i tylko jedno zachowanie w możliwym zakresie.

Pojęcie „testu jednostkowego” testującego nietrywialną, skomplikowaną logikę jest, jak sądzę, trochę oksymoronem.

Jeśli więc nastąpił taki celowy podział projektu, to jak na świecie test jednostkowy mógłby nagle zacząć generować fałszywe alarmy, chyba że podstawowa funkcja testowanej jednostki kodu uległa zmianie? A jeśli tak się stanie, lepiej uwierzyć, że w grze występują pewne nieoczywiste efekty tętnienia. Twój zepsuty test, ten, który wydaje się dawać fałszywie dodatni, tak naprawdę ostrzega cię, że jakaś zmiana przerwała szerszy krąg zależności w bazie kodu i należy go zbadać i naprawić.

Niektóre z tych jednostek (wiele z nich) mogą wymagać przetestowania przy użyciu fałszywych obiektów, ale to nie znaczy, że musisz pisać bardziej złożone lub skomplikowane testy.

Wracając do mojego contrived przykładzie systemu rezerwacji, naprawdę nie można wyłączyć wysyłanie żądań do bazy danych rezerwacji na żywo lub usług osób trzecich (lub nawet „dev” instancji nim) przed każdym jednostka testowego kodu.

Używasz więc makiet, które przedstawiają tę samą umowę interfejsu. Testy mogą następnie zweryfikować zachowanie stosunkowo niewielkiego, deterministycznego fragmentu kodu. Zielony na całej planszy następnie mówi, że bloki, które składają się na twoją podstawę, nie są zepsute.

Ale logika samych testów jednostkowych pozostaje tak prosta, jak to możliwe.

Craig
źródło
1

To oczywiście tylko mój sprzeciw, ale spędziłem kilka ostatnich miesięcy na nauce programowania funkcjonalnego w fsharp (pochodzącym z języka C #), uświadomiłem sobie kilka rzeczy.

Jak stwierdził PO, istnieją zazwyczaj 2 rodzaje „testów jednostkowych”, które widzimy każdego dnia. Testy obejmujące metody wejścia i wyjścia metody, które są na ogół najcenniejsze, ale trudne do wykonania dla 80% systemu, w którym mniej chodzi o „algorytmy”, a więcej o „abstrakcje”.

Innym typem jest testowanie interaktywności abstrakcji, zwykle polegające na kpinie. Moim zdaniem testy te są przede wszystkim konieczne ze względu na projekt twojej aplikacji. Ommiting ich, a ty ryzykujesz dziwnymi błędami i kodem spagetti, ponieważ ludzie nie myślą właściwie o swoim projekcie, chyba że zostaną zmuszeni do wykonania testów w pierwszej kolejności (a nawet wtedy, zwykle to psują). Problemem jest nie tyle metodologia testowania, co podstawowa konstrukcja systemu. Większość systemów zbudowanych z językami imperatywnymi lub OO ma wrodzoną zależność od „skutków ubocznych”, czyli „Rób to, ale nic mi nie mów”. Kiedy polegasz na skutku ubocznym, musisz go przetestować, ponieważ wymaganie biznesowe lub operacja jest zwykle jego częścią.

Kiedy projektujesz swój system w bardziej funkcjonalny sposób, w którym unikasz budowania zależności od skutków ubocznych i unikasz zmian stanu / śledzenia poprzez niezmienność, pozwala ci to skoncentrować się bardziej na testach „ins in out”, które wyraźnie testują więcej akcji , a tym bardziej, jak się tam dostać. Zdziwisz się, co może dać niezmienność w zakresie znacznie prostszych rozwiązań tych samych problemów, a kiedy nie będziesz już zależny od „efektów ubocznych”, możesz robić rzeczy takie jak programowanie równoległe i asynchroniczne bez prawie dodatkowych kosztów.

Odkąd zacząłem pisać w Fsharp, do niczego nie potrzebowałem frameworku, a nawet całkowicie porzuciłem swoją zależność od kontenera IOC. Moje testy są oparte na potrzebach biznesowych i wartości, a nie na ciężkich warstwach abstrakcji zwykle potrzebnych do uzyskania kompozycji w programowaniu imperatywnym.

Mitchell Lee
źródło