Odwrócona piramida testów

15

Jako że @Afish striggerował mnie pewnym postem, postanowowiłem wyjaśnić swoją krytykę mocków, tj. czemu uważam że Mockito i podobne biblioteki(jak Moq w C#) są stosowane nadmiernie i nic ne wnoszą.
Teraz żeby być bardziej precyzyjnym, pisząc o mockach mam na myśli rzeczy typu:

ValueGenerator vgMock = Mockito.mock(ValueGenerator.class);
when(vgMock.getValue()).thenReturn(7)

Jest jeszcze Wiremock czyli narzędzie do mockowania http, ale to już będe nazywał po prostu Wiremockiem, podobnie, jak z bazą danych w pamięci (nie będę H2 określał mockiem w tym poście).

A teraz wyjaśnię czemu sądze że mocki są nadmiernie stosowane i co zamiast nich, oraz dlaczego często lepsza jest odwrócona piramida testów, tj. podstawą są testy integracyjne(mówię o tych z poziomu aplikacji), a nie jednostkowego (cokolwiek tą jednostką) jest:

  1. Możemy przetestować całość aplikacji, cześć tej aplikacji to infrastruktura. Dodatkowo niektóre funkcje sa bardzo CRUDOwe.
    Założmy że mamy do czynienia ze sklepem internetowym w którym mamy możliwość dostarczenia komentarza do produktu jeśli kupiliśmy go wczesniej. Co w takim przypadku jest ważne? Żeby użytkownik mógł dodać komentarz tylko jako on, a nie ktoś inny/niezalogowany, żeby sprawdzić czy może dodac komentarz(iloczyn warunków logicznych - czy kupił produkt oraz czy nie dodał komentarza) a na koniec zapisać ten komentarz. Jak takie coś można przetestowac z Mockito? No nijak, bo najważniejsze to przetesowanie security i SQL, bo przecież on jest raczej bardziej złożony niż if z dwoma warunkami które mogliśmy zamockować z Mockito. Najlepiej uruchomić baze aplikacje z testowa bazą danych, wykonać odpowienie requesty i zrobić asercję np. na status HTTP/ stan bazy testowej. Tak czy owak należy testować takie rzeczy jak security, zapytania SQL itp. No można by teoretycznie obiekty dostępu do baz danych testować oddzielnie, tylko po co skoro już uruchamiamy test? Możemy przetestować aplikację a nie to czy mockito nam dobrze zamockowało dane.
  2. Testy integracyjne nie betonują tak kodu. Problem z testami "niższego rzędu" jest taki że bardzo są blisko kodu, a więc każda mała zmiana powoduje że trzeba zmieniać też również testy. Jak ludzie testują na poziomie klas to jest tragedia komplentna, trochę lepiej jest jak mamy jakieś fasady ( ciekawa prezentacja o tym
  3. Biblioteki do mockowania sa często nie dokońca intuicyjne i często się można pomylić przez to w testach. Dodatkowo gdy testujemy bardzo nisko poziomowo mamy cały zarypany kod jakimiś inicjacjami mocków, i nie sądze żeby to powodowało że takie testy da się pisać/modyfikować szybko, ani nie dodają czytelności.
  4. Szybkość/koszt testów - mówi się że testy integracyjne są wolne. Otóż nie do końca. Jesli korzystamy z takiego TestCointaners i startujemy testy to sam start jest rzeczywiście powolniejszy, ale po starcie testy bedą się raczej wykonywac szybko. Za to zaoszczędzimy czas na tym że testy nie są tak zabetonowane. Dlatego jeśli dla danej funkcji programu mamy 5 przypadków testowych, testy integracyjne mają sens.

Dodatkowo, zamiast korzystać z mocków możemy robic testowe implementacje interfejsów. Możemy mieć interface CurrencyRatesProvider, implementacje produkcyjną jak jakiś RestCurrencyRatesProvider i jakieś faki. Takie rozwiązania są często bardziej czytelne i zwięzłe niż jakieś Mockito when then/thenReturn.

Co w przypadku integracji z innymi systemami? Na JVM jest taka biblioteka jak Wiremock która umożliwia postawienie stuba z zamockowanym api. Oczywiście jest to mock, ale mock o wiele bardziej inteligenty gdzie my wywołujemy zapytanie HTTP z poziomu testu i pewnie podobne są dostepne dla innych środowisk.

Kiedy tak naprawde sens testowania jednostkowego? W przypadku gdy chcemy wyodrębnić jakiś bardziej złożony algorytm który może mieć tez wiele różnych warunków, na przykład może to być kalkulator promocji z których część włącza się do innych, a część nie, a łącznie dla danego zamówienia aktywnych będzie na przykład 3 albo i 4 (jak dwie ksiązki w cenie jednej, promocja z okazji black friday, zniższka powyżej iluś tam złotych i jakiś kod rabatowy do tego).

Gdybym miał napisać TL;DR - po co tak naprawde stosowac mocki skoro i tak chcemy sprawdzić działanie aplikacji, w tym na przykład security, zapytania SQL, itp.

1

Super podsumowanie. Staram się stosować i propagować podobny styl.

Od siebie jeszcze dorzucę:

  • Często przy tego typu tematach wychodzą rozmyte pojęcia typu test jednostkowy vs integracyjny. Świetnym wytłumaczeniem jest wtedy, że naszą jednostką jest usecase (operacja biznesowa) i nasze testy to testy per usecase.

  • Pomocą przy takim podejściu może być architektura heksagonalna w której, zarówno kod domenowy jak i kod testów abstrahują szczegóły techniczne (DB, HTTP, JMS etc.). Nic nie stoi na przeszkodzie (przynajmniej w JUnit5), by stworzyć BaseSalaryCalculatorTest który wymaga podania w konstruktorze konkretnych repozytoriów. Wówczas mogę w module domenowym dostarczyć repozytoria oparte na jakąś in memory mapę, a w module z adapterami opartą na magicznych frameworkach. Zaletą jest to, że w bazowych testach pojawia się słownictwo czysto biznesowe.

  • Startując bazę lub framework raz per test, pojawia się argument, że testy powinny być niezależne. Można to osiągnąć, czyszcząc taką bazę. Co do frameworka to zwykle na produkcji nikt nam nie restartuje serwera po każdej operacji, więc może to być nawet pożądane przy wykrywaniu pewnych haisenbugów (pomijam serverless / lambdy, tam raczej nikt nie bawi się w wolno startujące frameworki)

  • Ludzie czesto piszą testy mockując wybrane metody, które były akurat wykorzystywane. Nagle ktoś dodaje prostą walidację, a tu wszystkie testy wybuchają nie dlatego, że nowa walidacja coś psuje, a dlatego że te testy nie mają ustawionego np. Repository::count(). Oczywiście można to naprawić robiąc metodę typu givenAnimals(List.of(dog, cat, hippo)), ale nie jest to standard i wymaga samokontroli.

4

W temacie.
Co do odwróconej piramidy testów to jest takie pojęcie. Jakkolwiek... wróćmy do tematu za 4-5 lat - mainstream jeszcze nie jest gotowy aby słuchać, że testy to code smell.
Testy to testy. Od dawna uważam podział na jednostkowe, integracyjne itd. za sztuczny - jakiś relikt z frameworków enterprise ediszion.
Mocki... w sumie to nie mam nic przeciwko, o ile się nie nadużywa, a nadużywa się prawie wszędzie. Jak trafia do mnie gotowy serwis i ma metodę typu
Status sendMail() , to pewnie użyję Mockito. W praktyce jednak jest to przypadek dość nieczęsty - w większości projektów Mockito nigdy nie podłączam nawet jako zależność. Uważam za dziwne mockowanie w testach klas, które są w tym samym module, pakiecie. Co się stało, że nie można ich normalnie użyć?
Btw. kiedyś to robiłem :/ Londyńska szkoła TDD (co za kupa - wstyd mi za te testy teraz, ale 100% pokrycia było).

Często mocksturbacja to skutek choroby - frameworków DI.
W 30 minucie Tomer Gabel porusza ten temat:

2

@scibi92: Generalnie się zgadzam :)

Testy jednostkowe piszę nadal, jesli testuję jakiś algorytm wyliczania czegoś - mimo, że jest to składowa jakiejś większej funkcjonalności - niemniej czasem takie zabetonowanie jakiejś małej cząstki ma dla mnie sens - np. algorytmu wyliczania czegoś lub metody walidującej (mimo, że klasa owa ma zakres per package), chcę zabetonować tę konkretną metodę a nie tylko testować całość. Niemniej jak testuję całość to nie mockuję tego :)

