Z perspektywy TDD, czy jestem złym człowiekiem, jeśli przeprowadzam test na żywym punkcie końcowym zamiast na pozór?

16

Podążam religijnie za TDD. Moje projekty zazwyczaj mają 85% lub więcej pokrycia testowego, z sensownymi przypadkami testowymi.

Dużo pracuję z HBase , a główny interfejs klienta, HTable, jest bardzo trudny do wyszydzenia. Pisanie moich testów jednostkowych zajmuje mi 3 lub 4 razy więcej niż pisanie testów, które używają aktywnego punktu końcowego.

Wiem, że filozoficznie testy wykorzystujące symulacje powinny mieć pierwszeństwo przed testami wykorzystującymi aktywny punkt końcowy. Ale szydzenie z HTable jest poważnym bólem i nie jestem pewien, czy oferuje dużą przewagę nad testowaniem na żywo instancji HBase.

Wszyscy w moim zespole uruchamiają jedno-węzłowe wystąpienie HBase na swojej stacji roboczej, a my mamy jedno-węzłowe wystąpienia HBase działające na naszych urządzeniach Jenkins, więc nie jest to kwestia dostępności. Testy na żywo punktów końcowych trwają oczywiście dłużej niż testy wykorzystujące symulacje, ale tak naprawdę nas to nie obchodzi.

W tej chwili piszę na żywo testy punktów końcowych ORAZ testy próbne dla wszystkich moich klas. Chciałbym porzucić te kpiny, ale nie chcę, aby jakość spadła.

Co wszyscy myślicie

sangfroid
źródło
8
Aktywny punkt końcowy nie jest tak naprawdę testem jednostkowym, prawda? To test integracyjny. Ale ostatecznie jest to prawdopodobnie kwestia pragmatyzmu; możesz spędzać czas na pisaniu próbnych treści lub na pisaniu lub naprawianiu błędów.
Robert Harvey
4
Słyszałem historie o ludziach, którzy zlikwidowali usługi innych firm, przeprowadzając test jednostkowy pod kątem własnego kodu ... który został podłączony do punktu końcowego na żywo. Ograniczanie prędkości nie jest czymś, co zwykle robi lub zależy od testów jednostkowych.
14
Nie jesteś złą osobą. Jesteś dobrą osobą, która robi coś złego.
Kyralessa
15
Religijnie śledzę TDD. Może to jest problem? Nie sądzę, aby jakakolwiek z tych metod była traktowana tak poważnie. ;)
FrustratedWithFormsDesigner
9
Religijne przestrzeganie TDD oznaczałoby odrzucenie 15% odkrytego kodu.
mouviciel

Odpowiedzi:

23
  • Moim pierwszym zaleceniem byłoby, aby nie kpić z typów, których nie posiadasz . Wspomniałeś, że HTable jest prawdziwym bólem do kpienia - być może powinieneś owinąć go w Adapter, który ujawnia 20% potrzebnych funkcji HTable, i kpić z opakowania w razie potrzeby.

  • Biorąc to pod uwagę, załóżmy, że mówimy o typach, które wszyscy posiadacie. Jeśli twoje symulacje oparte są na scenariuszach szczęśliwych ścieżek, w których wszystko idzie płynnie, nie stracisz nic, porzucając je, ponieważ testy integracyjne prawdopodobnie już testują dokładnie te same ścieżki.

    Jednak izolowane testy stają się interesujące, gdy zaczniesz myśleć o tym, jak testowany system powinien reagować na każdą drobiazg, który może się zdarzyć zgodnie z umową jego współpracownika, niezależnie od konkretnego konkretnego obiektu, z którym rozmawia. To część tego, co niektórzy nazywają podstawową poprawnością . Może być wiele takich małych skrzynek i wiele innych kombinacji. W tym momencie testy integracyjne zaczynają być kiepskie, podczas gdy pojedyncze testy pozostaną szybkie i łatwe do zarządzania.

    Aby być bardziej konkretnym, co się stanie, jeśli jedna z metod adaptera HTable zwróci pustą listę? Co jeśli zwróci wartość null? Co się stanie, jeśli zgłosi wyjątek połączenia? Należy określić w umowie adaptera, czy coś takiego mogłoby się zdarzyć, a każdy z jego konsumentów powinien być przygotowany na poradzenie sobie z tymi sytuacjami , stąd potrzeba ich testów.

