Czy wystarczy zastosować testy akceptacyjne i integracyjne zamiast testu jednostkowego?

62

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: Pierwszy projekt

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: Drugi projekt

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.
Yggdrasil
źródło
Te pytania wydają się w większym stopniu dotyczyć problemu, dlaczego pisanie testów w ogóle. Chcę omówić, czy pisanie testów funkcjonalnych zamiast testów jednostkowych może być lepszym podejściem.
Yggdrasil
według mojej lektury odpowiedzi w zduplikowanym pytaniu dotyczą przede wszystkim tego, kiedy testy niejednostkowe mają większy sens
gnat
Ten pierwszy link sam w sobie jest duplikatem. Myślisz, że masz na myśli: programmers.stackexchange.com/questions/66480/…
Robbie Dee
Odpowiedzi z linku Robbiego Dee'a jeszcze bardziej mówią o tym, dlaczego w ogóle testować.
Yggdrasil

Odpowiedzi:

37

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.

Michael
źródło
W twojej definicji zakładam, że mam na myśli napisanie większej liczby testów zachowania (?) Niż testów jednostkowych. Test jednostkowy jest dla mnie testem, który testuje pojedynczą klasę z wyśmiewanymi wszystkimi zależnościami. Są przypadki, w których testy jednostkowe są najbardziej przydatne. To jest np. Kiedy piszę złożony algorytm. Następnie mam wiele przykładów z oczekiwanym wyjściem algorytmu. Chcę to przetestować na poziomie jednostki, ponieważ jest to faktycznie szybsze niż test zachowania. Nie widzę wartości testowania klasy na poziomie jednostki, która ma tylko rękę pełną ścieżek przez klasę, którą można łatwo przetestować za pomocą testu zachowania.
Yggdrasil
14
Osobiście uważam, że testy akceptacyjne są najważniejsze, testy zachowania są ważne podczas testowania takich rzeczy, jak komunikacja, niezawodność i błędy, a testy jednostkowe są ważne podczas testowania małych złożonych funkcji (dobrym przykładem tego byłyby algorytmy)
Michael
Nie jestem tak stanowczy w twojej terminologii. Programujemy pakiet oprogramowania. Tam jestem odpowiedzialny za edytor graficzny. Moje testy testują edytor z wyśmiewanymi usługami z reszty pakietu i wyśmiewanym interfejsem użytkownika. Jaki to byłby test?
Yggdrasil
1
Zależy od tego, co testujesz - czy testujesz funkcje biznesowe (testy akceptacyjne)? Czy testujesz integrację (testy integracyjne)? Czy testujesz, co dzieje się po kliknięciu przycisku (testy zachowania)? Czy testujesz algorytmy (testy jednostkowe)?
Michael
4
„Nie lubię skupiać się na podejściach z podręcznika - raczej skupić się na tym, co nadaje wartość testom” Och, to prawda! Pierwszym pytaniem, jakie zawsze należy zadać, jest „jaki problem rozwiązuję, robiąc to?”. A inny projekt może mieć inny problem do rozwiązania!
Laurent Bourgault-Roy
40

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ć.

  • Testy jednostkowe naprawdę pomagają w egzekwowaniu MKOl. Bez testów jednostkowych na deweloperach spoczywa obowiązek upewnienia się, że spełniają one wymagania dobrze napisanego kodu (o ile testy jednostkowe prowadzą do dobrze napisanego kodu)
  • Mogą być wolniejsze i mają fałszywe awarie, jeśli faktycznie używasz zasobów, które zwykle są wyśmiewane.
  • Test nie wskazuje konkretnego problemu, jak w przypadku testów jednostkowych. Musisz wykonać więcej badań, aby naprawić błędy testowe.

Teraz korzyści

  • Znacznie lepszy zasięg testu, obejmuje punkty integracji.
  • Zapewnia, że ​​system jako całość spełnia kryteria akceptacji, co stanowi cały punkt rozwoju oprogramowania.
  • Sprawia, że ​​duże reaktory są znacznie łatwiejsze, szybsze i tańsze.

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ń.