Tak czy owak należy testować takie rzeczy jak security

Tak, ale tu akurat robisz "mocka", jakąś dummy implementację, że nie musi być konto testUser z uprawnieniami do aplikacji na platformie zewnętrznej x. Tylko mockujesz metodę pobrania użytkownika/tokenu z zenętrznego serwisu.

5

Tak, ale tu akurat robisz "mocka", jakąś dummy implementację, że nie musi być konto testUser z uprawnieniami do aplikacji na platformie zewnętrznej x. Tylko mockujesz metodę pobrania użytkownika/tokenu z zenętrznego serwisu.

Chodzi o to zeby mockować API serwisu i go "udawać" a nie wyłącząć cała komunikację spod testu. Tzn nie mockujesz obiektu X i jego metody getToken tylko startujesz embedded http server który wystawia takie API jak ten serwis od tokenów i wie że na request /dejMjeTenToken ma odpowiedzieć przygotowym przez nas tokenem :) Patrz testy w: https://github.com/Pharisaeus/almost-s3

0

a co z czasem wykonania? jezeli klasa A wola klase B a klasa B jest juz przetestowana w innym tescie? nie mockujac klasy B w tescie klasy A, test A bedzie sie 2 razy dluzej wykonywal

4

@lambdadziara: pomijasz czas stworzenia mocka, który jest często dłuższy niż wykonanie tej metody

2

Testuje tak sporo serwisów, takich testów jest po kilkadziesiąt na serwis i czas wykonania to raptem kilka sekund. Z jakimś testcontainters byłoby pewnie trochę dłużej, ale mimo wszystko nigdy nie czułem żeby mnie to jakoś specjalnie spowalniało albo przeszkadzało.

6

Mi się idea odwrócenia piramidy testów i zniknięcia sztywnego podziału na "jednostkowe" i "integracyjne" nawet podoba.

Tak z perspektywy czasu, przy takiej nie-odwróconej piramidzie, silnym podziale na to co "jednostkowe" i "integracyjne", najlepiej testach-per-klasa i CRUDowych aplikacjach robi się po prostu kuriozalnie

  • Piszesz test "integracyjny", gdzie śmiga spory kawałek lub całość aplikacji, sekórity, pod spodem H2 / TestContainers / cokolwiek - i coś tam testujesz. Raczej niewiele, bo to CRUD i wielkiej logiki ani tym bardziej algorytmów w takim cudzie nadwiślańskiej technologii nie uświadczysz.
  • Teraz "musisz" napisać test "jednostkowy", najlepiej dla każdej warstwy swojej lasagne po kolei bo czemu nie - no to siup, mockujesz wszystko dookoła i testujesz praktycznie to samo, co wyżej, tyle że na mockach i jest jeszcze bardziej rozrzedzone
  • Dowolna zmiana wymaga przepisania od jednego do kilkudziesięciu testów, bo są tak zabetonowane że nawet po niewielkich zmianach nawet "integracyjne" się nie kompilują (xD), a jak już je "naprawisz" żeby gruz i beton wpasować do nowej formy, to nie masz w sumie 100% pewności, czy nie napsułeś czegoś przy okazji, a testy teraz sprawdzają czy jest poprawnie zepsute :D

Jako side-effect, po takich zmianach wszystko jest na ogół mocno przeorane w wielu miejscach, więc

  • jak ktoś wrzucił swoje zmiany i chcesz sobie walnąć rebase na najnowszą wersję, to może i obaj robiliście niewiele zmian w kodzie aplikacyjnym za to w testach są teraz TAAAAAKIE konflikty
  • bardzo możliwe że nikt nie wyłapie jak narobisz dziadostwa, bo dokładność code review jest często odwrotnie proporcjonalna do ilości zmian

A już zupełnie najgorzej, jak samemu przyłożyłeś łapę do tego stanu rzeczy i nie możesz wytykać paluchami, że to przez innych :/

4

@lambdadziara: czy wszedłbys do samochodu który miał oddzielnie testowane silniki, oddzielnie poduszki powietrzne i oddzielnie wycieraczki i światła ale po zamontowaniu nikt takiego modelu nie testował w całości?

3
lambdadziara napisał(a):

a co z czasem wykonania? jezeli klasa A wola klase B a klasa B jest juz przetestowana w innym tescie? nie mockujac klasy B w tescie klasy A, test A bedzie sie 2 razy dluzej wykonywal

Tylko że kod wykonuje się zwykle szybko. Wolno wykonują się operacje IO. Trzeba mieć naprawdę złożony proces biznesowy żeby kod wykonywał się długo

A co jeśli mamy ten przypadek jeden na tysiąc (milion?) że kod jednak wykonuje się za długo? No to wtedy bym może pomyślał o częściowym mockowaniu tego jednego procesu, ale wcześniej sprawdził czy czegoś nie da się poprawić. No bo jeśli kod wykonuje się za długo żeby go testować, to pewnie zaraz klient będzie narzekać że dla niego też ten kod wykonuje się za długo. A przecież produkcji nie zamokujemy żeby działała szybciej ;)

1

Zgadzam się z OP z drobnymi wyjątkami. Już kilkukrotnie chyba pisałem coś podobnego na forum w różnych dyskusjach na temat testów. Mocki mają swoje zastosowanie, jak najbardziej. Ale pisanie testów jednostkowych gdzie testując "jednostkę" tak naprawdę sprawdzamy czy mock zwraca nam to co powiedzieliśmy mu aby zwracał jest bez sensu.

