Testy integracyjne. Co i jak?

Odpowiedz Nowy wątek
2019-05-13 11:00
0

Hej, to moje pierwsze podejście do testów integracyjnych i chciałbym się dowiedzieć jak to prawidłowo robić i jakie mam błędy w rozumieniu tematu. Sporo na ten temat przeczytałem, ale nie wszystko jest dla mnie jasne.

Z testami jednostkowymi mam prosto:
na każdy projekt (który podlega testom jednostkowym) jest jeden projekt testowy. Jeden plik testuje jedną klasę, np:

- solution
    - source
        + ModelsProject
        + ClientProject
    - tests
      - unitTests
        - ModelsTestProject
            Model1Tests.cs
            Model2Tests.cs
            Model3Tests.cs
        - ClientTestProject
          ...

Do takiej hierarchii można to uprościć. To mi się sprawdza, jest przejrzyste i działa. I super. Jednak teraz chciałbym dołożyć testy integracyjne i nie za bardzo wiem, jak się do tego zabrać. Rozumiem, że tutaj działamy na żywym organizmie. Mockowanie ograniczam właściwie jedynie do interakcji z użytkownikiem (np. okna dialogowe). Baza danych to SQLite w pamięci. Tylko nie za bardzo teraz wiem, jak to zrobić dobrze. Miałem już kilka podejść, jednak za każdym razem mam nieodparte wrażenie, że coś robię źle.

Ja wiem, że to zależy głównie od systemu, ale jak tutaj powinna wyglądać taka hierarchia projektów i klas? No bo projekt testowy na jeden projekt w solucji (tak jak mam przy testach jednostkowych) w tym momencie jest bez sensu. Bo projekty współdziałają ze sobą. Więc Wpadłem na pomysł, żeby dać różne projekty na testowanie różnych klientów. Np: WpfIntegrationTests, XamarinIntegrationTests, WebIntegrationTests... Wydaje się to logiczne. Więc idźmy dalej. Co tak naprawdę powinienem testować? W tym momencie zacząłem od testów WpfClient i MainViewModelu. Jednak szybko okazało się, że w MainViewModel nie mam tak naprawdę za wiele do testowania, bo większość akcji jest na poziomie innych viewmodeli. Ale to chyba nie jest problem.

Kolejnym problemem jest co i jak testować. Przykładowo podczas unit testów tworzę sobie metody w standardowym stylu:

public void AddItem_ItemIsNull_ThrowArgumentNullException()
{
//
}

public void AddItem_ItemIsNotNull_AddsItem()
{
//
}

public void AddItem_ItemIsNotNull_AddsItem()
{
//
}

itd. czyli testuję brzegowe wartości, jakieś wartości, które są ok i jakieś wartości, które są nie ok (np. item = null).

Jeśli chodzi o testy integracyjne, to czytam różne rzeczy. Że tu testujemy np. tylko happy path. Czy to jest reguła? Czy też powinienem testować podanie np. niepoprawnych wartości? No i jak nazywać takie metody?

public void CreateDocumentTest()
{
//
}

public void SaveDocumentTest()
{
//
}

public void LoadDocumentTest()
{
//
}

Czy jakoś inaczej? Jak to robicie? Jakie macie doświadczenia i jakie systemy sobie wypracowaliście? I czy używacie do tego innej biblioteki niż do jednostkowych? Ja do wszystkiego używam nUnit.

Pozostało 580 znaków

2019-05-15 02:36
Juhas napisał(a):

Dobra, trochę odbiegamy od tematu. Czuję się jakbym rzucił kiełbasą w stado wilków :D

Raczej dżdżownic.

W ogóle chciałbym najlepiej zobaczyć jakieś Wasze praktyczne przykłady testów. O ile z testami jednostkowymi wydaje mi się, że nie mam problemów (zazwyczaj), to właśnie natrafiłem na pewien opór przy testach integracyjnych. I czytając wszystkie posty tak naprawdę nie wiem sam, czy robię w tym momencie testy integracyjne, funkcyjne, czy może akceptacyjne, bo teoretycznie wszystko by mi pasowało do tego, co robię teraz. Tu mogę zrozumieć nierozróżnianie tych testów, bo granica wydaje mi się dość cienka i płynna.