dietbuddha
źródło
8
Doskonały punkt Zbyt łatwo jest stać się małym kosmicznym kadetem podczas testowania i pisać setki przypadków, aby uzyskać ciepły blask satysfakcji, gdy „przechodzi” z latającymi kolorami. Mówiąc najprościej: jeśli twoje oprogramowanie nie robi tego, co musi zrobić z punktu widzenia UŻYTKOWNIKA, nie zaliczyłeś pierwszego i najważniejszego testu.
Robbie Dee
Warto wskazać konkretny problem. Jeśli mam ogromne wymagania, piszę testy akceptacyjne, które testują cały system, a następnie piszę testy, które testują podzadania niektórych elementów systemu w celu spełnienia tego wymagania. Dzięki temu mogę w większości przypadków wskazać składnik, w którym leży defekt.
Yggdrasil
2
„Testy jednostkowe pomagają w egzekwowaniu MKOl”?!? Jestem pewien, że miałeś na myśli DI zamiast IoC, ale tak czy inaczej, dlaczego ktoś miałby wymuszać stosowanie DI? Osobiście uważam, że w praktyce DI prowadzi do nie-obiektów (programowanie w stylu proceduralnym).
Rogério
Czy możesz podać swój wkład w opcję (najlepiej IMO) przeprowadzania OBIEKTYWNEJ integracji i testów jednostkowych w przeciwieństwie do argumentu przeprowadzania tylko testów integracyjnych? Twoja odpowiedź tutaj jest dobra, ale wydaje się, że te elementy wzajemnie się wykluczają, ale nie sądzę, żeby były.
starmandeluxe
@starmandeluxe W rzeczywistości nie wykluczają się wzajemnie. Raczej chodzi o wartość, którą chcesz czerpać z testowania. Testowałbym jednostkowo wszędzie tam, gdzie wartość przekraczała koszt opracowania / wsparcia pisania testów jednostkowych. dawny. Z pewnością przeprowadziłbym test jednostkowy na składanie odsetek w aplikacji finansowej.
dietbuddha
18

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

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 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ć

    var x= new X();
    Assert.AreEqual("expected value 1" x.MyMethod("value 1"));
    Assert.AreEqual("expected value 2" x.MyMethod("value 2"));
    // ...
    Assert.AreEqual("expected value 500" x.MyMethod("value 500"));

(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ś podobnego context), 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.

Doktor Brown
źródło
„Oznacza to, że komponenty są już dobrze zaprojektowane.”: Zgadzam się z tobą, ale jak można zaprojektować komponenty, jeśli napiszesz testy przed napisaniem kodu, a kod jest projektem? Przynajmniej tak zrozumiałem TDD.
Giorgio
2
@Giorgio: w rzeczywistości nie ma znaczenia, czy napiszesz testy najpierw czy później. Projektowanie oznacza podejmowanie decyzji dotyczących odpowiedzialności komponentu, interfejsu publicznego, zależności (bezpośrednich lub wstrzykiwanych), czasu działania lub zachowania w czasie kompilacji, zmienności, nazw, przepływu danych, przepływu sterowania, warstw itp. Dobry projekt oznacza także odroczenie niektórych decyzji do najnowszego możliwego momentu. Test jednostkowy może pokazać pośrednio, czy Twój projekt był w porządku: jeśli masz ochotę przefakturować wiele z nich po zmianie wymagań, prawdopodobnie tak nie było.
Doc Brown
@Giorgio: przykład może wyjaśnić: powiedzmy, że masz komponent X z metodą „MyMethod” i 2 parametrami. Używając TDD, piszesz X x= new X(); AssertTrue(x.MyMethod(12,"abc"))przed faktyczną implementacją metody. Korzystając z wstępnego projektu, możesz class 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.
Doc Brown
1
Zgadzam się z tobą: jestem nieco sceptyczny, gdy widzę TDD stosowane na ślepo z założeniem, że automatycznie wygeneruje dobry projekt. Co więcej, czasami TDD przeszkadza, jeśli projekt nie jest jeszcze jasny: jestem zmuszony przetestować szczegóły, zanim będę miał przegląd tego, co robię. Tak więc, jeśli dobrze rozumiem, zgadzamy się. Myślę, że (1) testy jednostkowe pomagają zweryfikować projekt, ale projekt jest osobną czynnością, a (2) TDD nie zawsze jest najlepszym rozwiązaniem, ponieważ musisz uporządkować pomysły przed rozpoczęciem pisania testów, a TDD może cię spowolnić to.
Giorgio
1
Krótko mówiąc, testy jednostkowe mogą wykazać wady w wewnętrznej konstrukcji komponentu. Interfejs, warunki wstępne i końcowe muszą być znane wcześniej, w przeciwnym razie nie można utworzyć testu jednostkowego. Tak więc projekt komponentu musi być wykonany przed napisaniem testu jednostkowego. Jak to robi - projekt niższego poziomu, projekt szczegółowy lub projekt wewnętrzny lub jakkolwiek chcesz to nazwać - może mieć miejsce po napisaniu testu jednostkowego.
Maarten Bodewes
9

Tak, oczywiście, że tak.

