[Java] Przetestowanie metody zwracającej void

[Java] Przetestowanie metody zwracającej void
BI
  • Rejestracja:prawie 5 lat
  • Ostatnio:ponad rok
  • Postów:72
0

Cześć
Jak przetestować poniższą metodę w klasie UserService:

Kopiuj
public class UserService {
...
    public void updateEntity(User user) {
        repositoryDAO.save(user);
    }
}

Używam mockito, junit 5, jpa

GigaBajt
  • Rejestracja:ponad 9 lat
  • Ostatnio:ponad 3 lata
  • Postów:37
1

Oprócz sprawdzenia czy było uderzenie w metodę .save() zbyt wiele nie przetestujesz :/
Mockito.verify(repositoryDAO, times(1)).save(any());

Charles_Ray
Za taki kod powinni dawać od razu numerek na onkologię :)
Shalom
Mam nadzieje ze ten post to żart xD
BI
  • Rejestracja:prawie 5 lat
  • Ostatnio:ponad rok
  • Postów:72
0

OK. A czy nie można stworzyć sobie jakiegoś obiektu User i wykonać

Kopiuj
 repositoryDAO.save(user)

i potem

Kopiuj
assertEquals("Jan", service.findById(1).getName());
PI
  • Rejestracja:ponad 9 lat
  • Ostatnio:4 miesiące
  • Postów:2787
2
biurostron napisał(a):

OK. A czy nie można stworzyć sobie jakiegoś obiektu User i wykonać

Kopiuj
 repositoryDAO.save(user)

i potem

Kopiuj
assertEquals("Jan", service.findById(1).getName());

To zależy czy robisz test jednostkowy, czy integracyjny ;)

KamilAdam
  • Rejestracja:ponad 6 lat
  • Ostatnio:dzień
  • Lokalizacja:Silesia/Marki
  • Postów:5505
6

Ta metoda nie ma żadnej logiki więc nie ma sensu jej testować. Chyba że chcesz podwyższyć procent pokrycia kodu. To wtedy czasem nawet do geterów i seterów pisze się testy


Mama called me disappointment, Papa called me fat
Każdego eksperta można zastąpić backendowcem który ma się douczyć po godzinach. Tak zostałem ekspertem AI, Neo4j i Nest.js . Przez mianowanie
Korges
  • Rejestracja:około 5 lat
  • Ostatnio:około 4 godziny
  • Postów:571
0

Z reguły uważam że metody pracujące na bazie nie powinny być void. Tym bardziej .save() który przecież już zwraca zapisywaną encję. Dobrą praktyką po zapisie do bazy była by informacja w response entity że encja taka i taka została zapisana i umieszczenie jej w JSON'ie (lub jej częśći - jakieś zmapowane DTO).
Co do otestowania, na pewno mockito.verify() + test .updateEntity() ze zmockowanym repo.
Poza tym, metoda nazywa się update*(), przemyślałeś czy może wystąpić sytuacja z nieistniejącym id ?

@Edit
np coś takiego

Kopiuj
@Override
    public ForumThread update(Long id, ForumThread thread) {
        thread.setId(id);
        return forumThreadRepository
                .findById(id)
                .map(x -> thread)
                .map(forumThreadRepository::save)
                .orElseThrow(PropertyNotFoundException::new);
edytowany 6x, ostatnio: Korges
Charles_Ray
Kolejny rakopodobny twór. Co chcesz weryfikować? Odpalenie metody save()? Co Ci to da?
Korges
Hmm wiesz no, a wyobraź sobie sytuacje gdy ta metoda update*() to rzeczywiście jest update i przed zapisem sprawdza czy dany rekord na bazie istnieje :D Wtedy chyba jest sens sprawdzania czy .save() jest wywołane lub coś tam :D
Charles_Ray
  • Rejestracja:około 17 lat
  • Ostatnio:około godziny
  • Postów:1876
9

Tak czytam sobie kolejne komentarze i oczom nie wierze :) ludzie, naprawdę mockujecie repozytoria i sprawdzacie wywoływanie metod save(), update(), findById() itd?