Nie czytałem żadnych książek o tej tematyce, więc nie znam oficjalnych definicji, posługuję się intuicyjnymi:

  • Testy akceptacyjne robi klient/BA/Product Owner albo ktoś inny odpowiadający za wymagania. To nie jest sprawdzanie, czy aplikacja działa i czy 2+2=4, tylko czy dana funkcja robi to, czego klient w ogóle chciał.
  • Testy funkcjonalne to testy uruchamiane na działającej aplikacji i sprawdzające czy działa i spełnia wymagania. Nie wymagają wiedzy o wewnętrznej implementacji, więc często są tworzone przez testerów.
  • Testy integracyjne są najtrudniejsze, bo różni ludzie nazywają tak różne rzeczy.
    a) Dla jednych to testy sprawdzające, czy klasa X działa ze swoimi zależnościami. (Bo w swoich "testach jednostkowych" zawsze mockują wszystkie zależności testowanej klasy.)
    b) Dla innych to testy sprawdzające działanie jakichś komponentów systemu z innymi komponentami systemu na żywo. Np. czy klasa przetwarzająca dane dane z jakiegoś API/bazy/pliku poprawnie przetwarza dane faktycznie pobrane z tego API/bazy/pliku, a nie z jakiegoś mocka w pamięci.
    c) Dla jeszcze innych to to samo, co testy funkcjonalne - bo de facto jedne i drugie sprawdzają zachowanie tworzonej aplikacji/serwisu/API z jego zależnościami w postaci

Moja osobista opinia jest taka, że:
a) to patologia i kompletne niezrozumienie idei testowania jednostkowego;
b) czasami ma sens (np. napisaliśmy swojego ORMa, i chcemy sprawdzić, czy dane odczytane z bazy są takie same jak te wstawione), ale często też jest oznaką, że jakiś projekt jest bardzo "enterprise" i cierpi na nadmiar warstw, komponentów i porąbanych technologii (mam takie rzeczy w pracy, np. testy integracyjne sprawdzające czy da się utworzyć obiekt bazując na konfiguracji IoC w XML albo czy jakieś klasy ze środkowej warstwy fizycznie kontaktują się z API, z którego korzystają). Nie mówię, że istnienie takich testów jest złe, po prostu ich pisanie było stratą czasu wynikającą z błędów architektury lub doboru technologii.
c) mam takie wrażenie, że w typowym nieprzekombinowanym projekcie, testy integracyjne i funkcjonalne się zrównują, bo testujemy, czy uruchomiona aplikacja działa prawidłowo i daje oczekiwane rezultaty współpracując z faktyczną bazą/plikami/zewnętrznymi usługami.

Juhas napisał(a):

Jeden plik testuje jedną klasę

Załóżmy, że to jakaś klasa utilsowa, która ma 10 metod. Załóżmy, że dla każdej metody rozpatrujemy 10 przypadków dla prawidłowych danych (typowe i brzegowe warunki) oraz 10 przypadków, kiedy metoda zwraca błąd/rzuca wyjątek. Załóżmy, że piszemy najkrótsze możliwe przypadki testowe, czyli: 3 linijki na kod testowy (arrange, act, assert), 2 linijki odstępu między poszczególnymi fazami, 3 linijki na sygnaturę metody oraz klamerki i linijkę na odstęp między metodami testowymi. Daje to razem 9 linijek na test.
10 * 20 * 9 = 1800 linijek kodu w pliku. Dla mnie to nie brzmi jak coś prostego.

Czasami może i zdarza mi się, że mam wszystkie testy jednej klasy w jednym pliku. Ale to chyba tylko wtedy, gdy klasą jest jakiś handler/serwis z jedną metodą publiczną i prawie nie zawiera logiki. Znacznie częściej do jednej testowanej metody mam wiele plików: oddzielne na błędy walidacji wejście, oddzielne na wyjątki rzucane podczas przetwarzania, oddzielne pozytywny przepływ - a i to nieraz dzielę, jeśli mam jakiś setup wspólny dla kilku przypadków testowych, gdy inne przypadki wymagają innego setupu.

