Krótkie wprowadzenie do tego pytania. Używam teraz TDD, a ostatnio BDD od ponad roku. Używam technik takich jak kpina, aby bardziej efektywnie pisać testy. Ostatnio rozpocząłem osobisty projekt, aby napisać dla siebie mały program do zarządzania pieniędzmi. Ponieważ nie miałem wcześniejszego kodu, był to idealny projekt na początek z TDD. Niestety, tak bardzo nie doświadczyłem radości TDD. Zepsuło to nawet moją zabawę do tego stopnia, że zrezygnowałem z projektu.
W czym był problem? Cóż, zastosowałem podejście podobne do TDD, aby testy / wymagania ewoluowały w zakresie projektowania programu. Problem polegał na tym, że ponad połowa czasu programowania była na pisanie / testowanie refaktorów. W końcu nie chciałem już implementować żadnych funkcji, ponieważ musiałbym dokonać refaktoryzacji i napisać do wielu testów.
W pracy mam dużo starszego kodu. Tutaj piszę coraz więcej testów integracyjnych i akceptacyjnych oraz coraz mniej testów jednostkowych. Nie wydaje się to złym podejściem, ponieważ błędy są najczęściej wykrywane przez testy akceptacji i integracji.
Mój pomysł polegał na tym, że w końcu mogłem napisać więcej testów integracyjnych i akceptacyjnych niż testów jednostkowych. Jak powiedziałem do wykrywania błędów, testy jednostkowe nie są lepsze niż testy integracji / akceptacji. Testy jednostkowe są również dobre dla projektu. Ponieważ pisałem wiele z nich, moje klasy są zawsze zaprojektowane tak, aby były dobrze sprawdzalne. Ponadto podejście pozwalające testom / wymaganiom kierować projektem prowadzi w większości przypadków do lepszego projektu. Ostatnią zaletą testów jednostkowych jest to, że są one szybsze. Napisałem wystarczająco dużo testów integracyjnych, aby wiedzieć, że mogą one być prawie tak szybkie, jak testy jednostkowe.
Po przejrzeniu internetu dowiedziałem się, że istnieją bardzo podobne pomysły do moich, o których tu i tam wspomniano . Co sądzisz o tym pomyśle?
Edytować
Odpowiadając na pytania jeden przykład, w którym projekt był dobry, ale potrzebowałem ogromnego refaktoryzacji dla następnego wymagania:
Na początku były pewne wymagania do wykonania niektórych poleceń. Napisałem rozszerzalny parser poleceń - który przeanalizował polecenia z jakiegoś wiersza poleceń i nazwał poprawne w modelu. Wyniki zostały przedstawione w klasie modelu widoku:
Tu nie było nic złego. Wszystkie klasy były od siebie niezależne i mogłem łatwo dodawać nowe polecenia, pokazywać nowe dane.
Kolejnym wymaganiem było, aby każde polecenie miało własną reprezentację widoku - pewnego rodzaju podgląd wyniku polecenia. Przeprojektowałem program, aby uzyskać lepszy projekt nowego wymagania:
Było to również dobre, ponieważ teraz każde polecenie ma swój własny model widoku, a zatem własny podgląd.
Chodzi o to, że parser komend został zmieniony, aby używał parsowania komend opartego na tokenach i został pozbawiony możliwości wykonywania komend. Każde polecenie ma swój własny model widoku, a model widoku danych zna tylko bieżący model widoku poleceń, który zna dane, które należy pokazać.
W tym momencie chciałem tylko wiedzieć, czy nowy projekt nie złamał żadnych istniejących wymagań. Nie musiałem zmieniać ŻADNEGO testu akceptacyjnego. Musiałem przefakturować lub usunąć prawie KAŻDE testy jednostkowe, co było ogromnym nakładem pracy.
To, co chciałem tutaj pokazać, to powszechna sytuacja, która często zdarzała się podczas tworzenia. Nie było problemu ze starymi lub nowymi projektami, po prostu zmieniły się one naturalnie wraz z wymaganiami - jak to zrozumiałem, to jedna z zalet TDD, że projekt ewoluuje.
Wniosek
Dziękuję za wszystkie odpowiedzi i dyskusje. Podsumowując tę dyskusję, pomyślałem o podejściu, które przetestuję przy następnym projekcie.
- Przede wszystkim piszę wszystkie testy przed wdrożeniem czegoś, co zawsze robiłem.
- Dla wymagań piszę najpierw testy akceptacyjne, które testują cały program. Następnie piszę testy integracji dla komponentów, w których muszę zaimplementować wymaganie. Jeśli istnieje komponent ściśle współpracujący z innym komponentem w celu wdrożenia tego wymogu, napisałbym również kilka testów integracyjnych, w których oba komponenty są testowane razem. Wreszcie, jeśli muszę napisać algorytm lub inną klasę o wysokiej permutacji - np. Serializator - napisałbym testy jednostkowe dla tych konkretnych klas. Wszystkie pozostałe klasy nie są testowane, ale testy jednostkowe.
- W przypadku błędów proces można uprościć. Zwykle błąd jest powodowany przez jeden lub dwa komponenty. W tym przypadku napisałbym jeden test integracji dla komponentów testujących błąd. Jeśli dotyczy to algorytmu, napisałbym tylko test jednostkowy. Jeśli nie jest łatwo wykryć komponent, w którym występuje błąd, napisałbym test akceptacyjny, aby zlokalizować błąd - powinien to być wyjątek.
źródło
Odpowiedzi:
Porównuje pomarańcze i jabłka.
Testy integracyjne, testy akceptacyjne, testy jednostkowe, testy zachowania - wszystkie są testami i wszystkie pomogą ci ulepszyć twój kod, ale są też zupełnie inne.
Moim zdaniem przejrzę każdy z różnych testów i mam nadzieję wyjaśnić, dlaczego potrzebujesz ich wszystkich:
Testy integracyjne:
Po prostu przetestuj, czy różne części składowe systemu poprawnie się integrują - na przykład - być może symulujesz żądanie usługi internetowej i sprawdzasz, czy wynik wraca. Zasadniczo używałbym rzeczywistych (ish) danych statycznych i fałszywych zależności, aby zapewnić ich spójną weryfikację.
Test wstępny:
Test akceptacyjny powinien być bezpośrednio skorelowany z przypadkiem użycia biznesowego. Może być ogromny („transakcje są przesyłane poprawnie”) lub mały („filtr skutecznie filtruje listę”) - to nie ma znaczenia; ważne jest to, że powinien on być wyraźnie powiązany z konkretnym wymaganiem użytkownika. Lubię skupiać się na nich w rozwoju opartym na testach, ponieważ oznacza to, że mamy dobry podręcznik do testów historii użytkowników, które deweloper i qa mogą zweryfikować.
Testy jednostkowe:
W przypadku małych dyskretnych jednostek funkcjonalności, które mogą samodzielnie tworzyć indywidualną historię użytkownika - na przykład historia użytkownika, która mówi, że odzyskujemy wszystkich klientów, gdy uzyskujemy dostęp do określonej strony internetowej, może być testem akceptacyjnym (symulacja trafienia do sieci strony i sprawdzanie odpowiedzi), ale może również zawierać kilka testów jednostkowych (sprawdź, czy uprawnienia bezpieczeństwa są sprawdzone, sprawdź, czy połączenie z bazą danych jest poprawnie wysyłane, sprawdź, czy kod ograniczający liczbę wyników jest wykonywany poprawnie) - to wszystko są „testy jednostkowe” które nie są pełnym testem akceptacyjnym.
Testy zachowania:
Zdefiniuj przepływ aplikacji w przypadku określonych danych wejściowych. Na przykład „gdy nie można nawiązać połączenia, sprawdź, czy system spróbuje nawiązać połączenie”. Ponownie jest to mało prawdopodobne, aby był to pełny test akceptacyjny, ale nadal pozwala zweryfikować coś użytecznego.
Wszystko to, moim zdaniem, wynika z dużego doświadczenia w pisaniu testów; Nie lubię skupiać się na podejściach podręcznikowych - raczej skupiać się na tym, co daje wartość testom.
źródło
TL; DR: Tak długo, jak spełnia twoje potrzeby, tak.
Od wielu lat pracuję nad rozwojem testów opartych na testach akceptacyjnych (ATDD). Może być bardzo udany. Jest kilka rzeczy, o których należy pamiętać.
Teraz korzyści
Jak zawsze to od Ciebie zależy przeprowadzenie analizy i ustalenie, czy ta praktyka jest odpowiednia w Twojej sytuacji. W przeciwieństwie do wielu ludzi nie sądzę, aby istniała idealna odpowiednia odpowiedź. Będzie to zależeć od twoich potrzeb i wymagań.
źródło
Testy jednostkowe działają najlepiej, gdy publiczny interfejs komponentów, do których są używane, nie zmienia się zbyt często. Oznacza to, że gdy komponenty są już dobrze zaprojektowane (na przykład zgodnie z zasadami SOLID).
Tak więc wiara w dobry projekt po prostu „ewoluuje” od „rzucania” wielu testów jednostkowych na element jest błędem. TDD nie jest „nauczycielem” dobrego projektowania, może jedynie trochę pomóc w sprawdzeniu, czy pewne aspekty projektu są dobre (zwłaszcza testowalność).
Kiedy zmieniają się twoje wymagania i musisz zmienić elementy wewnętrzne komponentu, a to zepsuje 90% testów jednostkowych, więc musisz je często refaktoryzować, wtedy najprawdopodobniej projekt nie był tak dobry.
Moja rada jest więc taka: pomyśl o projekcie elementów, które stworzyłeś i jak możesz je ulepszyć zgodnie z zasadą otwartego / zamkniętego. Ideą tego drugiego jest upewnienie się, że funkcjonalność twoich komponentów może zostać później rozszerzona bez ich zmiany (a tym samym nie psując interfejsu API komponentu używanego przez twoje testy jednostkowe). Takie elementy mogą (i powinny być) objęte testami jednostkowymi, a doświadczenie nie powinno być tak bolesne, jak to opisałeś.
Jeśli nie możesz od razu wymyślić takiego projektu, testy akceptacyjne i integracyjne mogą być rzeczywiście lepszym początkiem.
EDYCJA: Czasami konstrukcja twoich komponentów może być w porządku, ale projekt testów jednostkowych może powodować problemy . Prosty przykład: chcesz przetestować metodę „MyMethod” klasy X i napisać
(załóżmy, że wartości mają jakieś znaczenie).
Załóżmy ponadto, że w kodzie produkcyjnym jest tylko jedno wywołanie
X.MyMethod
. Teraz, dla nowego wymagania, metoda „MyMethod” wymaga dodatkowego parametru (na przykład czegoś podobnegocontext
), którego nie można pominąć. Bez testów jednostkowych należałoby przekodować kod wywołujący w jednym miejscu. W przypadku testów jednostkowych należy refaktoryzować 500 miejsc.Ale przyczyną tego nie są same testy urządzenia, to tylko fakt, że to samo wezwanie do „X.MyMethod” jest powtarzane wielokrotnie, nie ściśle według zasady „Don't Repeat Yourself (DRY). Więc rozwiązanie tutaj jest umieszczenie danych testowych i powiązanych oczekiwanych wartości na liście i uruchomienie wywołań „MyMethod” w pętli (lub, jeśli narzędzie testujące obsługuje tak zwane „testy napędu danych”, aby użyć tej funkcji). liczba miejsc do zmiany w testach jednostkowych, gdy sygnatura metody zmienia się na 1 (w przeciwieństwie do 500).
W prawdziwym świecie sytuacja może być bardziej złożona, ale mam nadzieję, że masz pomysł - kiedy testy jednostkowe używają interfejsu API komponentów, dla którego nie wiesz, czy może ulec zmianie, zmniejsz liczbę połączeń do tego API do minimum.
źródło
X x= new X(); AssertTrue(x.MyMethod(12,"abc"))
przed faktyczną implementacją metody. Korzystając z wstępnego projektu, możeszclass X{ public bool MyMethod(int p, string q){/*...*/}}
najpierw napisać , a później napisać testy. W obu przypadkach podjęto tę samą decyzję projektową. Jeśli decyzja była dobra lub zła, TDD nie powie ci.Tak, oczywiście, że tak.
Rozważ to:
Zobacz ogólną różnicę ....
Problem dotyczy pokrycia kodu, jeśli możesz uzyskać pełny test całego kodu za pomocą testów integracji / akceptacji, to nie ma problemu. Twój kod jest testowany. To jest cel.
Myślę, że może być konieczne ich pomieszanie, ponieważ każdy projekt oparty na TDD będzie wymagał pewnych testów integracyjnych, aby upewnić się, że wszystkie jednostki faktycznie działają dobrze razem (wiem z doświadczenia, że w 100% sprawdzona podstawa kodu testowana jednostkowo niekoniecznie działa kiedy je wszystkie połączysz!)
Problem naprawdę sprowadza się do łatwości testowania, debugowania błędów i ich naprawiania. Niektóre osoby uważają, że ich testy jednostkowe są w tym bardzo dobre, są małe i proste, a awarie są łatwe do zauważenia, ale wadą jest to, że trzeba zreorganizować kod, aby pasował do narzędzi do testów jednostkowych, i napisać bardzo wiele z nich. Test integracyjny jest trudniejszy do napisania, aby objąć dużo kodu, i prawdopodobnie będziesz musiał użyć technik takich jak rejestrowanie w celu debugowania wszelkich awarii (choć powiedziałbym, że musisz to zrobić i tak, nie można testów jednostkowych na miejscu!).
Tak czy inaczej, nadal otrzymujesz przetestowany kod, po prostu musisz zdecydować, który mechanizm bardziej Ci odpowiada. (Chciałbym trochę wymieszać, przetestować złożone algorytmy, a zintegrować przetestować resztę).
źródło
Myślę, że to okropny pomysł.
Ponieważ testy akceptacyjne i test integracyjny dotykają szerszych części kodu w celu przetestowania określonego celu, będą one wymagały więcej refaktoryzacji w czasie, a nie mniej. Co gorsza, ponieważ obejmują one szerokie sekcje kodu, zwiększają czas, który spędzasz na poszukiwaniu przyczyny, ponieważ masz szerszy obszar do wyszukiwania.
Nie, zwykle powinieneś pisać więcej testów jednostkowych, chyba że masz dziwną aplikację, która ma 90% interfejs użytkownika lub coś innego, co jest niewygodne w testach jednostkowych. Ból, na jaki napotykasz, nie pochodzi z testów jednostkowych, ale z testowania pierwszego rozwoju. Ogólnie rzecz biorąc, powinieneś spędzać tylko 1/3 swojego czasu na większości testów pisemnych. W końcu służą ci, a nie odwrotnie.
źródło
„Wygrana” dzięki TDD polega na tym, że po napisaniu testów można je zautomatyzować. Drugą stroną jest to, że może pochłonąć znaczną część czasu programowania. To, czy faktycznie spowalnia cały proces, jest dyskusyjne. Argumentem jest to, że testy wstępne zmniejszają liczbę błędów, które należy naprawić na końcu cyklu programowania.
Tutaj pojawia się BDD, ponieważ zachowania mogą być uwzględnione w testach jednostkowych, więc proces jest z definicji mniej abstrakcyjny i bardziej namacalny.
Oczywiście, jeśli dostępny byłby nieskończony czas, zrobiłbyś tyle testów różnych odmian, ile to możliwe. Czas jest jednak ogólnie ograniczony, a ciągłe testowanie jest opłacalne tylko do pewnego stopnia.
Wszystko to prowadzi do wniosku, że testy, które zapewniają największą wartość, powinny znajdować się na początku procesu. To samo w sobie nie faworyzuje automatycznie jednego rodzaju testów nad drugim - tym bardziej, że każdy przypadek musi być brany pod uwagę pod względem merytorycznym.
Jeśli piszesz widżet wiersza poleceń do użytku osobistego, przede wszystkim zainteresują Cię testy jednostkowe. Podczas gdy serwis internetowy mówi, wymagałby znacznej ilości integracji / testów behawioralnych.
Podczas gdy większość rodzajów testów koncentruje się na czymś, co można by nazwać „linią wyścigową”, tj. Testowaniem tego, co dziś wymaga firma, testy jednostkowe są doskonałe w usuwaniu subtelnych błędów, które mogą pojawić się w późniejszych fazach rozwoju. Ponieważ jest to korzyść, której nie można łatwo zmierzyć, często jest ona pomijana.
źródło
To jest kluczowy punkt, a nie tylko „ostatnia zaleta”. Gdy projekt staje się coraz większy, testy akceptacyjne integracji stają się coraz wolniejsze. A tutaj mam na myśli tak wolno, że przestaniesz je wykonywać.
Oczywiście testy jednostkowe również stają się wolniejsze, ale wciąż są o ponad rząd wielkości szybsze. Na przykład w moim poprzednim projekcie (c ++, około 600 kLOC, 4000 testów jednostkowych i 200 testów integracyjnych) wykonanie wszystkich i ponad 15 testów integracyjnych zajęło około minuty. Aby zbudować i wykonać testy jednostkowe dla zmienianej części, zajmie to średnio mniej niż 30 sekund. Kiedy możesz to zrobić tak szybko, będziesz chciał to robić cały czas.
Żeby było jasne: nie mówię, aby nie dodawać testów integracji i akceptacji, ale wygląda na to, że TDD / BDD zrobiłeś w niewłaściwy sposób.
Tak, projektowanie z myślą o testowaniu poprawi projekt.
Cóż, gdy zmieniają się wymagania, musisz zmienić kod. Powiedziałbym, że nie ukończyłeś pracy, gdybyś nie napisał testów jednostkowych. Ale to nie znaczy, że powinieneś mieć 100% pokrycia testami jednostkowymi - to nie jest cel. Niektóre rzeczy (takie jak GUI lub dostęp do pliku, ...) nie są nawet przeznaczone do testowania jednostkowego.
Wynikiem tego jest lepsza jakość kodu i kolejna warstwa testów. Powiedziałbym, że warto.
Przeprowadziliśmy także kilka tysięcy testów akceptacyjnych, a wykonanie wszystkich zajęłoby cały tydzień.
źródło