”Engineering is easy. People are hard.” Bill Coughran
jarekr000000
Mocksturbacja zawsze w modzie.
S9
@jarekr000000: no nie wiem czy zawsze w modzie. wiele osób przejrzało na oczy :D
jarekr000000
niekoniecznie przejrzało - od mocksturbacji podobno się ślepnie
KK
  • Rejestracja:prawie 17 lat
  • Ostatnio:15 dni
0

@Charles_Ray: szczerze mówiąc, to tak :D

Otóż w swoich małych projektach wczytuję całą bazę do kolekcji javowych i na nich sobie pracuję. Używam Spring Data i MongoDB, które traktuję jako persistent storage.
Pisząc logikę biznesową zaczynam od testów, tych jednostkowych, szybkich, bez kontekstu springa.

Pewnego dnia, kiedy napisałem sobie logikę i zapis danych, moje testy szły na zielono, wprowadzałem sobie jakieś dane z frontu. Jakież było moje zdziwinie, kiedy okazało się, że po restarcie aplikacji danych nie ma ;)

Tak, zapisywałem dane do kolekcji w serwisie, ale zapomniałem wywołać save na repozytorium. Z tego powodu napisałem sobie dwie abstrakcyjne klasy jak DataProvider i DataService, coś takiego:

Kopiuj
abstract class DataProvider<T : DataDomain>(
        private val emptyList: Boolean = false
) : CrudRepository<T, String> {

    private var items: List<T>

    init {
        items = if (emptyList) List.empty() else sampleData()
    }

    open fun sampleData(): List<T> {
        return List.empty()
    }

    override fun <S : T?> save(p0: S): S {
        this.items
                .find { items -> items.id == p0?.id }
                .map { oldItem ->
                    this.items = this.items
                            .remove(oldItem)
                            .append(p0)
                }
                .getOrElse { this.items = this.items.append(p0) }
        return p0
    }

    override fun findAll(): MutableIterable<T> {
        return this.items
    }

//.... pozostale metody

abstract class DataService<T : DataDomain>(
        val repository: CrudRepository<T, String>
) {
    private var items: List<T> = List.ofAll(repository.findAll())

//.... podobne metody jak w DataProvider

W DataService mam podobne metody jak w DataProvider, tylko np. metoda addOrUpdate zapisuje dane zarowno do kolekcji items jak tez robi save na repository. No i to mam przetestowane raz. W ten sposób wiem, że jak wywołuję addOrUpdate w serwisie pochodnym od DataService to save idzie i do kolekcji, i do bazy danych.

I tak wiem, że jak będzie jakiś błąd z serializacją, albo coś ze Spring Data czy samą bazą, to moje testy i tak będą szły na zielono. Do sprawdzenia tego potrzebne są testy integracyjne z całym kontekstem. Mimo to i tak uważam, że takie sprawdzanie czy jest save na repository jest lepsze niż gdyby tego nie było w ogóle.


edytowany 1x, ostatnio: kkojot
Korges
Tak sobie teraz myśle, .verify() to może być też dobry sposób na testowanie transakcji :p
DQ
  • Rejestracja:prawie 10 lat
  • Ostatnio:7 miesięcy
  • Postów:141
2
kkojot napisał(a):

I tak wiem, że jak będzie jakiś błąd z serializacją, albo coś ze Spring Data czy samą bazą, to moje testy i tak będą szły na zielono. Do sprawdzenia tego potrzebne są testy integracyjne z całym kontekstem. Mimo to i tak uważam, że takie sprawdzanie czy jest save na repository jest lepsze niż gdyby tego nie było w ogóle.

Takie testy mają ten problem, że zagłębiają się w wewnętrzną logikę funkcji przez co uniemożliwiają w przyszłości prosty refaktor.

Przykładowo, ktoś postanawia wrzucać Mockito gdzie popadnie i sprawdzać czy konkretne metody się wywołały. Potem ten sam ktoś chce zmienić kilka funkcji (różne powody - zmiana biznesowa, bo można to ogarnąć lepiej itd.) i nagle się okazuje, że ma do poprawienia 200 testów. Taki sposób testowania bardziej przeszkadza niż pomaga i prowadzi do systemu w którym nic nie chcesz zmieniać (bo się nie da).

Może Mockito gdzieś ma jakiś sens (nie udało mi się go jeszcze spotkać, poza mega legacy kodem) ale za każdym razem jak się go chce użyć w nowym kodzie to najpierw zastanowiłbym się dlaczego w ogóle istnieje taka potrzeba. Jest duża szansa, że problemem jest kod, który jest napisany tak, że nie da się go prosto otestować, a nie realna potrzeba użycia Mockito. Może metody robią za dużo, może podział odpowiedzialności jest pomieszany pomiędzy klasami, a może została wybrana zbyt mała jednostka testowa, powodów może być wiele.

KK
  • Rejestracja:prawie 17 lat
  • Ostatnio:15 dni
0

Cieżko mi jest komentować Mockito, bo nigdy z tego nie korzystałem. Ale korzystam ze Spring Data, bo działa to całkiem nieźle i tak mi jest wygodnie. I z racji tego, że to repozytorium wstrzykuje mi Spring, do testów jednostkowych potrzebuję, no cóż.. mocka, imitację działania tego repozytorium. Stąd takie klasy jak DataProvider i DataService, które opisałem powyżej.

I jeśli mam 200 serwisów, które dziedziczą po DataService, to wywołanie metody save czy update w repository mam przetestowane tylko w jednym miejscu - w teście klasy abstrakcyjnej DataService. Dlatego problem refaktoringu opdada. Problemem jest tylko to, że testuję swojego mocka (choć w testach to jawnie widzę, że wstrzykuję sobie DataProvider), dlatego nie wykluczam potrzeby pisania testów integracyjnych.


Shalom
  • Rejestracja:ponad 21 lat
  • Ostatnio:około 3 lata
  • Lokalizacja:Space: the final frontier
  • Postów:26433
7

Takie rzeczy testuje się tak:

  • startujemy aplikacje (@SpringBootTest) z bazą in memory
  • wykonujemy test
  • robimy check, czy zmiany w bazie się pojawiły (czyli stukamy do tej bazy in memory i patrzymy co w niej siedzi)

Pomysły z mockowaniem repozytoriów czy inne cuda to serio :D


"Nie brookliński most, ale przemienić w jasny, nowy dzień najsmutniejszą noc - to jest dopiero coś!"
edytowany 1x, ostatnio: Shalom
Pixello
  • Rejestracja:około 10 lat
  • Ostatnio:5 miesięcy
  • Lokalizacja:Podkarpacie
  • Postów:448
0
Shalom napisał(a):

Takie rzeczy testuje się tak:

  • startujemy aplikacje (@SpringBootTest) z bazą in memory
  • wykonujemy test
  • robimy check, czy zmiany w bazie się pojawiły (czyli stukamy do tej bazy in memory i patrzymy co w niej siedzi)

Pomysły z mockowaniem repozytoriów czy inne cuda to serio :D

Ostatnio miałem przypadek, że jak chciałem użyć providera do ORM in-memory, to zostałem skrytykowany, że to nie zadziała, bo nigdy nie działa - okazało się, że używali niestandardowych typów kolumn w jakiejś egzotycznej bazie danych, który akurat ta implementacja in-memory nie wspierała - dlatego zrobili swoją nakładkę na ORM, udawała bazę in-memory - a wystarczyo troszkę konfiguracji :|

Shalom
Od biedy można wtedy użyć test containers albo startować tą dziwną bazę w dockerze na czas testu. Zawsze się da ;)
KK
  • Rejestracja:prawie 17 lat
  • Ostatnio:15 dni
0
Shalom napisał(a):

Takie rzeczy testuje się tak:

  • startujemy aplikacje (@SpringBootTest) z bazą in memory
  • wykonujemy test
  • robimy check, czy zmiany w bazie się pojawiły (czyli stukamy do tej bazy in memory i patrzymy co w niej siedzi)

Pomysły z mockowaniem repozytoriów czy inne cuda to serio :D

I czekamy minutę na odpalanie takiego testu. Przy pisaniu logiki biznesowej, odpaladnie co chwilę takiego testu to istna strata czasu. Wolę sobie zmockować repozytorium (przecież to jedna klasa tylko) i odpalać testu na poziomie milisekund, a kiedy wszystko bangla dodać test endpointa tak jak to opisałeś.


KamilAdam
  • Rejestracja:ponad 6 lat
  • Ostatnio:dzień
  • Lokalizacja:Silesia/Marki
  • Postów:5505
1
kkojot napisał(a):

I czekamy minutę na odpalanie takiego testu. Przy pisaniu logiki biznesowej, odpaladnie co chwilę takiego testu to istna strata czasu. Wolę sobie zmockować repozytorium (przecież to jedna klasa tylko) i odpalać testu na poziomie milisekund, a kiedy wszystko bangla dodać test endpointa tak jak to opisałeś.

Bywa różnie. Jak pisałem testy z Cassandrą in memory to najwięcej czasu zajmowało czyszczenie Cassandry. Więc jej nie czyściłem i zawsze generowałem nowe uuidy. W rezultacie miałem zadawalającą szybkość testów bo Cassandrę uruchamiałem tylko raz przed wszystkimi testami.


Mama called me disappointment, Papa called me fat
Każdego eksperta można zastąpić backendowcem który ma się douczyć po godzinach. Tak zostałem ekspertem AI, Neo4j i Nest.js . Przez mianowanie
vpiotr
  • Rejestracja:ponad 13 lat
  • Ostatnio:prawie 3 lata
0

Za mało kodu w tej metodzie żeby ją testować.
Zresztą zwykle nie testuje się ORMa.
Co najwyżej mapowanie do niego.
Napisz test integracyjny do tego, a jednostkowe olej.
https://www.baeldung.com/spring-boot-testing

Shalom
  • Rejestracja:ponad 21 lat
  • Ostatnio:około 3 lata
  • Lokalizacja:Space: the final frontier
  • Postów:26433
0

I czekamy minutę na odpalanie takiego testu

@kkojot tak, powtarzamy dalej takie mity xD Widac, że chyba nigdy w życiu takiego testu nie robiłeś, albo klepiesz jakiś turbo monolit. Mam całą masę takich testów i nigdy nie zauważyłem żeby mnie jakoś spowalniały albo wykonywały się minutę :)