Jeśli chodzi o testy integracyjne, to czytam różne rzeczy. Że tu testujemy np. tylko happy path. Czy to jest reguła?

Nie wiem, czy to reguła, ale dla mnie testy wyłącznie happy path to de facto brak testów. Bo nie da się testami udowodnić, że kod zadziała w każdym przypadku, ale musisz pokazać, w jakich przypadkach zgodnie z oczekiwaniami nie zadziała.

Ja do wszystkiego używam nUnit.

Ja również. Ale w przypadku testów integracyjnych "po mojemu" pisanych do API webowego nie ma problemu, aby użyć w ogóle innej technologii. W końcu request HTTP można podobno wysłać nawet w JS.

yarel napisał(a):

Tak, z ciekawości, gdzie jest wartość dodana w nazywaniu na różne sposoby (mock, stub, fake) implementacji użytej na potrzeby testów ?
To nie jest tak jak z tym dowcipem o juhasach i bacy, który rozwiewa wątpliwości jak nazywać jeża poprawnie?

Myślę, że chodzi o wyrywanie lachonów z dziwnymi fetyszami. ;)

A tak na serio, to nie sądzę aby ktoś Ci odpowiedział na to pytanie. Bo odpowiedzieć mogą tylko ludzie, którzy faktycznie piszą tego typu klasy, czyli jaskiniowcy, a oni jeszcze nie wynaleźli języka.

Z pragmatycznego punktu widzenia prościej stworzyć SUT z automatycznymi mockami wszystkich ulotnych zależności oraz automatycznym stubowaniem zwracanych danych. Jak trzeba, to się po prostu stubuje po swojemu, to co się chce i można weryfikować wywołania metod. I nie trzeba się męczyć z dyskusjami na temat tego, czy prawidłowo się nazwało fejkowe klasy w projektach testowych. No chyba, że ktoś chce mieć wąską specjalizację, zostać senior test double developerem i debatować w nieskończoność, czy mock jest spy'em czy odwrotnie.


"HUMAN BEINGS MAKE LIFE SO INTERESTING. DO YOU KNOW, THAT IN A UNIVERSE SO FULL OF WONDERS, THEY HAVE MANAGED TO INVENT BOREDOM."
Po co pisac osobna metode dla kazdego przypadku testowego? - kzkzg 2019-05-15 19:10
W ogólności po nic, struktura kodu testującego zależy od kodu testowanego oraz celu testów. Czasem da się kod przypadków testowych uwspólnić, a czasem się nie da. - somekind 2019-05-15 19:13

Pozostało 580 znaków

2019-05-15 16:48
0

Z pragmatycznego punktu widzenia prościej stworzyć SUT z automatycznymi mockami wszystkich ulotnych zależności oraz automatycznym stubowaniem zwracanych danych.

A dlaczego nie CUT, OUT, Substitute.? Czy ten SUT to nie jest też jakieś zboczenie.?

Jeśli do stubowania jednego parametru potrzebujesz frameworka izolacji to prawdopodobnie sam się nabawiłeś jakiegoś dziwnego fetyszy od kolegów z pracy. :D

Testy akceptacyjne robi klient/BA/Product Owner albo ktoś inny odpowiadający za wymagania. To nie jest sprawdzanie, czy aplikacja działa i czy 2+2=4, tylko czy dana funkcja robi to, czego klient w ogóle chciał.

A jak sprawdzić, czy dana funkcja robi to, co chce klient, jeśli aplikacja nie działa, czyli nie dodaje 2+2? Kto ci takich bzdur nagadał.?
Testy Akceptacyjne mają gwarantować akceptacje oprogramowania przez klienta. Może to tylko u was klienci akceptują aplikacje, które nie działają.
Testy akceptacyjne nie robi klient.
Testy akceptacyjne wykonuje się z klientem/Product Owner'em.


Unhandled Exception: System.MissingMethodException: Constructor on type 'System.Exception' not found.

