Dziś dzień Testera Oprogramowania. Najlepszego! Chociaż wolałbym aby był to dzień testowania, czy dzień zapewniania jakości oprogramowania, dzień pięknej architektury, czy dzień działającego oprogramowania.
Ile powinny trwać testy?
Kilka dni temu na LinkedIn wywiązała się krótka dyskusja na temat tego, co można robić w czasie jeżeli implementujemy test automatyczny którego przebieg trwa 10 minut (sic!). Tak, chodzi o jeden test, nie o wszystkie testy automatyczne. Michał odpisał w kolejnych wersetach, że ma nawet test który trwa 30 min, bo składa się z testów opartych o GUI i API.
Cóż, moim zdaniem nie jest problemem “co robić w tym czasie”, tylko dlaczego w ogóle implementujemy takie testy.
Bez urazy do moich rozmówców, wierzę, że macie jakieś ważne powody aby implementować te testy, jednak też z mojej strony sam fakt takich testów jest “podejrzany”. Myślę, że każdy sam musi to osądzić w swoim serduszku. Moje mówi: “yyyyy – Nope”.
Jaka jest Twoja odpowiedź?
tylko proszę 😉 nie odpowiadaj #tozależy, a jeżeli już tak odpowiadasz to powiedz od czego, co zależy 😉
Ale z czym Ty masz problem?
Klient chciał taki (długotrwający) test oparty o GUI, klient nasz pan, płaci, my robimy. Czy na pewno?
A może mamy taki duży system ABCD…Z, i klient chce taki test END To END, gdzie jeden END to początek systemu w module A a ostatni END to moduł Z. Klient nasz pan, płaci to robimy.
Byliśmy tam, widzieliśmy, nie zwycieżyliśmy… To nie działa. A nawet jeżeli działa, to mamy problem …
Dłuższy nie zawsze znaczy lepszy
Długo uruchamiający się test przeważnie oznacza że:
- nie jest on częścią builda/deploya ==> nie jest on częścią PRa/MRa ==> nie chroni naszego builda przed błędami ==> brak sprzężenia zwrotnego z tego testu TU i TERAZ ==> produkujemy WASTE, jeżeli jest błąd to znajdziemy go kiedyś, gdzieś*
- opieraja się o interfejs/operacje bardzo wolne ==> przypadek Jana ==> GUI wolno działa
- testujemy jakiś super długi flow, prawdopodobnie jakąś dużą integrację
- sama implementacja takiego testu jest karkołomna i podatna na błędy, wymaga wiele kroków, wiele prób itp.
- implementacja zapewne wymaga wielu zależności np. testy w przypadku oparciu o GUI pełnej integracji jego z jakimś API (o testach w izolacji samego interfejsu może w jakimś innym odcinku), w drugim przypadku (długiego flow) pełnego systemu z modułów ABCD…Z gdzieś na jakiejś produkcji
- kolejne konsewkencje powyższego to posiadanie jakiegoś środowiska “testowego”/”produkcyjnego” – co oznacza jego przygotowanie, zapewnienie stabilności (przewidywalność ==> deterministyczność), przygotowanie danych testowych i przygotowanie wszystkich innych zależności
- jeżeli do tego dodamy jeszcze wymiar czasu, tzn. nasz test jest zależny od wywołań w jakimś konkretnym czasie, albo w jakiejś sekwencji to nasz setup staje się często albo niemożliwy albo wymaga jakiegoś zcentralizowanego rygoru (w korporacjach zwany “zamawianiem środowiska” ;))
- *a jeżeli uda nam się uruchomić ten test i on nie przejdzie to szybko nie dojdziemy do tego dlaczego on nie przeszedł, mamy w końcu wiele kruchych punktów gdzie mogło pójść coś nie tak, zaczynając chociażby od złożoności samego testu, kończąc na zapewnionych zewnętrznych warunkach środowiska
Byłem tam, widziałem, płakałem
Takie testy “integracyjne” całych systemów to jest utopia. Zapewnianie dostępności środowisk w klasie systemów typu “bank” – to jest utopia. Oczywiście są na to sprawdzone sposoby w takich organizacjach stosowane od lat, np. 2 tygodnie na stabilizacje środowiska, 2 tygodnie na testy … itp. itd.
Brzmi jak plan. Tylko to podejście również nie działa. Ostatnie lata spędziłem w organizacjach mających problemy ze “środowiskami” i z “niestabilnymi testami automatycznymi” oraz z “releasami”.
Sednem problemu jest podejście do testowania.
Otwieram oczy
Jeszcze kilka lat temu byłem pewny, że zawsze potrzebny jest jednak jakiś test integracyjny full systemu. Mocno otworzył mi oczy klasyk gatunku. Być może mocno radykalny, ale jak dla mnie przemawiający.
Zrozumienie testowania na krawędzi systemów jak i samo nakreślanie ich odpowiedzialności i granic jest kluczem (powiązane z DDD).
Telegraficzny skrót – przykład
Mamy sobie system jak poniżej. Aby przetestować logikę biznesową systemu jak poniżej, nie potrzebuję ani App1 ani App3 ani nawet prawdziwej App2 wystawionej na środowisku.
W systemach klasy enterprise często argumentem który słyszałem było to, że aby przetestować jakiś flow App1–>App2–>App3 potrzeba zasilić App2 jakimiś magicznymi danymi z App1 a wynik przetwarzania sprawdzać w App3.
Tylko …
Jak zaczniemy drążyć co ma być celem testu, zaczniemy zadawać pytania, dlaczego te dane z App1 są takie ważne i czy na pewno aby przetestować biznesowy scenariusz potrzebuję całego systemu, to wyjdzie, że ta logika siedzi w jednej z aplikacji np. że chodzi konkretnie o jakąś logikę przetwarzania App2, do której testów nie potrzebuję zależności App1 ani App3, ponieważ dobrze wiem (powinienem) czego oczekuję od logiki klasy Some***LogicToProcess w App2
Na dziś kończę
Dla wielu czytających być może to co piszę to oczywistość. Recepta jest prosta – włączamy bieg “shif left” (oczywiście dla mnie jest to buzzword ;)) i wszystkie testy przenosimy na wcześniejsze fazy wytwarzania, mockujemy, piszemy testy kontraktowe itp.
Jednak problemy w organizacjach wciąż są. Nawet w nowo tworzonych systemach nie zawsze jest wdrożona”piramida testów”, poziom wiedzy o implementacji testów na różnych poziomach kuleje, tak samo jak wiedza o odcinaniu zależności (loosly coupled architecture). A przecież utrzymujemy też systemy kilkudziesięcioletnie – jak tam zapewnić jakość bez implementacji karkołomnych wielogodzinnych testów, mrożenia kodu i srodowisk?
A to czy powinniśmy zdawać się ślepo na potrzebę klienta i implementować testy integracyjne trwające po wiele minut to każdy i jego wewnętrzna etyka zawodowa powinna sobie odpowiedzieć. A na post o etyce, mam nadzieję, też przyjdzie czas.
Aktywizacja
Próbuję się trochę zaktywizować ostatnio, ale cięzko mi idzie. Jeżeli chcesz mnie czytać, rozmawiać, daj jakoś znać. Mam jeszcze kilka historii do opowiedzenia i technikaliów do sprzedania 😉 Jestem ostatnio też na insta gdzie można mnie złapać
Do roboty Decu. Z chęcią poczytam 🙂
A co z ograniczeniami fizycznymi, które powodują wydłużanie się testu? Co kiedy weryfikujemy czy nasza aplikacja działa na np. przy bardzo słabym połączeniu internetowym? Kiedy testujemy migrację danych która trwa ok 10- 30 minut i jest jednocześnie krytyczną funkcjonalnością dla naszych użytkowników? Czy wówczas też polecałbyś pozbycie się takich testow?
Dzięki za komentarz. Po kolei. Po pierwsze to, zasadniczo nigdzie piszę, że jakiekolwiek testy które trwają długo nie mają sensu 😉 weźmy chociażby testy wydajnościowe – ich specyfika wymaga czasu. Wpis dotyczy głównie testów funkcjonalnych, gdzie myślą przewodnią jest “jak chcesz przetestować logikę biznesową konretnego elementu to nie musisz zestawiać całego systemu ABCD…Z”. Oczywiście logika może być rozproszona, trochę siedzi w A, trochę w B itd. co tworzy logikę systemu ABCD…Z. Ale jeżeli przetestujemy logikę A, logikę B … to dlaczego logika ABCD…Z ma nie działać. Zwracam uwagę nad zastanowieniem się czy na pewno potrzebujemy takich testów, jeżeli tak to jak dużo, może kogoś sprowokuje to to przeniesienia 90% swoich testów end to end do niższych warstw (konkretnych modułów).
Idąc dalej. Ograniczenia fizyczne. Jeżeli oczekujemy jakiegoś stanu środowiska (zewnętrznego świata) to możemy go w testach zasymulować. Przy czym trzeba tu rozróżnić dwie rzeczy. Można napisać testy wewnętrz modułu A, które będą weryfikowały kod pod kątem timeoutów (mockowanie czasu ;), czyli przyśpieszenie wywołania tych timeoutów). Jeżeli to nie odzwierciedla dobrze rzeczywistości a chcemy mieć test bardziej “integracyjny” to naszym kontraktem jest “pobieram dane z modułu B co trwa 2 min” – możemy to również zasymulować. Nie chcę Ci mówić teraz gdzie powinny być dokładnie te testy. Ja bym chciał zawsze móc przetestować jak najwięzej się da, jak najwcześniej, więc na PR/MR. Jeżeli potrzebujesz testów które sprawdzą zachowanie aplikacji przy “wolnym połączeniu” … to ich potrzebujesz 😉 nię będę z tym dyskutował, pytanie tylko jak inaczej (bez uruchamiania całego środowiska) mógłbyś to zrobić (np. to co napisałem wyżej jest w mocy).
Co do migracji która trwa długo. Więc analogicznie – zadaj sobie pytanie, co mógłbyś przetestować jeżeli chodzi o migrację przy PR/MR (merge reuquest) aby to zrobić np. poniżej minuty?
np. można by było uruchomić ją na jakimś malutkim zestawie danych testowych lub widziałem (robiłem) testy do malutkich częsci migracji. Czyli np. Twoją zmianą jest kolejny fragment migracji – jak to przetestować? pisząc test jednostkowy 😉 – w kontekście danych (a w zasadzie bazy danych) – możę być to test procedury/skryptu (zawsze to jest jakiś kawałek kodu, do któego mogę podać dane i uruchmomić). Da się to robić 😉
Nie wiem czy odpowiedziałem na pytania. Daj znać
Decu świetne uwagi w tym poście. Akurat w aktualnym projekcie stanąłem przed problemami opisanymi przez Ciebie poście i ciężko mi sobie wyobrazić bazowanie w tym projekcie na testach e2e głównie. Trzeba schodzić niżej: UT, jak UT nie zadowalają to pisać takie pseudo UT o większym pokryciu niż pojedyńcze klasy czy metody (może bardzie pasuje okreslenie testy modułów). Poza tym zawsze można testy systemów ‘ABCD…Z’ sprowadzić do testowania każdego z serwisów w izolacji od pozostałych – w końcu każdy z tych serwisów otrzymuje jakiś input (czy to request http, czy wiadomość w systemie kolejkowym, whatever…) i wypluwa output (również w różnych formach, ale jednak ten output jest). Kwestia przygotować input i porównać output ze spodziewanym. Keep posting 🙂