Ogólnie to ja promuję podział który zastosowaliśmy u mnie w pracy- chociaż również zgadzam się z niektórymi przedmówcami że kwestia nazewnictwa nie ma większego znaczenia.Otóż my stosujemy coś co nazywamy testami jednostkowymi, behawioralnymi (behavior tests) i integracyjnymi. Idąc od lewej do prawej zwiększa się zakres odpowiedzialności takiego testu oraz to jakie komponenty (ich liczba jak i rodzaj) są testowane. W skrócie:

  • Testy jednostkowe- jak sama nazwa wskazuje testują konkretną jednostkę kodu gdzie ma to sens, np. parsery, mapowanie DTO itp. Innymi słowy sprawdzamy dane wyjściowe dla konkretnych danych wejściowych konkretnej metody/klasy, unikając tego aby na kształt danych wejściowych miało wpływ cokolwiek innego. Co za tym idzie prawie w ogóle nie stosujemy tu mocków, bo kod testowany jednostkowo jest raczej "prymitywny" i nie ma albo ma minimalną ilość dodatkowych zależności. Oczywiście zdarzają się wyjątki, ale są to właśnie wyjątki od reguły.N admierne używanie testów jednostkowych jest błędem ponieważ testy te ze swojej natury testują logikę wyjętą z szerszego kontekstu a więc powinny być używane do testowania właśnie takiej logiki która na szerszym kontekście nie polega (np. wymienione już parsery)..

  • Testy behawioralne- działają w obrębie konkretnej sub-domeny (mamy mikroserwisy gdzie każdy serwis odpowiada za konkretną subdomenę) i testują wszystkie **wewnętrzne ** "ruchome części". Każdy pojedynczy test testuje cały workflow konkretnego przypadku, np. wysłanie requesta do endpointu (za pomocą testowego hosta w pamięci, a więc również testujemy przepływ HTTP, walidację itp), wraz z logiką biznesową, i z powrotem do uzyskania odpowiedzi z serwera. Wtedy sprawdzamy spodziewaną odpowiedź oraz skutki uboczne operacji- eventy emitowane przez serwis jak i zapisane/zmodyfikowane encje. Tutaj ważne zastrzeżenie- nie uważamy tego za testy integracyjne ponieważ wszystko dzieje się w procesie (również testowy host jest odpalany w pamięci) a wszelkie zewnętrzne zależności takie jak baza danych, service bus czy zewnętrzne API są podmienione przez testowe implementacje (raczej nie mocki, chociaż czasem użyty jest mock jeśli jest to wygodniejsze). Tych testów mamy najwięcej i skupiamy się na testowaniu zachowań biznesowych systemu a nie na szczegółach implementacjach.

  • Testy integracyjne- właściwe testy integracji elementów całego systemu. W zasadzie robią to co zrobiłby użytkownik przez interfejs, tylko że my robimy to programistycznie. Idea takich testów jest taka że są oddzielnym projektem, odpalanym niezależnie kiedy wysłane są nowe zmiany na środowisko.

Różnica od tego co napisał OP jest taka że w tym przypadku to testy behawioralne są główną siłą napędową testowania systemu. Robią to oczywiście w swoim ograniczonym zakresie, ale zapewniają że cały workflow działa jak należy w obrębie konkretnego serwisu (z wyłączeniem zewnętrznych zależności). Programista pracując nad nową funkcjonalnością w dużej ilości przypadków nawet nie musi odpalać lokalnie systemu ponieważ testy behawioralne przetestują mu zmiany.

Argumenty o dłuższym wykonywaniu testów jest natomiast zabawny. W przypadku naszym testów behawioralnych to każdy pojedynczy test odpala nowego hosta w pamięci i.... i nie ma to większego znaczenia bo wszystkie testy wykonuję się może o jakieś 10 sekund dłużej niż gdybyśmy mieli same testy jednostkowe. 10 sekund w zamian za większą wartość biznesową i zapewnienie że testy testują konkretne scenariusze a nie jedynie ich wyjęte z kontekstu fragmenty to żadna cena. Jeśli 10 czy nawet 30 sekund robi komuś różnicę to powinien zweryfikować swoje podejście do testowania czy programowania ogólnie, jak i poszerzyć wiedzę na temat dostępnych narzędzi. Współczesne IDE i/lub narzędzia do nich mają często możliwość automatycznego testowania w tle, a niektóre nawet uruchamiania tylko tych testów które pokrywają przed chwilą zmodyfikowany kod.

7

@scibi92: generalnie się zgadzam z większością, ale miało być, że "mocki to zło", a jest o testach ogólnie. :P

To nie mocki to zło, co ich złe używanie. Podobnie jest z IoC, ORMami, refleksją, itd. Jedyne obiektywnie złe rzeczy (czyli takie, które nie mają dobrych zastosowań albo nie da się ich dobrze zrobić) to:

  • konfiguracja IoC w XML;
  • frameworki, które mają wczytywać jakąś konfigurację skądś (najczęściej relacyjnej bazy danych pełnej XML) i na tej podstawie generować aplikację od góry do dołu (baza, logiką biznesowa, GUI);
  • wrappery na powszechnie znane frameworki/biblioteki, które zmniejszają ich podstawową funkcjonalność. Typowym przykładem jest antywzorzec repozytorium opakowujący ORMa.

Ja mam teraz taki projekt, w którym na podstawie szablonu mikroserwisu i jakiejś konfiguracji (brzmi jak punkt 2 z listy powyżej, ale to zupełnie nie to) stawia serwisy w chmurze. Używam mocków, aby imitować API do konfigurowania tych serwisów, sprawdzenia czy mój kod tworzy/usuwa odpowiednią liczbę w zależności od konfiguracji. Oczywiście, mógłbym nie używać, ale wtedy testy byłyby znacznie dłuższe. Ponieważ zyskuję na tym czas, to używam mocków. Mógłbym też przepisać nieco kod, ale nie warto inwestować w to w projekcie, który działa, został mu kwartał życia, a do tego są ciekawsze rzeczy do zrobienia.

scibi92 napisał(a):

A teraz wyjaśnię czemu sądze że mocki są nadmiernie stosowane i co zamiast nich, oraz dlaczego często lepsza jest odwrócona piramida testów, tj. podstawą są testy integracyjne(mówię o tych z poziomu aplikacji), a nie jednostkowego (cokolwiek tą jednostką) jest:

A czemu to ma być odwrócona piramida? A nie klepsydra, prostokąt czy koło? ;)
Ktoś to przeczyta, zacznie stosować i w efekcie każdy kalkulator będzie bazował na testach integracyjnych. Tak właśnie powstają kulty kargo.