Pozostało 580 znaków

2019-05-15 17:06
0
Gworys napisał(a):

A dlaczego nie CUT, OUT, Substitute.? Czy ten SUT to nie jest też jakieś zboczenie.?

Użyłem nazwy, która jest zrozumiała dla wszystkich.
No, najwyraźniej prawie dla wszystkich.

Jeśli do stubowania jednego parametru potrzebujesz frameworka izolacji to prawdopodobnie sam się nabawiłeś jakiegoś dziwnego fetyszy od kolegów z pracy. :D

Nie potrzebuję stubować jednego parametru, nie mam takich malutkich projektów.

A jak sprawdzić, czy dana funkcja robi to, co chce klient, jeśli aplikacja nie działa, czyli nie dodaje 2+2? Kto ci takich bzdur nagadał.?

Napisałem, co jest celem testów akceptacyjnych - sprawdzanie zgodności z wymaganiami, a nie samo działanie, bo to jest weryfikowane innymi testami znacznie wcześniej.
To, że nie umiesz czytać ze zrozumieniem to już nie mój problem.


"HUMAN BEINGS MAKE LIFE SO INTERESTING. DO YOU KNOW, THAT IN A UNIVERSE SO FULL OF WONDERS, THEY HAVE MANAGED TO INVENT BOREDOM."

Pozostało 580 znaków

2019-05-15 17:18
0

Użyłem nazwy, która jest zrozumiała dla wszystkich.
No, najwyraźniej prawie dla wszystkich.

Jak to dobrze wypowiadać się za wszystkich. :D

Nie potrzebuję stubować jednego parametru, nie mam takich malutkich projektów.

A co mnie obchodzą twoje projekty.? Masz jakąś manie wielkości.? :D Rozumiem, że nie uświadczysz w owych projektach obiektów z metodami, które mają 1 parametr, super :D

Napisałem, co jest celem testów akceptacyjnych - sprawdzanie zgodności z wymaganiami, a nie samo działanie, bo to jest weryfikowane innymi testami znacznie wcześniej.
To, że nie umiesz czytać ze zrozumieniem to już nie mój problem.

To dlaczego te testy nie nazywają się testy zgodności wymagań.? HAHA :D