Podsumowując: nie zobaczysz żadnego spadku jakości po usunięciu próbnych testów, jeśli przetestują dokładnie te same rzeczy, co testy integracyjne . Jednak próba wyobrazenia sobie dodatkowych izolowanych testów (i testów kontraktowych ) może pomóc w znacznym przemyśleniu interfejsów / kontraktów i podniesieniu jakości przez wyeliminowanie defektów, o których trudno byłoby pomyśleć i / lub które wolno testować za pomocą testów integracyjnych.

guillaume31
źródło
+1 Uważam, że o wiele łatwiej jest budować przypadki brzegowe za pomocą próbnych testów niż zapełnianie bazy danych tymi przypadkami.
Rob
Zgadzam się z większością twojej odpowiedzi. Nie jestem jednak pewien, czy zgadzam się co do części adaptera. HTable jest kpiną, ponieważ jest dość goły. Na przykład, jeśli chcesz wykonać wsadową operację get, musisz utworzyć wiązkę obiektów Get, umieścić je na liście, a następnie wywołać HTable.batch (). Z kpiącego punktu widzenia jest to poważny problem, ponieważ musisz utworzyć niestandardowy Matcher, który bada listę obiektów get przekazywanych do HTable.batch (), a następnie zwraca prawidłowe wyniki dla tej listy obiektów get (). POWAŻNY ból.
sangfroid
Przypuszczam, że mógłbym stworzyć fajną, przyjazną klasę opakowania dla HTable, która zajmowałaby się całym tym sprzątaniem, ale w tym momencie ... Czuję, że buduję strukturę wokół HTable i czy to naprawdę powinna być moja praca? Zwykle „Zbudujmy szkielet!” to znak, że idę w złym kierunku. Mógłbym spędzać dni na pisaniu lekcji, aby uczynić HBase bardziej przyjaznym, i nie wiem, czy to świetne wykorzystanie mojego czasu. Plus, a następnie pauzuję wokół interfejsu lub opakowania zamiast zwykłego starego obiektu HTable, a to zdecydowanie uczyniłoby mój kod bardziej złożonym.
sangfroid
Zgadzam się jednak z twoją główną uwagą, że nie należy pisać szyderstw do klas, których nie mają. I zdecydowanie zgadzam się, że nie ma sensu pisać próbnego testu, który testuje to samo, co test integracyjny. Wydaje się, że najlepsze są testy do testowania interfejsów / umów. Dzięki za radę - to bardzo mi pomogło!
sangfroid
Nie mam pojęcia, co tak naprawdę robi HTable i jak z niej korzystasz, więc nie bierz mojego przykładu opakowania do listu. Wspominałem o owijarce / adapterze, ponieważ uważałem, że owijanie było stosunkowo małe. Nie musisz wprowadzać repliki jeden-do-jednego do HTable, co oczywiście byłoby bolesne, nie mówiąc już o całym frameworku - ale potrzebujesz szwu , interfejsu między królestwem twojej aplikacji a HTable. Powinien przeformułować niektóre funkcje HTable na własne warunki aplikacji. Wzór repozytorium jest idealnym wcieleniem takiego szwu, jeśli chodzi o dostęp do danych.
guillaume31
11

filozoficznie testy wykorzystujące symulacje powinny mieć pierwszeństwo przed testami wykorzystującymi aktywny punkt końcowy

Myślę, że co najmniej, to punktem bieżącym trwającej kontrowersji wśród zwolenników TDD.

Mój osobisty pogląd wykracza poza to, by powiedzieć, że próbny test jest głównie sposobem na reprezentację formy umowy interfejsu ; idealnie psuje się (tzn. nie działa) wtedy i tylko po zmianie interfejsu . I jako taki, w rozsądnie silnie typowanych językach, takich jak Java, a przy użyciu wyraźnie zdefiniowanego interfejsu jest prawie całkowicie zbędny: kompilator już ci powie, czy zmieniłeś interfejs.

Głównym wyjątkiem jest sytuacja, w której używasz bardzo ogólnego interfejsu, być może opartego na adnotacjach lub refleksjach, że kompilator nie jest w stanie skutecznie użytkować policji automatycznie. Nawet wtedy powinieneś sprawdzić, czy istnieje sposób na programową weryfikację (np. Bibliotekę sprawdzającą składnię SQL), a nie ręcznie przy użyciu próbnych metod.

To właśnie ten drugi przypadek ma miejsce podczas testowania przy użyciu lokalnej „bazy danych” na żywo; Wdrożenie htable rozpoczyna się i stosuje znacznie bardziej kompleksową walidację umowy międzyfazowej, niż można sobie wyobrazić ręcznie.