Wszystko oczywiście zależy od projektu, ale poza jakimś ORMem albo czymś, co bazuje głównie właśnie na zewnętrznych zależnościach, to trudno mi sobie wyobrazić, aby testów integracyjnych faktycznie potrzeba było więcej niż jednostkowych, zwłaszcza jeśli rozsądnie stosuje się TDD. Jeśli wiem jakie jednostka ma wejście i wyjście, to napiszę do tego testy, bo ułatwią mi implementację. Potem może dopiszę testy integracyjne, które wpadną w tą samą ścieżkę, jeśli uznam, że warto, a może nie. Typowe przykłady takich tanich w sensownym testowaniu jednostkowym rzeczy to: mapowanie, strategie, walidacja.

  1. Testy integracyjne nie betonują tak kodu. Problem z testami "niższego rzędu" jest taki że bardzo są blisko kodu, a więc każda mała zmiana powoduje że trzeba zmieniać też również testy. Jak ludzie testują na poziomie klas to jest tragedia komplentna, trochę lepiej jest jak mamy jakieś fasady ( ciekawa prezentacja o tym

W przypadku testów klas owszem, w przypadku testów jednostek już niekoniecznie, bo aby nowa zależność doszła do jednostki, to muszą się zmienić wymagania biznesowe. To zazwyczaj nie dzieje się codziennie.
No i też kwestia tego, czy w ogóle ruszamy stary kod. Jeśli nie, to i jego testy się nie posypią.

  1. Biblioteki do mockowania sa często nie dokońca intuicyjne i często się można pomylić przez to w testach. Dodatkowo gdy testujemy bardzo nisko poziomowo mamy cały zarypany kod jakimiś inicjacjami mocków, i nie sądze żeby to powodowało że takie testy da się pisać/modyfikować szybko, ani nie dodają czytelności.

Za to potem jest bardzo miłe uczucie, gdy uda się to wywalić i zastąpić jakąś sensowną biblioteką do automockowania. Nagle się okazuje, że testy mogą być czytelne. Albo, że można mieć funkcje testujące zamiast procedur. (W sensie, że dane i SUT wpadają w argumentach, a nie są jakimiś polami ustawianymi w jakichś dziwnych metodach/konstruktorach.)

Dodatkowo, zamiast korzystać z mocków możemy robic testowe implementacje interfejsów. Możemy mieć interface CurrencyRatesProvider, implementacje produkcyjną jak jakiś RestCurrencyRatesProvider i jakieś faki. Takie rozwiązania są często bardziej czytelne i zwięzłe niż jakieś Mockito when then/thenReturn.

No niby można, tylko czasem jednak przydaje się śledzenie ile razy mock został wywołany z jakimi parametrami. To też można zaimplementować, ale ja jednak wolę użyć biblioteki.

Co w przypadku integracji z innymi systemami? Na JVM jest taka biblioteka jak Wiremock która umożliwia postawienie stuba z zamockowanym api. Oczywiście jest to mock, ale mock o wiele bardziej inteligenty gdzie my wywołujemy zapytanie HTTP z poziomu testu i pewnie podobne są dostepne dla innych środowisk.

No niby też jest i też ma jakieś swoje zastosowania. Ja jednak zazwyczaj mam jakiegoś klienta HTTP po stronie swojego kodu, którego mogę zamockować szybciej i łatwiej.

3
scibi92 napisał(a):

Jak takie coś można przetestowac z Mockito? No nijak, bo najważniejsze to przetesowanie security i SQL, bo przecież on jest raczej bardziej złożony niż if z dwoma warunkami które mogliśmy zamockować z Mockito.

Raczej nijak, bo jak chcesz przetestować coś dostarczanego przez framework, to nie możesz tego frameworku wymockować. Aczkolwiek nie widzę powodu, żeby w każdym teście sprawdzać logowanie dostarczane przez kogoś innego.

Najlepiej uruchomić baze aplikacje z testowa bazą danych, wykonać odpowienie requesty i zrobić asercję np. na status HTTP/ stan bazy testowej. Tak czy owak należy testować takie rzeczy jak security, zapytania SQL itp. No można by teoretycznie obiekty dostępu do baz danych testować oddzielnie, tylko po co skoro już uruchamiamy test? Możemy przetestować aplikację a nie to czy mockito nam dobrze zamockowało dane.

Jeżeli nie zrobisz z tego absurdu, że najpierw tworzysz obiekty w kodzie, potem je zapisujesz do bazy, a potem odczytujesz je wszystkie z tej samej bazy i zwracasz dalej jako obiekty, to ma sens. Tylko będzie wolniejsze.

scibi92 napisał(a):
  1. Testy integracyjne nie betonują tak kodu. Problem z testami "niższego rzędu" jest taki że bardzo są blisko kodu, a więc każda mała zmiana powoduje że trzeba zmieniać też również testy. Jak ludzie testują na poziomie klas to jest tragedia komplentna, trochę lepiej jest jak mamy jakieś fasady ( ciekawa prezentacja o tym

Jak testy jednostkowe betonują kod, to łamiesz S or L z solida. Testy jednostkowe mają testować kontrakt, tak samo jak integracyjne.

scibi92 napisał(a):
  1. Biblioteki do mockowania sa często nie dokońca intuicyjne i często się można pomylić przez to w testach. Dodatkowo gdy testujemy bardzo nisko poziomowo mamy cały zarypany kod jakimiś inicjacjami mocków, i nie sądze żeby to powodowało że takie testy da się pisać/modyfikować szybko, ani nie dodają czytelności.

Jakoś w kodzie produkcyjnym ludzie pamiętają o wydzieleniu funkcji, a w kodzie testowym już przestają myśleć. Wystarczy opakować dan testowe w czytelnego DSL-a i problem jest w większości załatwiony.

scibi92 napisał(a):
  1. Szybkość/koszt testów - mówi się że testy integracyjne są wolne. Otóż nie do końca. Jesli korzystamy z takiego TestCointaners i startujemy testy to sam start jest rzeczywiście powolniejszy, ale po starcie testy bedą się raczej wykonywac szybko. Za to zaoszczędzimy czas na tym że testy nie są tak zabetonowane. Dlatego jeśli dla danej funkcji programu mamy 5 przypadków testowych, testy integracyjne mają sens.

Czyli są wolne, ale jak mamy mało testów, to jakoś przeżyjemy. No tak, co innego, jak tych testów jest tysiące. Dodatkowo tu mówisz o TestContainers, poproszę o biblioteczkę do SNS-a, S3, szyny, lambdy, do tego wszystko ma działać w pythonie i perlu. A, i jeszcze Redshift, wołany ze scali ze sparka.

Ani to nie będzie szybkie, ani przyjemne, ani w szczególności nie potrzebuję testować tych klocków w każdym teście mojej logiki biznesowej.

scibi92 napisał(a):

Dodatkowo, zamiast korzystać z mocków możemy robic testowe implementacje interfejsów. Możemy mieć interface CurrencyRatesProvider, implementacje produkcyjną jak jakiś RestCurrencyRatesProvider i jakieś faki. Takie rozwiązania są często bardziej czytelne i zwięzłe niż jakieś Mockito when then/thenReturn.

"Często" czyli jak często konkretnie? Raz na sto? Sto razy na sto? Nie widzę przewagi we własnej implementacji nad zrobieniem mocka z jednym thenReturn i wrzuceniem go do nazwanej funkcji (znaczy widzę przewagę, to drugie jest jeszcze prostsze).

scibi92 napisał(a):

Co w przypadku integracji z innymi systemami? Na JVM jest taka biblioteka jak Wiremock która umożliwia postawienie stuba z zamockowanym api. Oczywiście jest to mock, ale mock o wiele bardziej inteligenty gdzie my wywołujemy zapytanie HTTP z poziomu testu i pewnie podobne są dostepne dla innych środowisk.

No i czym to się różni od mocka na mockito? Tym, że żądanie leci po sieci? Po co mam testować serializację i deserializację jsona na każdym żądaniu, ani to mi nic nie daje, ani nie różni się to od gołego mocka, a jeszcze muszę kolejny klocek konfigurować. Pomijając już fakt, że przy wołaniu innych systemów zazwyczaj nie API jest problemem, tylko warstwy bezpieczeństwa po drodze, których wireomockiem też nie testujesz.

scibi92 napisał(a):

Kiedy tak naprawde sens testowania jednostkowego? W przypadku gdy chcemy wyodrębnić jakiś bardziej złożony algorytm który może mieć tez wiele różnych warunków, na przykład może to być kalkulator promocji z których część włącza się do innych, a część nie, a łącznie dla danego zamówienia aktywnych będzie na przykład 3 albo i 4 (jak dwie ksiązki w cenie jednej, promocja z okazji black friday, zniższka powyżej iluś tam złotych i jakiś kod rabatowy do tego).

Czyli ma sens w logice biznesowej.

scibi92 napisał(a):

Gdybym miał napisać TL;DR - po co tak naprawde stosowac mocki skoro i tak chcemy sprawdzić działanie aplikacji, w tym na przykład security, zapytania SQL, itp.

Jeżeli mógłbym postawić prawdziwą infrastrukturę produkcyjną, odpalić testy niezależnie od nikogo, równolegle, bez dostępu do internetu, w ciągu jednej milisekundy, to prawdopodobnie tak bym robił, chociaż nie wiem, czy byłby sens tak żonglować obiektami w teście (ale pewnie ładnym DSL-em byłbym w stanie przykryć inicjalizację wszystkich klocków dookoła). Jeszcze nie trafiłem do nietrywialnej aplikacji, gdzie byłbym w stanie tak zrobić, a że szkoda mi czasu na czekanie, aż jakieś zmyślniejsze mocki się podniosą (które i tak nie są produkcyjnym klockiem), to efekty uboczne mockuję w testach jednostkowych, a w testach integracyjnych robię infrastructure as a code i niech już sobie maszynka do testów młóci godzinami (i tak trzeba zrobić load testy, więc dodatkowe testy integracyjne bardzo nie spowolnią całości). Testy mam szybkie, nie muszę zabijać komputera w celu przetestowania przeliczania ceny koszyka, a jednocześnie mam pewność, że aplikacja działa z klockami produkcyjnymi. Jeżeli nie odpalasz testów integracyjnych na prawdziwych klockach, to rozumiem, że możesz chcieć testować z wiremockami czy innymi zabawkami, ale jeżeli odpalasz, to ja nie widzę żadnego powodu, żeby jeszcze testy jednostkowe spowalniać i debugować, bo gdzieś tam zmieniła się deserializacja odpowiedzi z innego serwisu.

2

@somekind

No niby można, tylko czasem jednak przydaje się śledzenie ile razy mock został wywołany z jakimi parametrami. To też można zaimplementować, ale ja jednak wolę użyć biblioteki.

Wiremock (i inne podobne rozwiązania) robią to automatycznie i można spokojnie wyciągnąć z nich listę requestów/responsów ;)

No niby też jest i też ma jakieś swoje zastosowania. Ja jednak zazwyczaj mam jakiegoś klienta HTTP po stronie swojego kodu, którego mogę zamockować szybciej i łatwiej.

Trochę zaklinanie rzeczywistości, bo nie widzę tam nic ani łatwiejszego ani krótszego - zamieniasz jedną linijkę (konfiguracja co zmockowany klient ma zwrócić) na jedną linijkę (konfiguracja co wiremock ma zwrócić). A w praktyce to już w ogóle, bo jak opakujesz te testy DSLem i w kodzie testu masz tylko withServiceXReturning(ABC) i nawet nie wiesz czy tam pod spodem dzieje się mockowanie klienta czy konfigurowanie wiremocka (i dobrze, bo nie powinno cię to obchodzić kiedy piszesz test).

@Afish

Dodatkowo tu mówisz o TestContainers, poproszę o biblioteczkę do SNS-a, S3, szyny, lambdy

Da się ;) Codewise ma zestaw takich na większość AWSowych serwisów, część jest publiczna w mavenie https://mvnrepository.com/artifact/pl.codewise.canaveral ale niestety sporo jest proprietary. Niemniej mimo wszystko da się i wbrew pozorom nie jest to wcale jakieś wybitnie skomplikowane do osiągnięcia.