Wolę sobie zmockować repozytorium (przecież to jedna klasa tylko) i odpalać testu na poziomie milisekund

Świetny plan, tylko równie dobrze możesz nie odpalać tych testów wcale, bo przeciez taki zmockowany test niczego nie sprawdza... No ale jeśli dzięki temu masz spokojne sumienie...


"Nie brookliński most, ale przemienić w jasny, nowy dzień najsmutniejszą noc - to jest dopiero coś!"
Potat0x
I do tego jeszcze lepsze pokrycie testami :P
KK
  • Rejestracja:prawie 17 lat
  • Ostatnio:15 dni
0
Shalom napisał(a):

Świetny plan, tylko równie dobrze możesz nie odpalać tych testów wcale, bo przeciez taki zmockowany test niczego nie sprawdza... No ale jeśli dzięki temu masz spokojne sumienie...

Nie zgadzam się. Taki test wywali się, jeżeli np. usunę linijkę repository.save(). Przejdzie błędnie tylko w wyjątkowych sytuacjach, np. podczas problemu z serializacją przy zapisie do bazy danych.
Podczas refaktoringu kodu, jak sobie wydzielam klasy, przenoszę metody itp. to CTRL + F5 na testach idzie praktycznie co kilkanaście sekund. P%^%&^^* czekać wtedy, aż mi cały kontekst springa wystartuje ;)