Rozważ to:

  • test jednostkowy jest małym, ukierunkowanym testem, który wykonuje mały fragment kodu. Wiele z nich piszesz, aby uzyskać przyzwoity zasięg kodu, dzięki czemu wszystkie (lub większość niezręcznych bitów) są testowane.
  • test integracyjny jest dużym, szerokim testem, który wykorzystuje dużą powierzchnię twojego kodu. Napisz kilka z nich, aby uzyskać przyzwoity zasięg kodu, dzięki czemu wszystkie (lub większość niezręcznych bitów) zostaną przetestowane.

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ę).

gbjbaanb
źródło
7
Niezupełnie prawda ... Dzięki testom integracyjnym możliwe jest posiadanie dwóch komponentów, które są wadliwe, ale ich błędy anulują się w teście integracyjnym. Nie ma to większego znaczenia, dopóki użytkownik końcowy nie użyje go w sposób, w jaki używany jest tylko jeden z tych komponentów ...
Michael Shaw,
1
Pokrycie kodu! = Przetestowane - oprócz błędów wzajemnie się znoszących, co ze scenariuszami, o których nigdy nie pomyślałeś? Testy integracyjne są odpowiednie do testowania szczęśliwych ścieżek, ale rzadko widzę odpowiednie testy integracyjne, gdy coś nie idzie dobrze.
Michael
4
@Ptolemy Wydaje mi się, że rzadkość wzajemnego znoszenia się dwóch błędnych komponentów jest znacznie niższa niż 2 współpracujące ze sobą komponenty.
gbjbaanb
2
@Michael, po prostu nie włożyłeś wystarczającego wysiłku w testowanie, powiedziałem, że trudniej jest wykonać dobre testy integracyjne, ponieważ testy muszą być znacznie bardziej szczegółowe. Możesz dostarczyć złe dane w teście integracji tak łatwo, jak w teście jednostkowym. Testy integracyjne! = Szczęśliwa ścieżka. Chodzi o ćwiczenie jak największej ilości kodu, dlatego istnieją narzędzia do pokrycia kodu, które pokazują, jak dużo kodu zostało wykonane.
gbjbaanb
1
@ Michael Kiedy prawidłowo używam narzędzi takich jak Cucumber lub SpecFlow, mogę tworzyć testy integracyjne, które testują wyjątki i sytuacje ekstremalne tak szybko, jak testy jednostkowe. Ale zgadzam się, że jeśli jedna klasa ma zbyt wiele permutacji, wolę napisać test jednostkowy dla tej klasy. Ale będzie to rzadziej niż w przypadku klas posiadających tylko garstkę ścieżek.
Yggdrasil
2

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.

Telastyn
źródło
2
Główną wołowiną, którą słyszę przeciwko TDD, jest zakłócenie naturalnego rozwoju i wymusza programowanie obronne od samego początku. Jeśli programista jest już pod presją czasu, prawdopodobnie będzie chciał wyciąć kod i wypolerować go później. Oczywiście dotrzymanie arbitralnego terminu z błędnym kodem to fałszywa ekonomia.
Robbie Dee
2
Rzeczywiście, zwłaszcza, że ​​„wypoleruj to później” nigdy się nie zdarza - każda recenzja, którą robię, w której programista naciska „musi wyjść, zrobimy to później”, kiedy wszyscy wiemy, że to się nie stanie - technicznie dług = bankructwo programistów moim zdaniem.
Michael
3
Odpowiedź ma dla mnie sens, nie wiem, dlaczego ma tyle minusów. Cytując Mike'a Cohna: „Testowanie jednostkowe powinno być fundamentem solidnej strategii automatyzacji testów i jako takie stanowi największą część piramidy. Zautomatyzowane testy jednostkowe są wspaniałe, ponieważ dostarczają programistom konkretnych danych - jest błąd i jest włączony linia 47" mountaingoatsoftware.com/blog/...
guillaume31
4
@ guillaume31 Tylko dlatego, że ktoś powiedział kiedyś, że są dobrzy, nie znaczy, że są dobrzy. Z mojego doświadczenia wynika, że ​​błędy NIE są wykrywane przez testy jednostkowe, ponieważ zostały z góry zmienione na nowe wymagania. Również większość błędów to błędy integracyjne. Dlatego wykrywam większość z nich podczas testów integracyjnych.
Yggdrasil
2
@Yggdrasil Mógłbym również zacytować Martina Fowlera: „Jeśli wystąpi błąd w teście wysokiego poziomu, nie tylko masz błąd w kodzie funkcjonalnym, masz również brakujący test jednostkowy”. martinfowler.com/bliki/TestPyramid.html W każdym razie, jeśli sam testy integracyjne praca dla Ciebie, to w porządku. Z mojego doświadczenia wynika, że ​​chociaż są potrzebne, są one wolniejsze, dostarczają mniej precyzyjne komunikaty o awariach i mniej zwrotne (bardziej kombinatoryczne) niż testy jednostkowe. Poza tym piszę testy integracyjne - nie jestem w pełni gotowy na przyszłość - rozumiem raczej scenariusze wcześniej (błędnie?) Niż poprawność samego obiektu.
guillaume31,
2