ja nie widzę żadnego powodu, żeby jeszcze testy jednostkowe spowalniać i debugować, bo gdzieś tam zmieniła się deserializacja odpowiedzi z innego serwisu.

Ja nie widzę powodu żeby odpalać testy jednostkowe które w praktyce niczego mi nie mówią. Bo mogą być czerwone mimo, ze aplikacja działa, bo jakiś mały refaktor sprawił że wołamy metodę X a nie metodę Y. I mogą też być zielone, mimo ze aplikacja nie działa, bo popsułem właśnie konfiguracje security albo komunikacje z jakimś innym serwisem (ale klient zmockowany, więc test zielony). To ja już wolę dołożyć te 2 sekundy do wykonania testu, ale przynajmniej mam 99% pewności ze to działa i e2e się raczej nie wywalą.
No ale jak ktoś woli puścić testy o 2 sekundy szybciej, a potem czekać X czasu żeby się dowiedzieć dopiero z e2e że wysyła zły header w kliencie, to jego sprawa, nie mnie to oceniać, może komuś płacą od godziny?

Ja tu nikogo nie będę przekonywać, każdy pisze tak jak mu wygodnie. Też kiedyś namiętnie pisałem unit testy i żyłowałem 100% pokrycia. Ale potem popatrzyłem na to i doszedłem do wniosku że te testy nic mi nie dają. Nie dają mi żadnej pewności że to wszystko działa, bo jest cała masa rzeczy które są wyłączone z testowania i w praktyce i tak dopiero e2e powiedzą mi czy czegoś nie zepsułem. W takiej sytuacji to ja jednak wolę przenieść tak dużo realnego testowania bliżej developmentu jak to tylko możliwe, nawet jeśli kosztuje mnie to 5 sekund dłużej na wykonanie testów.

Może ktoś pisze lepsze unit testy niż ja i dają mu jakąś pewność :) Ja jeszcze takich nie widziałem w swojej karierze, ale wierzę że się da.

1
scibi92 napisał(a):

@lambdadziara: czy wszedłbys do samochodu który miał oddzielnie testowane silniki, oddzielnie poduszki powietrzne i oddzielnie wycieraczki i światła ale po zamontowaniu nikt takiego modelu nie testował w całości?

Ta analogia jest kiepska.
Myślisz, że silnik samochodu jest projektowany i testowany razem z całym samochodem? Owszem, wymiary samochodu mogą mieć wpływ na parametry silnika - duży samochód potrzebuje więcej mocy, mały ma mniej miejsca pod maską, ale poza ogólnymi założeniami sam samochód nie ma wpływu na projekt silnika. Zobacz, ile modeli samochodów produkuje np. VAG, a ile mają do nich modeli silników. Zauważ, że pewne silniki (większość?) mogą być montowane w różnych samochodach.
Pióro wycieraczki to akurat standard.
Poduszki powietrzne też zwykle nie są projektowane pod konkretny model.

A wracając do tematu programowania.
Zewnętrzne bilbioteki, których nie przykrywasz testami, są zwykle przetestowane przez autora oraz tysiące użytkowników. Jeśli taka biblioteka jest w miarę popularna, to testowanie jej nie ma sensu. Ponadto na etapie tworzenia funkcjonalności w kodzie testujesz ją "ręcznie", a więc przy okazji sprawdzasz, czy ta zewnętrzna biblioteka zachowuje się zgodnie z oczekiwaniami. Ewentualne kłopoty mogą pojawić się przy aktualizacji wersji; jednak aktualizacje zwykle nie są robione często, a w firmie powinien być tester, który przeklika aplikację (smoke tests). Oczywiście jest tu możliwy scenariusz, kiedy podbicie wersji spowoduje błędy dopiero na produkcji, ale nie oszukujmy się, może się to zdarzyć także przy automatycznych testach dowolnego typu.
Testy jednostkowe sprawdzają nie tylko same klocki (jednostki), ale też jak te klocki łączą się z innymi klockami (za pomocą mocków). Wracając do Twojej analogii: budujesz ramę, mockujesz, że jest tu silnik o takich rozmiarach, takiej mocy itp i sprawdzasz, że rama wytrzyma. Nie potrzebujesz do tego silnika, tylko jego "output" - skrajne siły, z jakimi działa na elementy, które go trzymają.