edytowany 1x, ostatnio: kkojot
Zobacz pozostały 1 komentarz
KK
Celem jest sprawdzenie, czy nie zapomniałem o zapisie do bazy danych. Czy jest potrzebny do logiki biznesowej? W sumie nie ;) oczywiśćie ten błąd wyłapałbym testem integracyjnym, ale takie piszę zawsze na końcu (albo wcale, bo są żmudne, wolne i męczace, a ja nie wiem nawet czy dana funkcjonalność mi jest potrzebna więc nie tracę czasu), ale skoro taki mock pokrywa większość przypadków i jest już gotowy, to czemu nie?
Charles_Ray
Bo testujesz mocka + wyczuwam piekło podczas refactoringu kodu biznesowego
KK
Piekła nie będzie, bo mam to tylko w jednym miejscu, w abstrakcyjnej klasie + jej jeden test. No ale dzięki niej też nie muszę pamiętać o repository.save()
Charles_Ray
Ale wołasze tego sejwa z wielu serwisów zapewne - więc testy każdego z tych serwisów dostają z automatu +1 mocka. Więc pytanie, co jest złe w testach serwisu, który ma zależność do N mocków. Odpowiadam - widziałem takie testy po jakimś czasie i to jest masakra coś tam zmienić.
KK
Ok, pomyślę. Ale jeżeli jedyny sensowny test klasy, w którą wstrzykuję repository ze spring data, jest tylko ten, co stawia cały kontekst Springa to dla mnie masakra jakaś. Bo na myśl zmieniania czegoś w klasie, której test ładuje te wszystkie springowe beany, robi mi się słabo. Więc albo unikałbym tam zmiany, albo zaniechał pisania takiego testu ;)
danek
  • Rejestracja:ponad 10 lat
  • Ostatnio:7 miesięcy
  • Lokalizacja:Poznań
  • Postów:797