„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.

Robbie Dee
źródło
1
Moje testy piszę z góry i piszę wystarczająco dużo, aby pokryć większość błędów. Co do błędów, które pojawiają się później. Wydaje mi się, że wymaganie się zmieniło lub że pojawia się nowe wymaganie. Następnie testy integracji / zachowania muszą zostać zmienione, dodane. Jeśli wtedy błąd pojawia się w starym wymaganiu, mój test go pokaże. Co do automatyzacji. wszystkie moje testy trwają cały czas.
Yggdrasil
Przykład, o którym myślałem w ostatnim akapicie, to powiedzmy, że biblioteka była używana wyłącznie przez jedną aplikację, ale wtedy istniały wymagania biznesowe, aby uczynić ją biblioteką ogólnego przeznaczenia. W takim przypadku lepiej może być przeprowadzanie przynajmniej niektórych testów jednostkowych niż pisanie nowych testów integracji / zachowania dla każdego systemu podłączonego do biblioteki.
Robbie Dee
2
Automatyzacja testów i testowanie jednostkowe są kwestią całkowicie ortogonalną. Każdy szanujący się projekt będzie miał zautomatyzowaną integrację i testy funkcjonalne. To prawda, że ​​często nie widzisz ręcznych testów jednostkowych, ale mogą one istnieć (w zasadzie ręczny test jednostkowy jest narzędziem testującym dla niektórych określonych funkcji).
Jan Hudec
Cóż, rzeczywiście. Rozwija się rynek automatycznych narzędzi innych firm, które istnieją od dłuższego czasu poza sferą programistyczną.
Robbie Dee
1

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.

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.

Testy jednostkowe są również dobre dla projektu.

Tak, projektowanie z myślą o testowaniu poprawi projekt.

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.

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ń.

BЈовић
źródło
1
Czy spojrzałeś na mój przykład? To się zdarzało przez cały czas. Chodzi o to, że kiedy wdrażam nową funkcję, zmieniam / dodam test jednostkowy, aby przetestowali nową funkcję - dlatego żaden test jednostkowy nie zostanie przerwany. W większości przypadków mam skutki uboczne zmian, które nie są wykrywane przez testy jednostkowe - ponieważ środowisko jest wyśmiewane. Z mojego doświadczenia wynika, że ​​żaden test jednostkowy nigdy nie powiedział mi, że zepsułem istniejącą funkcję. Zawsze były testy integracji i akceptacji, które pokazały mi moje błędy.
Yggdrasil
Co do czasu wykonania. Wraz z rosnącą aplikacją mam głównie rosnącą liczbę izolowanych komponentów. Jeśli nie, zrobiłem coś złego. Kiedy wdrażam nową funkcję, jest to głównie tylko ograniczona liczba komponentów. Piszę jeden lub więcej testów akceptacyjnych w zakresie całej aplikacji - co może być powolne. Dodatkowo piszę te same testy z punktu widzenia komponentów - testy te są szybkie, ponieważ komponenty są zwykle szybkie. Mogę wykonywać testy komponentów przez cały czas, ponieważ są one wystarczająco szybkie.
Yggdrasil
@Yggdrasil Jak powiedziałem, testy jednostkowe nie są potężne, ale zwykle są pierwszą warstwą testów, ponieważ są najszybsze. Inne testy są również przydatne i należy je łączyć.
BЈовић
1
To, że są szybsze, nie oznacza, że ​​należy ich używać tylko z tego powodu lub dlatego, że często się je pisze. Jak powiedziałem, moje testy jednostkowe się nie psują - więc nie mają dla mnie żadnej wartości.
Yggdrasil
1
Pytanie brzmiało: jaką wartość mam z testu jednostkowego, kiedy się nie psują? Po co męczyć się z ich pisaniem, skoro zawsze muszę je dostosowywać do nowych wymagań? Jedyną ich wartością są algorytmy i inne klasy o wysokiej permutacji. Ale są to mniej niż testy części i testy akceptacyjne.
Yggdrasil