Jakie testy piszecie?

2

Witam,

W ramach pisania hobbystycznie nowego projektu zaczalem sie zastanawiac nad podejsciem do testowania. W internecie mozna znalezc wiele informacji na temat roznych rodzajow testow i o jakim by sie nie czytalo, wydawac by sie moglo ze wlasnie ten konkretny rodzaj jest najwazniejszy, "sluszny" itp. Ostatnio coraz czesciej spotykam sie z podejsciem aby testowac dany "feature", tj. w ramach jednego testu (ktory moze miec kilka iteracji z roznymi danymi wejsciowymi) zostaje pokryty caly workflow z tymze testem zwiazany. W pewnym sensie niweluje to potrzebe testow jednostkowych (a moze i nie?) jako ze poszczegolne jednostki zostaja pokryte przez ten test. Czy Wy stosujecie takie testy (prywatnie lub w pracy)? A moze cos calkowicie innego?

Tutaj tez pojawia sie pytanie- czy np. testujac jakis kolejny modul API, np. zwrajacy liste zamowien, testujecie taki modul bezposrednio go wywolujac jak metode (np. wywolujac akcje kontrolera w ASP Web API) czy tez stawiacie testowy host, a wiec przy okazji testujecie takie elementy jak naglowki requestow?

Linki do ciekawych materialow mile widziane.

Pozdrawiam i zapraszam do dyskusji.

7
Aventus napisał(a):

Ostatnio coraz czesciej spotykam sie z podejsciem aby testowac dany "feature", tj. w ramach jednego testu (ktory moze miec kilka iteracji z roznymi danymi wejsciowymi) zostaje pokryty caly workflow z tymze testem zwiazany. W pewnym sensie niweluje to potrzebe testow jednostkowych (a moze i nie?) jako ze poszczegolne jednostki zostaja pokryte przez ten test. Czy Wy stosujecie takie testy (prywatnie lub w pracy)? A moze cos calkowicie innego?

Ja tak robię od zawsze i nazywam to testem jednostkowym. W ten sposób testuję jednostkę, czyli taką część kodu, która daje jakąś wartość. Nie testuję każdej klasy oddzielnie, bo to po prostu bezdennie głupia strata czasu.

Tutaj tez pojawia sie pytanie- czy np. testujac jakis kolejny modul API, np. zwrajacy liste zamowien, testujecie taki modul bezposrednio go wywolujac jak metode (np. wywolujac akcje kontrolera w ASP Web API) czy tez stawiacie testowy host, a wiec przy okazji testujecie takie elementy jak naglowki requestow?

Jeśli testuję endpoint na postawionym serwerze, to jest to dla mnie już test integracyjny, a nie jednostkowy. I takie testy też stosuje (a w niektórych przypadkach przede wszystkim takie), bo to jedyna możliwość faktycznego sprawdzenia, czy aplikacja działa. Bo co z tego, że testy jednostkowe pokażą, że logika przetwarzania danych jest prawidłowo zaimplementowana, skoro binding HTTP nie parsuje daty, albo pobierana jest wartość ze złego headera?

0
somekind napisał(a):

Jeśli testuję endpoint na postawionym serwerze, to jest to dla mnie już test integracyjny, a nie jednostkowy.

Dla wyjasnienia, chodzi mi to testowanie endpointa w pamieci, np. https://www.nuget.org/packages/Microsoft.AspNetCore.TestHost

0

Z tego, co rozumiem z opisu, to to nie jest w pamięci tylko po prostu lekki serwer aplikacji.

1

Nie trzeba stawiać serwera (tzn czegoś nasłuchującego na porcie) by przetestować końcówki RESTowe (wliczając w to parsowanie parametrów, nagłówków itd). Ciekawych odsyłam do dokumentacji Akki HTTP: https://doc.akka.io/docs/akka-http/current/routing-dsl/testkit.html

1

Nie lubię absurdalnych podziałów na różne rodzaje testów.
Test to test: dla danego wejścia sprawdzamy wyjście.

Nieważne czy to a) metoda typu 2+2 , b) czy też system do rozpatrywania kredytów, gdzie sprawdzamy, że jeżeli zarejestrowany wniosek kredytowy nie zostanie rozpatrzony w 2 tygodnie to trafia na czerwono na top listy do kierownika.
Mimo, ze maszyneria w przypadku b) jest bardziej skomplikowana, to ten test jest zupełnie normalnie postaci given - when - then (*). Oczywiście, żeby coś takiego utrzymać trzeba troszkę zainwestować.

Testowanie RESTów, tak jak pisze wyżej @Wibowit - w większości przypadków kluczowe elementy, łącznie z nagłówkami itp. sprawdzimy startując wirtualne endpointy (co ma tą zaletę, że nie trzeba bindować portu fizycznego i można odpalać wiele równolegle na jednej maszynie). Zupełnie fajnie jak można łatwo się przełączyć między testowaniem na fizycznym i wirtualnym porcie.