Unhandled Exception: System.MissingMethodException: Constructor on type 'System.Exception' not found.
edytowany 1x, ostatnio: Gworys, 2019-05-15 22:20
@somekind, @Gworys: nie moglibyście jakoś tak bez ironii i docinków?... - Silv 2019-05-15 17:20
@Silv: ja tam lubię wypowiedzi somekinda, kibicuję mu tu, zobaczymy co z tego wyjdzie :) - mr_jaro 2019-05-15 17:29
@mr_jaro: ale ja bym chciał poczytać o testach, a od tej ironii i docinków odechciewa mi się. :( - Silv 2019-05-15 17:34
Aha potem przyłażą do mnie i mi mówią, że DDD nie może istnieć bez repozytorium, bo Somekind tak napisał na forum albo inne infantylne bzdury. - Gworys 2019-05-15 19:44
@Gworys: nie "bzdury", tylko inny punkt widzenia. - Silv 2019-05-15 19:45
Oczywiście. :D - Gworys 2019-05-15 19:47

Pozostało 580 znaków

2019-05-15 18:25
0
Gworys napisał(a):

A co mnie obchodzą twoje projekty.?

Skoro cię nie obchodzą, to po co pytasz? Jak dla mnie EOT.

@Silv - jeśli masz jakieś konkretne pytania, to po prostu pytaj.


"HUMAN BEINGS MAKE LIFE SO INTERESTING. DO YOU KNOW, THAT IN A UNIVERSE SO FULL OF WONDERS, THEY HAVE MANAGED TO INVENT BOREDOM."
Akurat nie mam konkretnych pytań; przysłuchuję się dyskusji, która może mnie czegoś nauczyć. - Silv 2019-05-15 18:27
Nie jest to to samo, co długi post na jakimś blogu, ale jakoś mi przyjemniej ciekawe dyskusje czytać niż blogi (które też czytam – w swoim czasie). - Silv 2019-05-15 18:28

Pozostało 580 znaków

2019-05-15 18:30
4

Bez sensu wymyślać kilkanaście nic nieznaczących nazw, łatwiej podzielić po czasie i wyśiłku wymaganego do zrobienia testu:
-takie które odpala się z IDE i trwają kilka sekund (odpalane przy każdej zmianie)
-takie które trwają dłużej, ale fajnie je mieć bo działają na żywym organizmie (zazwyczaj odpalane raz przed merge)
...i tyle.
Resztę testów mnie jako deva nie obchodzi bo to nie moja odpowiedzialność.

A co do tego czy coś jest fake, stubem czy innym wynalazkiem. Nazwij to po prostu InMemoryCostam i problem z głowy


Spring? Ja tam wole mieć kontrole nad kodem ᕙ(ꔢ)ᕗ

Pozostało 580 znaków

2019-05-15 18:31
4

@danek

a o czym później ludzie będą opowiadać na konferencjach i pisać na blogach?

edytowany 2x, ostatnio: WeiXiao, 2019-05-15 18:32
w sumie najs, najpierw X lat opowiadać o nowych terminach, a potem Y lat uczyć jak wszystko uprościć  - danek 2019-05-15 18:32
i nazywać to innowacyjne/współczesne - WeiXiao 2019-05-15 18:33

Pozostało 580 znaków

2019-05-15 19:21
4

Teza o szybkości testów jest słaba. Nieraz jak testuję np jakiegoś aktora Akkowego to test może trwać nawet kilka sekund, bo takie sobie ustawiam timeouty. Operacje na aktorach dzieją się asynchronicznie, a test działa synchronicznie, więc trzeba robić awaity z timeoutami. Z drugiej strony mogę mieć test, który tworzy pliki na dysku, ale działa 10x szybciej. Co z tego wynika? To, że test jednostkowy niekoniecznie musi być szybki. Może być cholernie wolny, ale jeśli zysk z niego jest wystarczająco duży, to co z tego? Mogę sobie pogrupować testy (otagować) i jedną komendą odpalać tylko szybkie testy, a drugą komendą odpalać wszystkie testy. Jak kto woli.

Zamiast się głowić jak kategoryzować testy albo powstrzymywać się przed stworzeniem tymczasowego pliku w teście jednostkowym w obawie, że ktoś się wścieknie wolę skupić się na praktycznych aspektach testów, a więc kategoryzuję n.p. tak:

  • samodzielne testy, które nie wymagają żadnych działających środowisk (tzn uprzedniego stawiania i konfigurowania aplikacji na jakichś maszynach), ale wystarczy jest odpalić prostym skryptem i działają na każdym kompie z poprawnie skonfigurowanym build toolem
  • testy wymagające zewnętrznych środowisk - takie testy mogą być poprawnościowe lub wydajnościowe

To jaką ktoś przyczepi etykietkę do testu to sprawa drugorzędna lub trzeciorzędna. I tak każdy ma inne intuicje i okazuje się, że termin np testy akceptacyjne jest przez każdego inaczej rozumiany. Zamiast tego lepiej dogadać się jakie katergorie testów są ważne w konkretnym projekcie. Dla przykładu w firmie mamy taką sytuację:

  • mamy wiele mikroserwisów i każdy ma builda w TeamCity
  • używamy więc często terminologii "testy serwisu A", "testy serwisu B", etc
  • zwykle jakiego rodzaju są testy w środku danego serwisu nie ma specjalnie znaczenia o ile te testy nie łączą się poza maszynę która je odpala ani nie wymagają specjalnego przygotowania maszyny (np stawianie apek)
  • jednak jest pewien wyjątek - mamy specjalny framework do tworzenia testów (nazywa się BackGarden) i operuje on na globalnym mutowalnym stanie, klika poprzez Selenium, stawia kilka JVMek, komunikuje się Akką między JVMkami i robi jeszcze inne dziwne rzeczy - te testy często padają i trzeba puszczać builda w kółko, więc wystarczy powiedzieć, że padają nam testy BackGardenowe i wszyscy wiedzą, że zadanie jest prawie gotowe, ale trzeba upolować zielonego builda
  • sporo testów jest w buildach, które nie dotyczą żadnego mikroserwisu, tzn build zawiera tylko kod testowy, gdzie testy są poprawnościowe czy wydajnościowe
  • pewne testy (np testy end-to-end) łączą się do np środowiska UAT, więc jeśli padają to zwykle oznacza iż z tym środowiskiem jest coś nie teges (zła konfiguracja mikroserwisu, wypięcie się mikroserwisów z Kafki, itd). Jeśli natomiast działają to dają większą pewność, że system działa na świeżych realnych danych niż testy ze sztywnymi danymi (tzn te w kodzie mikroserwisów), które ktoś kiedyś wrzucił do repozytorium VCSs.
  • część testów wymaga łączenia się z zewnętrznymi serwisami (np spoza naszej firmy w ogóle), które w pewnych porach nie działają, np w środę przez parę godzin pewne testy nam nie mają prawa iść, więc je w tym czasie ignorujemy

Powyższa kategoryzacja jest konkretna i ma znaczenie zarówno dla programistów jak i menedżera, biznesu, a także zewnętrznych zespołów. Natomiast to czy ja sobie w jakimś teście, który do tej pory był 100% unitowy (według wszelkich wyrytych w skałach zasad) zacznę tworzyć tymczasowy plik na dysku to jest taka pierdoła, że nie warto się nad tym pochylać. Jeśli stworzenie tymczasowego pliku umożliwi stworzenie sensownego testu (o wciąż akceptowalnej wydajności), gdzie lepiej pokrywam rzeczywisty kod biznesowy, a nie robię kolejnego testu Mockito to jak najbardziej warto taki tymczasowy plik stworzyć i zapomnieć o mockach przy testach danej klasy.


"Programs must be written for people to read, and only incidentally for machines to execute." - Abelson & Sussman, SICP, preface to the first edition
"Ci, co najbardziej pragną planować życie społeczne, gdyby im na to pozwolić, staliby się w najwyższym stopniu niebezpieczni i nietolerancyjni wobec planów życiowych innych ludzi. Często, tchnącego dobrocią i oddanego jakiejś sprawie idealistę, dzieli od fanatyka tylko mały krok."
Demokracja jest fajna, dopóki wygrywa twoja ulubiona partia.

Pozostało 580 znaków

2019-05-16 02:02
0
danek napisał(a):

Bez sensu wymyślać kilkanaście nic nieznaczących nazw, łatwiej podzielić po czasie i wyśiłku wymaganego do zrobienia testu:
-takie które odpala się z IDE i trwają kilka sekund (odpalane przy każdej zmianie)
-takie które trwają dłużej, ale fajnie je mieć bo działają na żywym organizmie (zazwyczaj odpalane raz przed merge)
...i tyle.
Resztę testów mnie jako deva nie obchodzi bo to nie moja odpowiedzialność.

A co do tego czy coś jest fake, stubem czy innym wynalazkiem. Nazwij to po prostu InMemoryCostam i problem z głowy

Moim zdaniem wy po prostu nie rozumiecie różnicy pomiędzy jednym a drugim. Więc najłatwiej jest to po prostu wyśmiać lub stanąć po stronie jakiegoś urojonego lidera.


Unhandled Exception: System.MissingMethodException: Constructor on type 'System.Exception' not found.

Pozostało 580 znaków

2019-05-16 08:12
5

@Gworys:
Ależ rozumiem. Przypomina mi to po prostu kłótnie dwóch PMów czy dane zadanie to task czy story, podczas gdy dev już skończył je robić w międzyczasie,


Spring? Ja tam wole mieć kontrole nad kodem ᕙ(ꔢ)ᕗ
Dobra, wymiękam :D - Gworys 2019-05-16 08:34

Pozostało 580 znaków

Odpowiedz
Liczba odpowiedzi na stronę

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

Robot: Xenu Link Sleuth (2x)