Oczywiście najważniejszy jest zdrowy rozsądek, złe/zbyt dokładne przykrycie kodu/funkcjonalności jakimikolwiek testami może spowodować, że większość pracy będzie polegała na poprawianiu testów (acz wtedy trzymanie się OCP powinno to rozwiązać).

0
Shalom napisał(a):

Da się ;) Codewise ma zestaw takich na większość AWSowych serwisów, część jest publiczna w mavenie https://mvnrepository.com/artifact/pl.codewise.canaveral ale niestety sporo jest proprietary. Niemniej mimo wszystko da się i wbrew pozorom nie jest to wcale jakieś wybitnie skomplikowane do osiągnięcia.`

Klawo, to jak tego użyję z perla i gdzie jest tam redshift do testowania ze sparkiem? Czasami się da, tylko po co mam tak kombinować, jak ciągle jest to mock, któremu nie mogę do końca ufać?

Shalom napisał(a):

Ja nie widzę powodu żeby odpalać testy jednostkowe które w praktyce niczego mi nie mówią. Bo mogą być czerwone mimo, ze aplikacja działa, bo jakiś mały refaktor sprawił że wołamy metodę X a nie metodę Y.

Jak testy są napisane źle, to nic dziwnego, że nie działają poprawnie, ale czy to wina testów czy programistów? wołamy metodę X a nie metodę Y - to jest w ogóle zabawne, bo pokazuje, że mockito jest na 95% używany źle. Test jednostkowy ma testować kontrakt, jeżeli ten jest spełniony, to nie ma znaczenia, czy wołana była metoda X czy Y. Chyba, że ktoś pisze testy do metod i wszędzie robi assertWasCalled, ale to znowu wina programisty.

Shalom napisał(a):

I mogą też być zielone mimo ze aplikacja nie działa, bo popsułem właśnie konfiguracje security albo komunikacje z jakimś innym serwisem (ale klient zmockowany, więc test zielony)

Ale czy w przypadku zepsutej konfiguracji security potrzebujesz setki czerwonych testów, czy jeden wystarczy? Nie widzę potrzeby testowania tego samego fragmentu za każdym razem, jeden raz jest jak znalazł.

Shalom napisał(a):

To ja już wolę dołożyć te 2 sekundy do wykonania testu, ale przynajmniej mam 99% pewności ze to działa i e2e się raczej nie wywalą.

Testów e2e i tak nie unikniesz, więc po co płacić sekundami teraz.

Shalom napisał(a):

No ale jak ktoś woli puścić testy o 2 sekundy szybciej, a potem czekać X czasu żeby się dowiedzieć dopiero z e2e że wysyła zły header w kliencie, to jego sprawa, nie mnie to oceniać, może komuś płacą od godziny?

Tak jak pisałem, problem najczęściej jest z warstwami bezpieczeństwa, a nie z jakąś serializacją czy nagłówkiem obsługiwanym przez framework. Ja nie potrzebuję na każdym kroku sprawdzać, czy biblioteki pod spodem dobrze obsłużyły żądanie, to załatwia za mnie test integracyjny na samym końcu, który strzela do prawdziwego serwisu, a nie do jakiegoś mocka. Pomijam już fakt, że takie nagłówki konfiguruję raz, więc po co mam każdego dnia sprawdzać, czy tam się nic nie wywaliło i tracić te sekundy.

1

Testów e2e i tak nie unikniesz, więc po co płacić sekundami teraz.

  1. Bo mogę to sobie debugować lokalnie, w dość "realistycznych" ale jednocześnie kontrolowanych warunkach.
  2. Bo wiem że na 95% działa i mogę klepać dalej, bez specjalnego ryzyka że zaraz będę znowu musiał orać napisany kod, bo jednak z e2e przyjdzie że nie działa. Zresztą to taki sam argument jak za pisaniem testów w ogóle -> bo chcesz mieć szybki feedback że nie popsułeś czegoś. Unit testy dają ultra-szybki feedback że może działa na 75%, testy o których tu piszemy dają trochę wolniejszy feedback ze może działa na 95%, a e2e dają bardzo wolny feedback że może działa na 99%. Ja osobiście z chęcią przehandluje te kilka sekund z życia za te dodatkowe %.
  3. Raptem wczoraj miałem głupi problem z błędnym ustawianiem content-length na długość stringa (z jakimśtam kodowaniem) zamiast rozmiar wysyłanych bajtów. Zanim doszedłem gdzie jest problem (bo był taki trochę heisenbug występujący tylko jak gdzieś leciały binarne dane i były obcinane) to potrzebowałem odpalić test pewnie kilkanaście razy, jak nie więcej, do tego z debugiem. Oczywiście gdybym mockował klienta to wszystko byłoby zielone :D A gdybym miał to debugować czekając za każdym razem na logi z e2e to robiłby bym to jeszcze przez następne kilka dni.

Ale czy w przypadku zepsutej konfiguracji security potrzebujesz setki czerwonych testów, czy jeden wystarczy? Nie widzę potrzeby testowania tego samego fragmentu za każdym razem, jeden raz jest jak znalazł.

Tylko że to nic nie kosztuje, a możesz testować całe use-case. Przecież do e2e też nie robisz tak, że tylko jeden test sprawdza uprawnienia a resztę testów puszczasz jako admin, no bo przecież nie ma sensu testowania security w więcej niż jednym teście. Chyba ze tak robisz? :)

3
Shalom napisał(a):
  1. Bo mogę to sobie debugować lokalnie, w dość "realistycznych" ale jednocześnie kontrolowanych warunkach.

Jak musisz debugować framework a nie logikę biznesową, to tylko współczuję.

Shalom napisał(a):
  1. Bo wiem że na 95% działa i mogę klepać dalej, bez specjalnego ryzyka że zaraz będę znowu musiał orać napisany kod, bo jednak z e2e przyjdzie że nie działa.

Ale co Ty piszesz, że jakieś nagłówki http miałyby wymusić oranie kodu?

Shalom napisał(a):

Zresztą to taki sam argument jak za pisaniem testów w ogóle -> bo chcesz mieć szybki feedback że nie popsułeś czegoś. Unit testy dają ultra-szybki feedback że może działa na 75%, testy o których tu piszemy dają trochę wolniejszy feedback ze może działa na 95%, a e2e dają bardzo wolny feedback że może działa na 99%. Ja osobiście z chęcią przehandluje te kilka sekund z życia za te dodatkowe %.

plus kilka godzin na konfigurację (plus kilka dni na zrobienie tego redshifta do sparka).

Shalom napisał(a):
  1. Raptem wczoraj miałem głupi problem z błędnym ustawianiem content-length na długość stringa (z jakimśtam kodowaniem) zamiast rozmiar wysyłanych bajtów. Zanim doszedłem gdzie jest problem (bo był taki trochę heisenbug występujący tylko jak gdzieś leciały binarne dane i były obcinane) to potrzebowałem odpalić test pewnie kilkanaście razy, jak nie więcej, do tego z debugiem. Oczywiście gdybym mockował klienta to wszystko byłoby zielone :D A gdybym miał to debugować czekając za każdym razem na logi z e2e to robiłby bym to jeszcze przez następne kilka dni.