Tak zwaną integrację - np, czy system podniósł się na serwerze i czy działa z korpo baza danych i czy zbiera dane z jakiegoś innego systemu załatwiam przez smoke testy - mam ich malo. Czasem to selenium, czasem rest serwis diagnostyczny. (Często takie testy to objaw słabej infrastuktury, ale taka jest korporzeczywistość.)

* - kiedyś ze względów wydajnościowych bawiłem sie w stawianie baz testowych dla wszystkich testów, automatyczne rollbacki po każdym teście itp. W ostatnich latach odpalenie h2 z dumpa jest na tyle szybkie, że przeważnie można sobie całość podnosić i kłaść pro test. O wiele gorzej jest z czasem inicjowaniem kontektu springowego, który to jest np. w Springu nadal żenujący (nawet w Spring 5 webflux nie jest super - a tam kontektu nawet nie potrzebuje).

0

Nie lubię absurdalnych podziałów na różne rodzaje testów.

Ja mam wrażenie, że te podziały albo:

  1. stosują zwykle początkujący, którzy szukają punktu zaczepiania (tudzież autorzy kursów/podręczników, którzy szukają trafnej metafory dzielącą wiedzę na kilka obszarów, stąd mamy podział na e2e, integracyjne, unitowe). Czyli generalnie taki podział, który się stosuje w celach edukacyjnych.

  2. ludzie nieco bardziej zaawansowani stosują głównie po to, żeby z mądrą miną oddzielać rodzaj testów oraz po to, żeby trollować innych programistów i się kłócić z nimi o rodzaj testu (trochę jak w każdej dyskusji o Reakcie jako frameworku JS musi znaleźć się "this guy", który wyskoczy i powie React to nie framework bo mu XKCD 386 kazało, tak samo zawsze ktoś się będzie kłócił o to, jakiego rodzaju jest dany test.

Ew.
3. stosują ludzie, którzy po prostu potrzebują testować system na wielu poziomach, żeby sprawdzić poprawność działania aplikacji, dlatego roboczo wydzielają różne rodzaje testów, być może nawet stosują różne narzędzia (frameworki testowe, test runnery, headless browsers etc.) do testowania różnych rzeczy. Zapewne też trochę inną taktykę podejmują w przypadku różnych testów. Dlatego taki roboczy podział ma wtedy sens.

Co do 1. to Nie byłoby w tym nic złego, gdyby nie to, że złe rozumienie testów unitowych (czyli: piszemy testy do każdej klasy i metody) i rozpowszechniona opinia, że unit testy są "lepsze", prowadzi do pisania ogromnej ilości testów, które raz, że testują szczegóły implementacyjne (więc się rozwalą przy pierwszym refaktorze), dwa, że są kompletną stratą czasu, bo asercje są tak szczegółowe, że i tak nie powiedzą, czy apka działa, a jedynie to, czy implementacja pozostała taka sama.

To, co wielu ludzi uważa za testy unitowe, to raczej określa się snapshot tests (które nawiasem mówiąc można sobie wygenerować z automatu, jak ktoś chce - więc po co je pisać dodatkowo?).

A co do 2. to już jest przypadłość programistów (również moja), że coś nam każe mieć rację albo prostować to, co uważamy za błędy. Duty calls.

Punkt 3. niby oczywistość, ale mam wrażenie, że o testach więcej się mówi niż faktycznie się je pisze. A jeśli się je pisze to też ze złych powodów - z chęci bycia "profesjonalnym" albo z ambicji zdobycia 100% pokrycia kodu (co jest banalne do osiągnięcia swoją drogą), a nie z chęci przetestowania czy aplikacja dobrze działa i zapobieganiu regresji.

0
jarekr000000 napisał(a):

Tak zwaną integrację - np, czy system podniósł się na serwerze i czy działa z korpo baza danych i czy zbiera dane z jakiegoś innego systemu załatwiam przez smoke testy - mam ich malo.

@jarekr000000: Jak mało? Kilka na aplikację, czy raczej kilka na endpoint?

2

ludzie, którzy po prostu potrzebują testować system na wielu poziomach, żeby sprawdzić poprawność działania aplikacji, dlatego roboczo wydzielają różne rodzaje testów, być może nawet stosują różne narzędzia (frameworki testowe, test runnery, headless browsers etc.) do testowania różnych rzeczy. Zapewne też trochę inną taktykę podejmują w przypadku różnych testów. Dlatego taki roboczy podział ma wtedy sens.

Dokładnie. To normalne, że testuje się na różnych poziomach. I czasem ze względów technicznych te testy są robione w różnych technologiach. Aczkolwiek nie warto dopisywać do tego przesadnej ideologii nazewniczej. Już dwa razy widziałem zespoły dumne z posiadania wielu testów integracyjnych, gdzie de fakto testy integracyjne to były testy czy kontekst Springowy lub JavaEE faktycznie wstanie na serwerze. Przy kilku mgabajtach kodu w XMLu i drugim tyle adnotacjach niczego nie można być pewnym. Nawet jak wszystkie unit testy przejdą.

1

Testuje teraz podejscie takie, że do testów stawiam cały system ale w pamięci (zamiast bazy danych hashmapa itp) ale bez endpointów, samą część z logiką i testuje wszystkie metody publiczne w klasach publicznych jakie mam (czy metody fasad modułów). Dzięki temu nie muszę się bawić w żadne mocki ani nic bo wszystko działa na żywych obiektach. Minusem jest to, że potrzebuje coś w stylu bieda-sprigcontextu gdzie sam ręcznie składam cały system

3

aktualnie w projekcie mamy wymagania odnośnie testów dlatego pokrycie unitami mamy bardzo wysokie, każdej klasy która wnosi wartość + integi róznych części systemu z innymi :P + automaty jako sanity-check po buildach

4

Obecnie temat testów (szczególnie w architekturze mikrousługowej) jest na czasie i wiele osób/firm zastanawia się jak to robić dobrze. Dyskusja trwa i wygląda na to, że wszyscy idą w podobnym kierunku (linki poniżej). Osobiście uważam, że testowania każdej metody osobno jest zbyt szczegółowe, a testowanie tylko endpointów jest zbyt ogólne. Całkiem sensowne wydaje mi się traktowanie jako "jednostki" jakiegoś spójnego, biznesowego konceptu. Weźmy pod uwagę na przykład składanie zamówienia w sklepie. Taki proces składa się z kilku części: przyjmowanie zapytania REST, pobieranie produktów z bazy danych, sprawdzanie czy użytkownik w ogóle jest uprawniony do dokonania tego zamówienia (np. czy ma odpowiedni wiek czy pakiet VIP), wyliczanie całkowitej ceny, aplikowanie rabatów itp. W takim przypadku osobno testowałbym walidator, osobno aplikowanie rabatów, osobno wyliczanie ceny, itp. To byłyby moje testy jednostkowe. Takie rzeczy jak OrderRepository pewnie przetestowałbym z realną bazą danych, myślę, że można nazwać to testem integracyjnym. Zrobiłbym też kilka smoke testów, żeby sprawdzić czy wszystko razem dobrze działa (na postawionej aplikacji). Takie podejście z moją obecną wiedzą wydaje mi się w miarę sensowne.

Jeżeli chodzi o linki to udało mi się zgromadzić takie:

0

Jeśli testuję endpoint na postawionym serwerze, to jest to dla mnie już test integracyjny, a nie jednostkowy. I takie testy też stosuje (a w niektórych przypadkach przede wszystkim takie), bo to jedyna możliwość faktycznego sprawdzenia, czy aplikacja działa. Bo co z tego, że testy jednostkowe pokażą, że logika przetwarzania danych jest prawidłowo zaimplementowana, skoro binding HTTP nie parsuje daty, albo pobierana jest wartość ze złego headera?

Testy przez http bardziej brzmią jak testy black box'a, czyli ETE.
Binding możesz przetestować jednostkowo.
Ja to robie tak: https://github.com/Ja-rek/CSharp-Example/blob/master/PortraitOnline.Articles.UnitTests/ModelBindersTest/ItemsBinderTest.cs

0
._. napisał(a):

Binding możesz przetestować jednostkowo.

Mogę też przetestować jednostkowo filtry, handlery i kontrolery. Tylko po?
Moim zdaniem sztuczny izolowany test komponentu ściśle zależnego od infrastruktury daje bardzo niewiele. To coś jak testowanie treści SQL generowanych przez ORM. Pewnie ma sens jeśli piszemy ORMa, a niekoniecznie gdy z niego korzystamy.

Co mi po przechodzących testach jednostkowych samego bindera, skoro endpoint nie będzie działał, bo ktoś tego bindera zwyczajnie nie użyje? Albo ktoś zrefaktoryzuje binder, poprawi testy, a potem okaże się, że w praktyce i tak źle binduje, bo w unit testach zapomniał o przypadku, że parametry geta mogą mieć prefiksy? A co jeśli w nowej wersji frameworka zajdą jakieś zmiany wewnętrzne i okaże się, że unit testy dla binderów są niekompatybilne z nową infrastrukturą? Może dojść do takiej pięknej sytuacji, że unit testy mogą się nawet przestać kompilować, a aplikacja będzie działać prawidłowo.
Niepisanie unit testów i skupienie się na dobrych testach integracyjnych przed tym zabezpiecza.

0

Podział testów powinien być na:

  • szybkie (aka jednostkowe)
  • wolne (aka integracyjne)
  • strasznie wolne (aka behawioralne/E2E)

I nie ma tak, że jakieś są ważniejsze od innych. Testy integracyjne są potrzebne z tego powodu co napisał @somekind, ale nie zgodzę się, że są one ważniejsze od jednostkowych. Jednostkowe są po to, by móc je odpalać często gęsto, w czasie developmentu, by móc stwierdzić na szybko czy zepsuliśmy założenia międzymordzia czy nie. Jak nam wszystko przejdzie to oczywiście należy odpalić testy integracyjne, by mieć pewność, że wszystko działa jak powinno, ale przecież nie będziesz odpalał integracji za każdym razem jak się zmieni pojedyncza linia w kodzie, bo nie robiłbyś nic innego, tylko czekał na wyniki testów.

0
somekind napisał(a):

Mogę też przetestować jednostkowo filtry, handlery i kontrolery. Tylko po?
Moim zdaniem sztuczny izolowany test komponentu ściśle zależnego od infrastruktury daje bardzo niewiele. To coś jak testowanie treści SQL generowanych przez ORM. Pewnie ma sens jeśli piszemy ORMa, a niekoniecznie gdy z niego korzystamy.

Co mi po przechodzących testach jednostkowych samego bindera, skoro endpoint nie będzie działał, bo ktoś tego bindera zwyczajnie nie użyje? Albo ktoś zrefaktoryzuje binder, poprawi testy, a potem okaże się, że w praktyce i tak źle binduje, bo w unit testach zapomniał o przypadku, że parametry geta mogą mieć prefiksy? A co jeśli w nowej wersji frameworka zajdą jakieś zmiany wewnętrzne i okaże się, że unit testy dla binderów są niekompatybilne z nową infrastrukturą? Może dojść do takiej pięknej sytuacji, że unit testy mogą się nawet przestać kompilować, a aplikacja będzie działać prawidłowo.
Niepisanie unit testów i skupienie się na dobrych testach integracyjnych przed tym zabezpiecza.

Tak masz racje, ja nie mówie że to są końcowe testy, które mówią ze aplikacja zachowuje się prawidłowo. Te testy mają zmniejszyć problem wielkiego wybuchu. Gdzie nagle wysypują się wyjątki i tak czy siak musisz używać debugera, bo musisz debugować test. A przecież nie o to chodzi. No nie wiem, czy będziesz miał okazje wymienić Infrę, która rysuje prezentacje. Jeśli już to każda powinna mieć inne testy. Oczywiście możesz te testy pominąć, ponieważ zwykle logika filtrów i tak dalej, nie jest zbyt skomplikowana, decyzja należy do ciebie.

1

Ostatnio piszę testy kompilacyjne. Jesli się kompiluje, znaczy, że działa. Taki test nie ma żadnej asercji lub asercje, które mogą zwrócić tylko true.

Np. wczoraj napisałem funkcję sprawdzającą jakiś warunek. Jeśli warunek jest spełniony, funkcja zwraca true. Jeśli nie jest, funkcja rzuca błędem kompilacji.

Testy kompilacyjne mają szereg zalet i kilka wad.
Zalety:

  • Czas wykonania testu = czas skompilowania
  • IDE pokazuje, czy kod jest poprawny, a więc, czy będzie działał, więc jeśli mamy zaufanie do IDE, to można nawet nie kompilować ;)
  • Kompilacja przyrostowa pozwala wykonywać tylko te testy, które faktycznie mogliśmy zepsuć ostatnią zmianą. W praktyce każda zmiana w projekcie, który teraz robię, "testuje" się typowo 2-5 sekund.
  • Można testować własności, które wymagałyby przetestowania nieskonczonej liczby przypadków w testowaniu tradycyjnym.

Wady:

  • Wielu rzeczy nie da się tak w praktyce przetestować, bo test mógłby być znacznie trudniejszy do napisania niż kod, który testuje.
  • Nie da się przetestować każdego kodu tą metodą.
  • Czas kompilacji testów może znacząco wzrosnąć, jeśli się nie jest uwaznym.
  • Podejście to wymaga języka z porządnym systemem typów i porządnym systemem metaprogramowania.. Np. Scala a jeszcze lepiej Idris. Mam wątpliwości, czy Haskell* się łapie.
  • Debugowanie wymaga możliwości podłączenia debuggera do kompilatora. Scala na JVM to umożliwia, ale nie wiem jak inne języki.

Oczywiście poza tymi testami mam też jednostkowe i integracyjne, bo udowadnianie statycznie pewnych własności dla wszystkich możliwych wejść jest znacznie trudniejsze niż napisanie testu jednostkowego i sprawdzenie kilku przypadków.

*) Haskell nie obsługuje typów zależnych oraz wymaga dodatkowych rozszerzeń do metaprogramowania.

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