0
Shalom napisał(a):

Świetny plan, tylko równie dobrze możesz nie odpalać tych testów wcale, bo przeciez taki zmockowany test niczego nie sprawdza... No ale jeśli dzięki temu masz spokojne sumienie...

Sprawdza samą logikę, to nie jest nic ;)


Spring? Ja tam wole mieć kontrole nad kodem ᕙ(ꔢ)ᕗ
Haste - mała biblioteka do testów z czasem.
Shalom
No właśnie tylko że nie sprawdza. Sprawdza czy wywołano metodę na jakimś obiekcie. Prosty test: robie refaktor i zamiast użyć tego obiektu repository, używam innego mechanizmu, żeby zapisać zmiany w bazie z takich czy innych powodów. Testy czerwone, mimo że wszystko nadal działa! Drugi test: robię inny refaktor i cośtam popsułem w danych które mają lecieć do bazy (np. nie ustawiłem jakiegoś pola). Testy zielone. Czyli mamy zarówno false positive jak i false negative. Jackpot. Dobre testy, polecam :D
danek
dlatego masz osobny moduł na domene i osobny na zapis do bazy i oba testujesz osobno ( ͡° ͜ʖ ͡°)
Potat0x
  • Rejestracja:ponad 8 lat
  • Ostatnio:18 dni
  • Postów:370
1

Ja tam wolę poczekać nawet kilkanaście sekund i mieć normalne testy. Wtedy:

  • testy zielone <=> kod działa, testy czerwone <=> kod nie działa (no chyba że test jest skopany :P)
  • żeby napisać test dla serwisu nie trzeba się zagłębiać w to, jak gdzieś pod spodem są używane repozytoria
  • refactor bebechów nie powoduje konieczności naprawiania testów
  • można wykryć, gdy w adnotacjach JPA jest coś namieszane i aplikacja nie działa jak trzeba lub się wywala

Miałem okazję być w projekcie, w którym repozytoria JPA były zamockowane. I nawet jak testy przechodziły, to dla pewności ręcznie przeklikiwałem aplikację. Jeżeli dostajemy sytuację typu: {testy zielone, aplikacja nie działa} lub {testy czerwone, aplikacja działa}, to komu to potrzebne?