Czyli mam tracić codziennie czas na testy i utrzymywać dodatkowe klocki do nich po to, żeby raz na rok naprawić jakiś błąd we frameworku do weba? Dzięki, to ja już wolę klepać logikę biznesową.

Shalom napisał(a):

Tylko że to nic nie kosztuje, a możesz testować całe use-case.

No jak nic nie kosztuje? To pokaż mi wreszcie tego redshifta do sparka i szynę do perla.

Shalom napisał(a):

Przecież do e2e też nie robisz tak, że tylko jeden test sprawdza uprawnienia a resztę testów puszczasz jako admin, no bo przecież nie ma sensu testowania security w więcej niż jednym teście. Chyba ze tak robisz? :)

Hah, no w naciąganiu do przesady masz praktykę :) Mam tysiące testów jednostkowych i dziesiątki testów integracyjnych, gdzie każdy integracyjny testuje inną integrację (i w efekcie na przykład uprawnienia do innego API). Po co miałbym odpalać to samo API dla tych samych przypadków, to nie wiem, jeszcze jakby to trwało w sumie milisekundę, to mógłbym się bawić, ale póki co takie szybkie to nie jest.

Większość (wszystkie?) przemysłów działa na zasadzie komponentów testowanych głównie w izolacji i potem klejonych razem do kupy i testowanych integracyjnie. Nie widzę powodu, dlaczego miałbym bawić się w jakieś dodatkowe klocki do testów logiki biznesowej, szczególnie jeżeli te klocki są jakimiś bazami w pamięci czy innymi zmyślnymi mockami. To jest wpadanie ze skrajności w skrajność, ludzie nadużywali mockito i robili assertWasCalled na każdym kroku, to potem spanikowali i poszli do przesady w testowanie deserializacji jsona za każdym razem, zamiast ogarnąć klasy abstrakcji na każdym styku integracyjnym. Rozumiem, że daje Wam to większą pewność, że aplikacja działa, ale taką samą pewność można mieć używając mockito, tylko trzeba myśleć, co się robi.

1
Shalom napisał(a):

Wiremock (i inne podobne rozwiązania) robią to automatycznie i można spokojnie wyciągnąć z nich listę requestów/responsów ;)

No wiremock i inne mocki tak. Ale własna testowa implementacja interfejsu już tego nie ma. A jak mam dopisywać takie tricki do własnej implementacji, to wolę już znanej biblioteki użyć. Przynajmniej nikt, kto zobaczy ten kod nie będzie miał WTFa, czemu ktoś postanowił wynajdować koło na nowo.

Trochę zaklinanie rzeczywistości, bo nie widzę tam nic ani łatwiejszego ani krótszego - zamieniasz jedną linijkę (konfiguracja co zmockowany klient ma zwrócić) na jedną linijkę (konfiguracja co wiremock ma zwrócić).

Pod warunkiem, że mam już zależność do wiremocka. Zazwyczaj jej nie mam, więc zamiast ją dodawać, lepiej użyć czegoś, co już jest - o ile w ogóle trzeba. W końcu, jak sam zauważyłeś, nie zyska się tym ani na łatwości ani na długości kodu.
Jedyna zaleta tego byłaby, gdybym używał komunikacji HTTP bezpośrednio, bez żadnej warstwy abstrakcji dopasowanej do konkretnego serwisu. A to z kolei wydaje mi się dość dziwnym podejściem i raczej nie będę szedł w takim kierunku.

Wartości testu integracyjnego to też nie ma, nie jest lepsze od normalnych mocków, więc jak dla mnie brzmi trochę jak buzzword do CV. Za parę lat pewnie zaczną powstawać artykuły, że "wiremock considered harmful".

Ja nie widzę powodu żeby odpalać testy jednostkowe które w praktyce niczego mi nie mówią. Bo mogą być czerwone mimo, ze aplikacja działa, bo jakiś mały refaktor sprawił że wołamy metodę X a nie metodę Y.

No to są wtedy testy klas, a nie testy jednostkowe.

I mogą też być zielone, mimo ze aplikacja nie działa, bo popsułem właśnie konfiguracje security albo komunikacje z jakimś innym serwisem (ale klient zmockowany, więc test zielony).

No i w czym się różni wiremock w takiej sytuacji? Test też będzie fałszywie zielony.

Też kiedyś namiętnie pisałem unit testy i żyłowałem 100% pokrycia. Ale potem popatrzyłem na to i doszedłem do wniosku że te testy nic mi nie dają. Nie dają mi żadnej pewności że to wszystko działa, bo jest cała masa rzeczy które są wyłączone z testowania i w praktyce i tak dopiero e2e powiedzą mi czy czegoś nie zepsułem.

No jeśli ktoś chciałby unit testami sprawdzać, czy wszystko działa, to ma po prostu od początku złe podejście do testowania i to może być przyczyną klęski.
Testy jednostkowe mogą sprawdzić dające się izolować jednostki. A od przetestowania działania całości aplikacji są już testy integracyjne.

0

@somekind: mam klienta restowego który pobiera json i zwraca obiekt domenowy. Twój mock nie sprawdzi kodu który to robi, a jak mam WireMocka to mój kod będzie sprawdzony.
@Afish a co ma framework do tego czy dobrze napisałem SQLa?

0
scibi92 napisał(a):

@Afish a co ma framework do tego czy dobrze napisałem SQLa?

Framework dotyczył kodu webowego.

0

W frameworku webowym możesz na przykład zapomnieć ustawić sprawdzanie ról dla danego użytkownika na danym endponcie

0
scibi92 napisał(a):

W frameworku webowym możesz na przykład zapomnieć ustawić sprawdzanie ról dla danego użytkownika na danym endponcie

Czyli chcesz mieć test, który sprawdza tę konkretną część aplikacji. Piszesz jeden test, najlepiej z listą końcówek i ról do sprawdzenia, odpalasz raz i gotowe. Ewentualnie zakładasz, że test end to end na samym końcu sprawdzi to pośrednio i nic więcej nie robisz.

1

@Afish

Jak musisz debugować framework a nie logikę biznesową, to tylko współczuję.

Chce debugować aplikacje w możliwe jak najbardziej realistycznych warunkach, bo inaczej zwyczajnie nie ma to sensu.

Ale co Ty piszesz, że jakieś nagłówki http miałyby wymusić oranie kodu?

To nie muszą być żadne nagłówki. Może dodałem nieserializowane pole do DTO i pisze sobie dalej wesoło kod korzystając z tego pola, licząc że wszystko działa, bo mój mock client jest szczęśliwy. Takich przykładów można mnożyć. Fakt pozostaje faktem, ze wyłączasz część aplikacji spod testów i ta część może sie sypać, o czym dowiesz ze znacznie później.

plus kilka godzin na konfigurację (plus kilka dni na zrobienie tego redshifta do sparka).

Co robisz słownie raz per projekt, a może w ogóle raz per firma? :) Nie mówię już o tym że w realnych zastosowaniach to raczej 15 minut a nie kilka godzin. Http mock dla mojego SSO to jest kilkadziesiąt linijek.

Rozumiem, że daje Wam to większą pewność, że aplikacja działa, ale taką samą pewność można mieć używając mockito, tylko trzeba myśleć, co się robi.