Niestety, o wiele bardziej powszechnym zastosowaniem próbnych testów jest test, który:

  • przechodzi na dowolny kod w momencie pisania testu
  • nie daje żadnych gwarancji dotyczących jakichkolwiek właściwości kodu poza tym, że istnieje i rodzaju uruchomień
  • kończy się niepowodzeniem po każdej zmianie tego kodu

Takie testy należy oczywiście usunąć na widok.

soru
źródło
1
Nie mogę tego wystarczająco poprzeć. Wolę mieć pokrycie 1szt z świetnymi testami niż pokrycie wypełniacza 100pc.
Ian
3
Testy próbne rzeczywiście opisują kontrakt używany przez 2 obiekty do wspólnej rozmowy, ale wykraczają daleko poza to, co potrafi system typów języka takiego jak Java. Nie chodzi tylko o podpisy metod, mogą one również określać prawidłowe zakresy wartości argumentów lub zwracanych wyników, które wyjątki są dopuszczalne, w jakiej kolejności i ile razy można wywoływać metody itp. Sam kompilator nie ostrzeże Cię, jeśli istnieje są zmiany w nich. W tym sensie nie sądzę, aby były w ogóle zbędne. Zobacz infoq.com/presentations/integration-tests-scam, aby uzyskać więcej informacji na temat testów próbnych.
guillaume31
1
... zgadzają się, tj. testują logikę wokół wywołania interfejsu
Rob
1
Z pewnością może dodać niesprawdzone wyjątki, niezadeklarowane warunki wstępne i stan niejawny, do listy rzeczy, które sprawiają, że interfejs jest mniej statycznie typowany, a zatem usprawiedliwiają testowanie próbne zamiast prostej kompilacji. Problem polega jednak na tym, że gdy te aspekty się zmieniają, ich specyfikacja jest domyślna i rozprowadzana wśród testów wszystkich klientów. Które prawdopodobnie nie zostaną zaktualizowane, więc usiądź w ciszy i ukryj błąd za zielonym tyknięciem.
soru
„ich specyfikacja jest niejawna”: nie, jeśli piszesz testy kontraktów dla swoich interfejsów ( blog.thecodewhisperer.com/2011/07/07/contract-tests-an-example ) i trzymasz się ich podczas konfigurowania prób .
guillaume31
5

Ile dłużej trwa test oparty na punkcie końcowym niż test próbny? Jeśli jest znacznie dłuższy, to tak, warto zainwestować czas na pisanie testów, aby przyspieszyć testy jednostkowe - ponieważ będziesz musiał je uruchamiać wiele, wiele razy. Jeśli nie jest to znacznie dłuższe, mimo że testy oparte na punktach końcowych nie są „czystymi” testami jednostkowymi, o ile wykonują dobrą robotę testowania jednostki, nie ma powodu, aby być religijnym.

Carl Manaster
źródło
4

Zgadzam się całkowicie z odpowiedzią guillaume31, nigdy nie kpij z typów, których nie posiadasz!

Zwykle ból w teście (kpina ze złożonego interfejsu) odzwierciedla problem w twoim projekcie. Być może potrzebujesz pewnej abstrakcji między modelem a kodem dostępu do danych, na przykład z wykorzystaniem architektury heksagonalnej i wzorca repozytorium, który jest najczęstszym sposobem rozwiązania tego rodzaju problemów.

Jeśli chcesz wykonać test integracyjny w celu sprawdzenia rzeczy, zrób test integracyjny, jeśli chcesz wykonać test jednostkowy, ponieważ testujesz logikę, wykonaj test jednostkowy i odizoluj utrwalanie. Ale wykonując test integracji, ponieważ nie wiesz, jak odizolować swoją logikę od systemu zewnętrznego (lub ponieważ izolowanie jej bólu) to duży zapach, wybierasz integrację zamiast jednostki dla ograniczenia w projekcie, a nie dla prawdziwej potrzeby przetestować integrację.

Spójrz na tę rozmowę z Ianem Cooperem: http://vimeo.com/68375232 , mówi o architekturze heksagonalnej i testowaniu, mówi o tym, kiedy i co drwić, naprawdę inspirująca rozmowa, która rozwiązuje wiele pytań takich jak prawdziwe TDD .

AlfredoCasado
źródło
1

TL; DR - Z mojego punktu widzenia zależy to od nakładu pracy na testy i od tego, czy lepiej byłoby zamiast tego wydać więcej na rzeczywisty system.

Długa wersja:

Kilka dobrych odpowiedzi tutaj, ale moje zdanie jest inne: testowanie jest działalnością gospodarczą, która musi się zwrócić, a jeśli czas, który poświęcisz, nie zostanie zwrócony na rozwój i niezawodność systemu (lub cokolwiek innego, co chcesz wydostać testów), być może dokonujesz złej inwestycji; zajmujesz się budowaniem systemów, a nie pisaniem testów. Dlatego kluczowe jest ograniczenie wysiłku w pisaniu i utrzymywaniu testów.

Na przykład niektóre główne wartości, które uzyskuję z testów, to:

  • Niezawodność (a zatem i szybkość programowania): kod refaktora / zintegruj nowy framework / zamień komponent / port na inną platformę, bądź pewien, że rzeczy nadal działają
  • Informacje zwrotne od projektu: klasyczne informacje zwrotne TDD / BDD „używaj swojego kodu” na interfejsach niskiego / średniego poziomu

Testowanie na działającym punkcie końcowym powinno nadal je zapewniać.

Niektóre wady testowania w odniesieniu do aktywnego punktu końcowego:

  • Konfiguracja środowiska - konfiguracja i standaryzacja testowego środowiska pracy jest bardziej pracochłonna, a subtelnie różne konfiguracje środowiska mogą spowodować subtelnie różne zachowanie
  • Bezpaństwowość - praca z działającym punktem końcowym może skończyć się promowaniem testów pisania, które opierają się na zmutowanym stanie punktu końcowego, który jest kruchy i trudny do uzasadnienia (tj. Gdy coś zawodzi, czy zawodzi z powodu dziwnego stanu?)
  • Środowisko testowe jest niestabilne - jeśli test się nie powiedzie, czy jest to test, kod lub aktywny punkt końcowy?
  • Szybkość biegu - aktywny punkt końcowy jest zwykle wolniejszy, a czasem trudniej go zrównoleglić
  • Tworzenie skrajnych przypadków do testowania - zwykle trywialne z próbą, czasem bólem z żywym punktem końcowym (np. Trudne do skonfigurowania to błędy transportu / HTTP)

Gdybym był w takiej sytuacji, a wady nie wydawałyby się problemem, podczas gdy wyśmiewanie punktu końcowego znacznie spowolniło pisanie testu, w mgnieniu oka testowałbym w czasie rzeczywistym punkt końcowy na żywo. sprawdź ponownie po chwili, aby zobaczyć, czy wady nie okazują się problemem w praktyce.

orip
źródło
1

Z perspektywy testowania istnieją absolutnie pewne wymagania:

  • Testy (jednostkowe lub inne) nigdy nie mogą mieć wpływu na dane produkcyjne
  • Wyniki jednego testu nigdy nie mogą wpływać na wyniki innego testu
  • Zawsze musisz zaczynać od znanej pozycji

To duże wyzwanie, gdy łączysz się z dowolnym źródłem, które utrzymuje stan poza twoimi testami. To nie jest „czysty” TDD, ale załoga Ruby on Rails rozwiązała ten problem w sposób, który może być przystosowany do twoich celów. Środowisko testowe szyn działało w ten sposób:

  • Konfiguracja testu została automatycznie wybrana podczas uruchamiania testów jednostkowych
  • Baza danych została utworzona i zainicjowana na początku uruchomionych testów jednostkowych
  • Baza danych została usunięta po uruchomieniu testów jednostkowych
  • Jeśli używasz SqlLite, konfiguracja testowa używała bazy danych RAM

Cała ta praca została wbudowana w uprząż testową i działa dość dobrze. Jest o wiele więcej, ale podstawy tej rozmowy są wystarczające.

W różnych zespołach, z którymi pracowałem w czasie, dokonywaliśmy wyborów, które promowałyby testowanie kodu, nawet jeśli nie byłaby to najlepsza ścieżka. Idealnie byłoby zawinąć wszystkie wywołania do magazynu danych kontrolowanym przez nas kodem. Teoretycznie, jeśli którykolwiek z tych starych projektów uzyska nowe fundusze, moglibyśmy cofnąć się i przenieść je z bazy danych związanej z Hadoop, koncentrując naszą uwagę tylko na kilku klasach.

Ważne jest, aby nie mieszać się z danymi produkcyjnymi i upewnić się, że naprawdę testujesz to, co według Ciebie testujesz. Naprawdę ważne jest, aby móc zresetować usługę zewnętrzną do znanej linii bazowej na żądanie - nawet z poziomu kodu.

Berin Loritsch
źródło