KK
A w jakim przypadku testy z mockami przechodziły, a aplikacja nie działała? Nie pytam teortetycznie, tylko czy po prostu mieliście taki przypadek?
Potat0x
Nie pamiętam czy konkretnie były takie przypadki jak napisałeś (zapewne tak), ale w drugą stronę (test nie przechodzi, aplikacja działa) to było na porządku dziennym.
DA
  • Rejestracja:ponad 10 lat
  • Ostatnio:4 dni
  • Postów:176
0

@Shalom: Jakiej magii używasz, że z testcontainers masz czasy wykonania poniżej minuty?
Też używam tych kontenetów, i w moich testach leci po prostu request http pod dany endpoint, a dane są faktycznie zapisywane/wyciągane z bazy w kontenerze (w moim przypadku cassandra). Wtedy mam pewność, że dany endpoint działa tak jak zamierzam. W przypadku mockowanych repozytoriów czy in memory, nadal muszę zapewniać takie rzeczy jak to, że nie można querować po polach niebędących kluczami głównymi - w cassandrze tak nie można.
I w sumie wszystko mi się sprawdza, błędy wykrywam wcześnie, nie mam false positive'ów, tylko czas wykonania mógłby być trochę szybszy...

Shalom
Z testcontainers jest wolniej, ale nic nie poradzisz jak masz dziwną zależność. Możesz postawić jakiś mikroserwis nad taką zależnością, w głównym kodzie stukać po tym serwisie (więc w testach po konfigurowalnych httpserverach in memory), a testy z zależnością w kontenerze masz wtedy tylko w tym jednym mikroserwisie, który generalnie niewiele będzie się zmieniać ;)
S9
  • Rejestracja:ponad 10 lat
  • Ostatnio:6 miesięcy
  • Lokalizacja:Warszawa
  • Postów:3573
1

Teraz uwaga, można testować jednostkowo bez mockow! Zamiast mockowac DAO z Mockito możesz zrobić implementację np z użyciem HashMapy. Uwaga: działa szybko, I można rzeczywiście łatwo cos zweryfikować, np czy po metodzie save hashmapa zawiera dany obiekt. A zamiast mockowac inne serwisy biznesowe tworzysz cały graf obiektów czyli jednostkę która testujesz. Oczywiście testy integracyjne dalej warto mieć, nie neguje :)


"w haśle <młody dynamiczny zespół> nie chodzi o to ile masz lat tylko jak często zmienia się skład"
KK
No i właśnie taki rozwiązanie @Shalom mocno krytukuje, bo nie testujesz aplikacji, tylko własną implementację z HashMapą ;)
Shalom
Potwierdzam, uważam że taki test nie ma sensu za bardzo. Wcale nie będzie specjalnie szybciej niż z jakimś HSQL czy H2, a jednocześnie nie testujesz prawdziwego kodu, więc gdzie tu jakiś sens?
PI
@scibi92: gdzieś chyba Kuba Kubryński opowiadał o takim podejściu. Wydaje się spoko, ale czy nie dublujesz wtedy implementacji (specjalnie pod testy)?
jarekr000000
  • Rejestracja:ponad 8 lat
  • Ostatnio:2 dni
  • Lokalizacja:U krasnoludów - pod górą
  • Postów:4708
2

Najlepsze, że jak to klasyczne jpa, to często wywalenie linijki z save nic nie zmieni - nadal zmiani będą zapisywane w bazie danych.
testy na mockach czerwone, a aplikacja działa :-)


jeden i pół terabajta powinno wystarczyć każdemu
edytowany 3x, ostatnio: jarekr000000
Shalom
A potem ktoś przypadkiem przesunie @Transactional w inne miejsce, albo zrobi sobie kopię obiektu i nagle przestanie się zapisywać :D
jarekr000000
Akurat to czy jest save, czy nie niewiele zmienia. Zrypany Transactional to i tak problem (przy czym jak coš sie nie zapisze w bazie to i tak mała kara)

Zarejestruj się i dołącz do największej społeczności programistów w Polsce.

Otrzymaj wsparcie, dziel się wiedzą i rozwijaj swoje umiejętności z najlepszymi.