Chciałbym to choć raz w życiu zobaczyć. Takie testy których nie można trywialnie sprowokować do false positive/false negative. Bo jeśli można, to dla mnie ich wartość jest zerowa, bo w niczym mnie nie upewniają.

@somekind

bez żadnej warstwy abstrakcji dopasowanej do konkretnego serwisu

No tak, bo tego klienta to nie ma potrzeby testować? Ale bardziej ogólnie: ja osobiście wolę testować więcej mojego kodu a mniej kodu mockito. Im więcej mojego kodu wykonuje test, tym lepiej, bo mniejsza szansa że gdzieś w tym kodzie jest bug. Skoro puściłem 100 testów które wysłały 100 różnych payloadów tym klientem, to mam dużo większą pewność, że nie ma tam błędu.
Analogicznie jeśli te 100 requestów przeszło przez kontroler i jakiś dispatcher to znów mam dużo większą pewność że to faktycznie działa.

No i w czym się różni wiremock w takiej sytuacji? Test też będzie fałszywie zielony.

Nie będzie bo i czemu miałby być? Jak popsułeś gdzieś komunikacje to się wysypie, tak jak powinien.

Testy jednostkowe mogą sprawdzić dające się izolować jednostki. A od przetestowania działania całości aplikacji są już testy integracyjne.

Mówimy tu teraz o testach które są w stanie sprawdzić więcej kodu aplikacji, praktycznie takim samym nakładem pracy i czasu.

Oczywiście można mieć jakąś złożoną logikę do przetestowania w izolacji, nie wiem, jakaś maszyna stanów, albo parser. Nie widzę problemu że tak robić! Ale w praktyce takich kawałków kodu jest niewiele. Przecież sam tytuł wątku mówi o odwróconej piramidzie a nie nie piszcie testów jednostkowych.

0
Shalom napisał(a):

To nie muszą być żadne nagłówki. Może dodałem nieserializowane pole do DTO i pisze sobie dalej wesoło kod korzystając z tego pola, licząc że wszystko działa, bo mój mock client jest szczęśliwy.

I jest szczęśliwy, bo to nie jest jego problem. To sprawdzasz na integracji.

Shalom napisał(a):

Takich przykładów można mnożyć. Fakt pozostaje faktem, ze wyłączasz część aplikacji spod testów i ta część może sie sypać, o czym dowiesz ze znacznie później.

Dowiesz się przy odpaleniu testu, czyli wtedy, gdy trzeba. Jak piszę logikę biznesową, to odpalam testy do tej logiki, nie przejmuję się, że gdzieś daleko wysypała się jakaś serializacja, na to przyjdzie czas potem. A jak piszę nową klasę, to odpalam wszystkie testy, które mogą ją dotknąć, więc jak jest ona skopana, to dowiem się od razu.

Shalom napisał(a):

Co robisz słownie raz per projekt, a może w ogóle raz per firma? :) Nie mówię już o tym że w realnych zastosowaniach to raczej 15 minut a nie kilka godzin. Http mock dla mojego SSO to jest kilkadziesiąt linijek.

Ja ciągle czekam na tego redshifta ze sparkiem.

Shalom napisał(a):

Chciałbym to choć raz w życiu zobaczyć. Takie testy których nie można trywialnie sprowokować do false positive/false negative. Bo jeśli można, to dla mnie ich wartość jest zerowa, bo w niczym mnie nie upewniają.

Jeżeli szukasz testów jednostkowych, które wysypią się z powodu serializacji, to szukasz czegoś, co nigdy nie powinno mieć miejsca. Od serializacji są testy integracyjne.

Shalom napisał(a):

No tak, bo tego klienta to nie ma potrzeby testować? Ale bardziej ogólnie: ja osobiście wolę testować więcej mojego kodu a mniej kodu mockito. Im więcej mojego kodu wykonuje test, tym lepiej, bo mniejsza szansa że gdzieś w tym kodzie jest bug. Skoro puściłem 100 testów które wysłały 100 różnych payloadów tym klientem, to mam dużo większą pewność, że nie ma tam błędu.
Analogicznie jeśli te 100 requestów przeszło przez kontroler i jakiś dispatcher to znów mam dużo większą pewność że to faktycznie działa.

Zgadza się, ale dobre testy nie potrzebują większej liczby przypadków, tylko pokrycia klas abstrakcji.

Shalom napisał(a):

Mówimy tu teraz o testach które są w stanie sprawdzić więcej kodu aplikacji, praktycznie takim samym nakładem pracy i czasu.

To nie jest taki sam nakład pracy i czasu. Chyba, że wreszcie dostanę tego redshifta ze sparkiem (wiem, irytujący jestem).

3
Afish napisał(a):

Jeżeli szukasz testów jednostkowych, które wysypią się z powodu serializacji, to szukasz czegoś, co nigdy nie powinno mieć miejsca. Od serializacji są testy integracyjne.

Chyba pół tematu jest o tym, żeby tego nie rozróżniać

1

@Afish:

Jak piszę logikę biznesową, to odpalam testy do tej logiki, nie przejmuję się, że gdzieś daleko wysypała się jakaś serializacja, na to przyjdzie czas potem.

Widzisz, to nas różni, bo ja wolę pisać testy dla funkcji systemu a nie testy kawałka kodu (poza sytuacjami ekstremalnymi jak ten parser czy maszyna stanów, gdzie może faktycznie chciałbym upewnić się że dany kawałek kodu robi to co bym chciał).

redshifta ze sparkiem

Klasyczny https://en.wikipedia.org/wiki/Straw_man ale nie widzę przeszkód żeby na wzór testcontainers zrobić sobie dockera z takim setupem. Pewnych złożonych komponentów, jak jakaś baza danych, nie da się opędzić prostą zaślepką, nic na to nie poradzisz. Ale to jest trochę komiczne, bo narzekasz ze twojego redshifta ze sparkiem nie da się tak przetestować, ale twoje własne unit testy też go nie testują xD

1
danek napisał(a):
Afish napisał(a):

Jeżeli szukasz testów jednostkowych, które wysypią się z powodu serializacji, to szukasz czegoś, co nigdy nie powinno mieć miejsca. Od serializacji są testy integracyjne.

Chyba pół tematu jest o tym, żeby tego nie rozróżniać

To nie jest prawda objawiona, w szczególności różnica jest zauważalna gołym okiem, więc dlaczego udawać, że jej nie ma? Z jednej strony jest multum tematów o tym, że domenę trzeba separować, żeby była testowalna, ale jak przechodzimy do tematu testów to uważacie, że te samą domenę trzeba testować wraz ze wszystkimi zależnościami dookoła.

2

@Afish jeśli zadowala Cię kontener ze stackiem AWS, to czego brakuje https://hub.docker.com/r/localstack/localstack?

Note: Starting with version 0.11.0, all APIs are exposed via a single edge service, which is accessible on http://localhost:4566 by default (customizable via EDGE_PORT, see further below).

    ACM
    API Gateway
    CloudFormation
    CloudWatch
    CloudWatch Logs
    DynamoDB
    DynamoDB Streams
    EC2
    Elasticsearch Service
    EventBridge (CloudWatch Events)
    Firehose
    IAM
    Kinesis
    KMS
    Lambda
    Redshift
    Route53
    S3
    SecretsManager
    SES
    SNS
    SQS
    SSM
    StepFunctions
    STS

1 użytkowników online, w tym zalogowanych: 0, gości: 1