Spring Testing¶
The only constant is change.
Tesztelés típusai¶
A tesztelés a szoftverfejlesztés egyik legfontosabb lépése, amely biztosítja, hogy a szoftvertermékben meglévő hibákra, már a fejlesztési fázisban fény derüljön. A tesztelés növeli a szoftver minőségét és megbízhatóságát, de egyáltalán nem jelenthető ki egyetlen szoftverről sem, hogy hibátlan. A tesztelés egyik alapelve, hogy minél korábban kezdjük el a tesztelést, annál hamarabb találunk meg egy hibát és annál olcsóbb is javítani.
A tesztelés két alapvető típusát különböztetjük meg, az úgynevezett feketedobozos (black-box) és a fehérdobozos (white-box) tesztelést. Ezt nagyobb szoftverek esetén nem is feltétlen ugyanazon személyek végzik.
White-box tesztelés¶
Szokás strukturális tesztelésnek is nevezni, mert mindig egy meglévő (lefejlesztett) struktúrát tesztelünk vele. Egyes kisebb struktúrák tesztelése programozói feladat, míg egyes nagyobb struktúrák tesztelését sokszor külön tesztelői csapat végzi. A white-box tesztelés forráskód alapján történik, ezért ezen teszteket legtöbbször cégen belül végzik el. Struktúrának tekinthető, akár már egy kódsor is. Leggyakrabban tesztelt struktúrák:
- Kódsorok
- Elágazások
- Metódusok
- Osztályok
- Funkciók
- Modulok
Black-box tesztelés¶
Specifikáció alapú tesztelésnek is nevezhető, mert a tesztelők számára egyetlen elérhető információ a szoftverről a specifikációja, valamint természetesen maga a szoftver. Ilyen tesztelés esetén a tesztelők feladata azt ellenőrizni, hogy a szoftver a specifikációnak megfelel-e. A tesztelés azt vizsgálja, hogy a rendszer adott bemenetekre az elvárt választ adja-e. Sokszor ezt a fajta tesztelést külsős tesztelői csoportok végzik, ezzel növelve a tesztelés megbízhatóságát.
A tesztelés szintjei¶
- Komponens teszt
- Integrációs teszt
- Rendszerteszt
- Átvételi teszt
Komponens teszt¶
A komponens teszt a rendszer egy adott komponensét önmagában teszteli. Leggyakoribb formája a unit-teszt és a modulteszt. Jelen esetben mi majd a unit-tesztelésre nézünk majd példát.
Modulteszt¶
A modulteszt általában olyan tesztelést foglal magában, amely nem funkcionális tulajdonságokra terjed ki. Ilyen nem funkcionális tulajdonság például a sebesség, memóriaszivárgás stb.
Unit teszt¶
Unit-teszt a metódusokat teszteli. Segítségével megvizsgáljuk, hogy egy adott metódus visszatérési értéke vagy a metódus hatása megegyezik-e az elvárttal, ha igen a teszt sikeres, ha nem akkor sikertelen. Fontos, hogy magának a unit-tesztnek nem lehet mellékhatása a rendszeren.
Integrációs teszt¶
Az integrációs teszt során a komponensek közötti kölcsönhatásokat vizsgáljuk, vagy esetleg a rendszer és más rendszerek kölcsönhatásait vizsgáljuk (rendszer integrációs teszt). Az integrációs teszt azért fontos, mert nagyobb rendszerek esetén megeshet, hogy egy-egy komponenst más-más fejlesztői csapat fejleszt, így esetleg félreértésekből komoly hibák keletkezhetnek.
Rendszerteszt¶
A rendszerteszt legyakrabban fektetdobozos teszt. Feladata, hogy ellenőrizze, hogy a specifikációnak megfelel-e a termék.
Átvételi teszt¶
Hasonló a rendszerteszthez, mert ekkor is a teljes rendszer vizsgálata történik, de ezt már a végfelhasználók végzik. Több lépésből áll:
- Alfa teszt: Ilyenkor maga a tesztelés a fejlesztő cégnél zajlik, de nem a fejlesztői csapat által. Sokszor különböző segédprogramokkal egérkattintásokat szimulálnak és megvizsgálják, hogy össze-vissza kattintgatás vajon képes-e a rendszert kidönteni.
- Béta teszt: A végfelhasználók egy adott csoportja végzi.
- Felhasználó átvételi teszt: A végfelhasználók éles környezetben használatba veszik a rendszert, de nem alapoznak rá mindent. Általában ilyenkor használják aktívan a rendszert, de mellette a régi bevált módszereket is.
- Üzemeltetői átvételi teszt: A rendszer fenntartói ellenőrzik azon funkciókat, amely a rendszer üzemeltetését segítik esetleg (pl.: biztonsági mentések és helyreállítási funkciók), ha vannak ilyen funkciók.
A fenti egy rövid összefoglaló, arról, hogy milyen tesztelések léteznek egy szoftver esetén. Mi ezen a kurzuson a Unit tesztelést fogjuk megnézni (Spring-es környezetben), mivel a Unit teszteket általában maguk a fejlesztők írják. Akit a tesztelés mélyebben is érdekel felveheti a Szoftvertesztelés Alapjai kurzust, valamint mélyebben is utána olvashat itt.
TDD (Test-Driven Development)¶
Az agilis szoftverfejlesztés részeként alkalmazhatunk úgynevezett Test-Driven Development-et (TDD) is, mely célja, hogy először elhasaló teszteseteket írunk, majd a tesztesetek alapján írjuk meg a tényleges production kódot úgy, hogy a tesztesetek rendre mind "kizöldüljenek".
A TDD által rövid ciklusokban végezhetjük a fejlesztést, melynek lépései:
- Teszt hozzáadása az új funkcionalitáshoz/viselkedéshez
- Futtassuk, hogy lássuk, hogy elhasal
- Írjunk annyi kódot, hogy az előző teszt átmenjen
- Ellenőrizzük, hogy az új teszt mellett az összes eddigi tesz is átmegy
- Kód refaktorálás
- Előzőek iterálása, ameddig az új funkció el nem készül
A bukó tesztek írása segíti a rendszer tényleges megértését a fejlesztő számára, rá van kényszerítve, hogy megértse az új viselkedés mibenlétét. Emellett a gyors visszajelzések mentén jobban fókuszálhatjuk a figyelmünket és gyorsabban kaphatunk pozitív megerősítést, hogy amit csinálunk az jó irányba megy. A legfontosabb két cél, melyet segít elérni a TDD a következőek:
- Regressziós hibák detektálása
- Rendszer egyszerű marad, mivel minél kisebb egységekben szeretnénk teszteket írni (segíti a lazán csatolt megoldásokat)
Unit Testing With JUnit5¶
A Unit teszt esetén legtöbbször a következő szempontokra kell nagyon figyelni:
- Egy osztály minden publikus metódusát tesztelni kell
- A triviálisnak tűnő eseteket mindenképp tesztelni kell
- Kell olyan teszteseteket írni, amely speciális eseteket fednek le
- Kell pozitív és negatív teszteseteket is írni. Negatív teszteset, amikor egy függvényt hibás paraméterezéssel hívunk meg és ellenőrizzük, hogy megfelelő-e a hibakezelés.
A jó Unit tesztek a következő tulajdonságokkal rendelkeznek:
- Olvasható: A teszt olvasója könnyen értelmezheti a tesztet (mit is csinál a teszt). Jó elnevezést kell választani!
- Gyors: Egy tesztnek gyorsan kell futnia, melyet segít a függőségek mockolása (lásd később). A mockolással a függőségeket szimulálhatjuk kontrollált környezetben.
- Független és izolált: A tesztek során a sorrendnek nem szabad számítania. Nem szabad másik teszttől függnie.
- Korrekt: A teszt azt csinálja, amit a neve sugall. Egy teszt egy szimpla esetet tesztel.
- Környezet agnosztikus: A tesztek nem függhetnek környezeti konfigurációktól. Pl.: környezeti változók, fájl adott helyen kell, hogy legyen, stb.
- Megismételhető: A teszt újrafuttatáskor ugyanazt az eredményt kell, hogy adja minden alkalommal.
A legtöbb nyelvhez létezik Unitteszt keretrendszer. A Java nyelv esetén leggyakrabban a JUnit5 keretrendszert használjuk, így most ezzel ismerkedünk meg egy kicsit.
Miért is kellett a JUnit 5 az elődje helyett?
- Modularitás: A korábbi verziókban ez hiányzott. Minden egy JAR-ba volt belegyümüszkélve, ami azt jelentette, hogy mindenki függött a JUnit JAR-tól (az IDE, a build tool-ok, az extension-ök, stb.).
- Bővíthetőség: A JUnit 4-ben két módszer volt a bővítésre:
- Runner API: Saját teszt futtatóhoz a komplett test életciklust implementálni kell (inicializálás, futtatás, setup, teardown, stb.). Egyszerre több futtatót nem lehet kombinálni.
- Rule API: A tesztek előtt és után lefutó szabályok megadása. Nem lehet egyszerre osztály és metódus szintű callback-et is megadni egy rule-hoz
- Java 8
A JUnit 5 keretrendszer az előző verzió teljes újraírása Java 8 használatával, szóval ez azt is jelenti, hogy legalább 8-as java kell a JUnit 5 használatához. A JUnit 5 tervezésekor backward compatibility-t is szem előtt tartották, így a 4-es, sőt még a 3-as JUnit-tal írt tesztjeinket is le tudjuk futtatni.
JUnit architektúra¶
A JUnit 3 fő alprojektből épül fel, melyek mindegyike további modulokat tartalmaz.
- JUnit Platform: JVM teszt keretrendszerek indításához szükséges alapok. Magában foglalja a TestEngine API-t, mely segítségével tesztfuttató rendszereket írhatunk. Szolgáltatja továbbá a ConsoleLauncher-t, melyet a build eszközök használnak, mint például a Gradle és a Maven.
- JUnit Jupiter: A tesztek írásához szükséges alprojekt. Implementálja a TestEngine API-t, szóval a JUnit 5-ös teszteket tudjuk is futtatni.
- JUnit Vintage: TestEngine implementáció JUnit 3-as és 4-es verziójú tesztekhez.
Az alábbi ábrán a JUnit architektúra felépítése jól látható:
A JUnit 5-ben található modulok:
- junit-platform-commons: Közös részeket tartalmazó modul.
- junit-platform-launcher: A Launcher API-t definiálja, melyet a külső tool-ok használhatnak. A Launcher-eket a tesztek felderítésére, szűrésére és futtatására használhatjuk.
- junit-platform-engine: Amennyiben saját TestEngine-t szeretnénk írni, akkor ebben a modulban található API-t kell implementálnunk.
- junit-platform-console: ConsoleLauncher implementáció, mely segítségével elindíthatjuk a junit platform-ot a konzolból (build eszközöknek kell).
- junit-platform-gradle-plugin: JUnit 5 tesztek futtatására alkalmas Gradle plugin.
- junit-platform-surefire-provider: Maven integráció támogatása JUnit 5-höz.
- junit-jupiter-engine: A
junit-platform-engine
API implementációja JUnit 5 tesztekhez, azaz ez a JUnit 5 tesztek futtatója. - junit-jupiter-api: Az API, amit teszt íráshoz használhatunk.
- junit-vintage-engine: Ugyanaz mint az előző, de 3-as és 4-es tesztek futtatásához.
Első tesztünk¶
Első körben hozzunk létre egy függvényt, amelyet később tesztelni fogunk!
Készítsünk egy függvényt, amely egy másodfokú egyenlet gyökeit adja meg, ha ismerjük a
-t, b
-t és c
-t a másodfokú egyenlet megoldóképletből.
A függvény a gyököket egy 1 vagy 2 elemű tömbként adja vissza (0. index = 1. gyök; 1. index = 2. gyök).
Ha nincs valós megoldása az egyenlenek, akkor egy ArithmeticException
kivételt dob a következő hibaüzenettel "No real roots"
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
|
Ahhoz, hogy egy Java projektben JUnit5 segítségével teszteket írjunk az alábbi dependency-re lesz szükségünk:
1 2 3 4 5 |
|
Létre kell hoznunk egy vagy több tesztosztályt, amelyet általában az src/test/java
modulban a projektünkkel azonos package struktúrával adunk meg.
Egy tesztosztály az alábbi struktúrában adható meg:
1 2 3 4 5 6 7 8 9 |
|
Test
postfixummal jelezzük, hogy ez egy tesztosztály (ez csak egy konvenció, bárhogyan hívhatom a teszt osztályaimat).
A tesztosztály @Test
annotációval ellátott függvényei lesznek a tesztesetek.
A függvényeket érdemes úgy elnevezni, hogy minél jobban leírja, hogy az adott teszteset mit vizsgál.
Az első tesztesetünk azt fogja vizsgálni, hogy jó eredményt ad-e vissza a quadraticEquationSolver()
metódusunk, ha egy másodfokú egyenletnek 2 megoldása van.
- Lépés: Keresünk, olyan másodfokú egyenletet, amelynek 2 megoldása van: \(x^2-4x-5\)
- Határozzuk meg az elvárt eredményt: \(\{5,-1\}\)
A második tesztesetünk azt fogja vizsgálni, hogy jó eredményt ad-e vissza a quadraticEquationSolver()
metódusunk, ha egy másodfokú egyenletnek 1 megoldása van.
- Lépés: Keresünk, olyan másodfokú egyenletet, amelynek 1 megoldása van: \(x^2+6x+9\)
- Határozzuk meg az elvárt eredményt: \(\{-3\}\)
A harmadik tesztesetünk azt fogja vizsgálni, hogy jó eredményt ad-e vissza a quadraticEquationSolver()
metódusunk, ha egy másodfokú egyenletnek egytelen valós megoldása sincs.
- Lépés: Keresünk, olyan másodfokú egyenletet, amelynek nincs valós megoldása: \(x^2+2x+8\)
- Határozzuk meg az elvárt eredményt:
ArithmeticException
és"No real roots"
üzenet
A negyedik tesztesetünk azt fogja vizsgálni, hogy jó eredményt ad-e vissza a quadraticEquationSolver()
metódusunk, ha egy másodfokú egyenlet értékeit random határozzuk meg.
- Lépés: Adjuk hozzá a következő dependency-t a
pom.xml
-hez
1 2 3 4 5 6 |
|
Megjegyzés
Ilyen extension-t saját magunk is írhatunk!
A következőben meg kell néznünk, hogy hogyan tudjuk ellenőrizni, hogy jó eredményt ad-e a függvényünk. Ennek ellenőrzéséhez szükségünk van néhány kisegítő függvényre, amelyeket a JUnit ad számunkra:
Method | Működése |
---|---|
assertEquals(expected, result, message) |
Akkor sikeres a teszteset, ha az expected és a result egyenlő (object esetén meghívja az equals() metódust). A message egy nem kötelező paraméter, amely megadja, hogy ha a teszteset elszáll, akkor milyen üzentet adjon vissza. (ez mindenhol igaz) |
assertArrayEquals(expected, result, message) |
Akkor sikeres a teszteset, ha az expected és a result tömb azonos. Fontos,hogy számít a sorrend és az elem szám is! |
assertIterableEquals(expected, result, message) |
Akkor sikeres a teszteset, ha az expected és a result Iterable interface-t megvalósító objektum azonos. Fontos, hogy számít a sorrend és az elem szám is! |
assertNotEquals(expected, result, message) |
Akkor sikeres a teszteset, ha az expected és a result nem egyenlő (Object esetén meghívja az equals() metódust). |
assertTrue(result, message) |
Akkor sikeres a teszteset, ha a result igaz értékű |
assertThrows(expected, executable, message) |
Akkor sikeres a teszteset, ha az executable (legtöbbször lambda expression) dobja az adott exceptiont. Úgy szoktuk vizsgálni, hogy adunk egy lambda kifejezést, amelyen belül meghívjuk a saját függvényünket. |
assertDoesNotThrow(expected, executable, message) |
Akkor sikeres a teszteset, ha az executable (legtöbbször lambda expression) nem dobja az adott exceptiont. Úgy szoktuk vizsgálni, hogy adunk egy lambda kifejezést, amelyen belül meghívjuk a saját függvényünket. |
assertNull(result, message) |
Akkor sikeres a teszteset, ha a result objektum null . |
assertNotNull(result, message) |
Akkor sikeres a teszteset, ha a result objektum nem null . |
assertSame(expected, result, message) |
Akkor sikeres a teszteset, ha a result objektum megegyezik az expected objektummal. Itt fontos megjegyezni, hogy akkor egyezik két objektum, ha referenciáik megegyeznek! |
assertNotSame(expected, result, message) |
Akkor sikeres a teszteset, ha a result objektum nem egyezik az expected objektummal. Akkor nem egyezik meg 2 objektum, ha referciáik különbözőek! |
assertTimeout(expected, executable, message) |
Akkor sikeres a teszteset, ha az executable (legtöbbször lambda expression) lefut az expected által meghatározott idő alatt. |
assertAll(executable) |
Csoportosíthatunk vele ellenőrzéseket. Egymásba is ágyazhatók. (lásd későbbi példák) |
fail(message) |
Sikertelenné tesz egy tesztesetet, ha meghívjuk |
Megeshet, hogy adott teszteseteket csak bizonyos feltételekkel szeretnénk végrehajtani (például más-más teszteseteket szeretnénk futtatni prod
és dev
környezetben, vagy esetleg operációs rendszertől függően).
Egyik megoldás lehet, hogy használjuk a org.junit.jupiter.api.Assumptions
osztályban található statikus függvényeket a következő módon:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
TODO: Annotációk
Most már nagyjából látjuk mire képes a JUnit5.
Jól látható, hogy ezek az ellenőrző függvények sokszor nagyon megkötik a kezünket, de alapvető teszteléshez elegendőek.
Mielőtt továbblépnénk és megnéznénk, hogyan lehet még jobb teszteseteket írni, írjuk meg az osztályunkhoz tartózó alapvető teszteseteket!
Az olvashatóság kedvéért használni fogjuk a @DisplayName("Név")
annotációt, amellyel a teszteseteket könnyen elnevezhetjük (felülírható vele az automatikus elnevezés, amely a metódus nevével lenne ekvivalens).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 |
|
AssertJ¶
Ahogy fentebb említettük a Junit5 által adott metódusok sokszor túl szigorúak (pl.: két tömb csak akkor ekvivalens, ha az elemek pozíciói is megegyeznek) vagy éppen nem elég kifinomultak komolyabb tesztesetek elkészítéséhez (pl.: objektumok összehasonlítása referencia szerint működik csak). Ezért több különböző library is létrejött, amelyek jól összehangolhatók a JUnit keretrendszerrel és assert függvényeikkel könnyebbé teszik a tesztesetek írását. Ebből a legismertebb az AssertJ. Ahhoz, hogy az AssertJ library-t használni tudjuk az alábbi dependency-re lesz szükségünk:
1 2 3 4 5 6 |
|
Valamint a következő statikus importot is el kell helyezzük a tesztosztályunk importjai között:
1 |
|
A következőekben megnézünk néhány alap metódust, amelyeket használhatunk. Későbbiekben, amikor a projektünkhöz írunk tezsteket ennél jóval szélesebb skáláját fogjuk látni az AsserJ által nyújtott függvényeknek.
Az AssertJ használata objektumokkal:¶
Hozzunk létre egy Point
Java osztályt az alábbiak szerint:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
|
Hozzuk létre a Point
osztályunkhoz tartotó tesztosztályt PointTest
névvel a következő módon:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
p1
és p2
objektumok "tartalma" megegyezik, de az isEqualTo()
metódus mégis hamissal tér vissza.
Az isEqualTo()
metódus a referenciákat hasonlítja össze, ha nincs equals()
metódus.
Ha nincs equals()
metódus, akkor használható az isEqualToComparingFieldByField()
.
Több hasonló függvény is található, amelyeket nem sorolunk most fel.
Az AssertJ használata boolean értékekkel:¶
1 2 3 4 5 6 |
|
Az AssertJ használata tömbökkel és Iterable objektumokkal:¶
1 2 3 4 5 6 7 |
|
Bármilyen iterálható objektum összehasonlítható a contains()
metódussal.
A JUnit5 alapvető függvénye nem működik, ha a sorrend nem azonos.
A contains()
metódus nem figyeli a sorrendet.
Több hasonló függvény is található a AssertJ könyvtárban, ezekre később még kitérünk, ha szükségünk lesz rá.
Az AssertJ használata fájlokkal:¶
1 2 3 4 5 6 7 8 9 |
|
Az AssertJ biztosít számunkra olyan metódusokat is, amelyekkel fájlok meglétét, fájlok tulajdonságaira írhatunk vizsgálatokat.
Az AssertJ használata Map objektumokkal:¶
1 2 3 4 5 6 7 8 9 10 |
|
A fenti kódrészlet megmutatja, hogy milyen alapvető metódusokat tartogat számunkra az AssertJ kulcs érték párokat reprezentáló Map
objektumok vizsgálatára.
Az AssertJ használata kivételek ellenőrzésére:¶
1 2 3 4 5 6 7 8 9 10 11 |
|
Természetesen sok más metódust és egyéb lehetőséget is ad számunkra az AssertJ ezekről bővebben olvashattok az alábbi sorozatban: https://www.baeldung.com/introduction-to-assertj
Mockito¶
Az előző részekben láttuk, hogy nagyjából milyen lehetőségeink vannak Unit tesztelésre a Java környezetben.
Mielőtt továbblépnénk megnézzük még egy keretrendszert.
Az mondtuk, hogy a Unit teszt esetén mindig csak egy egységet tesztelünk.
Felmerülhet a kérdés, hogy mi van a dependency-kkel.
Vegyük azt, hogy van egy Repository
és egy Service
rétege az alkalmazásunknak.
Ilyen esetben a service réteg használja a repository réteget.
Ha a mi feladatunk a Service
réteg adott osztályának tesztelése, akkor valahogy el kell érjük, hogy a rendszer ne az eredeti Repository
-t használja, hanem egy általunk írt repository-t, amely utánozza az eredeti repository-t, de általunk meghatározott válaszokkal tér vissza meghatározott bemenetekkel.
Vegyük a következő Service
osztályt, amely használ egy Repository
osztályt!
Most még nem használunk semmilyen Spring-es dependency-t, csak egyszerű Java osztályokat fogunk írni.
UserService
, amelyet tesztelünk:
1 2 3 4 5 6 7 8 9 10 11 12 |
|
A UserService által használt UserRepository:
1 2 3 4 5 6 7 8 |
|
Írjunk olyan teszteseteket a UserService osztályunkhoz, amellyel tudjuk ellenőrizni, hogy a service által visszaadott érték valóban megegyezik a UserRepository által adott válasznak (tekintsünk el attól, hogy a jelenlegi repository megvalósítás mindig igazzal tér vissza)!
Létre kell hoznunk valahogy egy olyan Repository
osztályt, amely utánozza a UserRepository
osztályunkat!
Ennek megvalósításához nyújt segítséget a Mockito keretrendszer.
A következő dependency-ket kell hozzáadnunk a projektünkhöz:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
A következő teszt osztályt fogjuk megírni:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
|
- Létrehozunk egy UserRepository
Mock
objektumot. Ez az objektum lesz, amellyel utánozni fogjuk aUserRepository
osztályt. - Meghatározzuk, hogy mi történjen, ha az osztály adott metódusát adott paraméterekkel hívjuk. Megadjuk, hogy mi legyen a visszatérési érték.
- Megnézzük, hogy tényleg meg lett e hívva a függvény és az adott paraméterekkel.
A következőkben egy kicsit ezt egyszerűsítjük egy @InjectMocks
annotációval.
Ezzel az annotációval elérjük, hogy a vizsgálandó objektumunkat is a Mockito keretrendszer hozza létre valamint a keretrendszer fel is oldja a dependency-ket is.
Ezzel az annotációval így egyszerűsödik a kódunk:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
|
- Létrehozunk egy UserRepository
Mock
objektumot. Ez az objektum lesz, amellyel utánozni fogjuk aUserRepository
osztályt. - Létrehozza a keretrendszer számunkra a
UserService
osztály egy példányát és feloldja a dependency-ket - Meghatározzuk, hogy mi történjen, ha az osztály adott metódusát adott paraméterekkel hívjuk. Megadjuk, hogy mi legyen a visszatérési érték.
- Megnézzük, hogy tényleg meg lett e hívva a függvény és az adott paraméterekkel.
Egy kicsit vissza a JUnit5-hoz¶
A Junit5-ről szóló részben kihagytuk egy fontos elemét a könyvtárnak.
Ennek egyszerűen annyi oka volt, hogy nem feltétlen tudtuk volna bizonyítani hasznosságát könnyedén.
A fenti példában látható, hogy bár le tudtuk csökkenteni a ragacs kódokat, de mégis egy igazán jó Repository
utánzathoz igen sok metódus tulajdonságot kellene megadnunk, ráadásul az összes tesztesetben külön külön.
Felmerülhet a kérdés, hogy vajon van-e ennek Ctrl + C, Ctrl + V kímélő módja?
Természetesen igen, mivel a JUnit ad nekünk 4 olyan annotációt, amellyel tudunk teszteseteket konfigurálni.
Vannak olyan konfigurációk, amelyeket minden egyes teszteset előtt/után külön-külön újra és újra el kell végeznünk.
Ilyen műveletek megadásához a @BeforeEach
/@AfterEach
annotációkat használhatjuk. Ilyen metódusban elhejezhetjük a mock objektumaink tulajdonságait.
Megeshet, hogy szeretnénk valamilyen műveletet az összes teszteset előtt/után végrehajtani.
Ehhez a @BeforeAll
/@AfterALl
annotációkat használhatjuk.
Egy példa a fenti 4 annotációra:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
|
Unit Test Spring-ben¶
Service tesztelés¶
Első körben nézzük meg, hogy a fentiek alapján, hogyan tesztelhetjük a Service rétegünket Spring Boot környezetben!
Lényegében teljesen hasonlóan járunk el mint fent.
A tesztelt metódus legyen a ContactUserDetailsService
registerUser()
metódusa.
Adjuk a következő dependency-t a pom.xml fájlunkhoz (új projekt esetében automatikusan hozzáadja a Spring Initializr ezt a projektünkhöz):
1 2 3 4 5 6 7 8 9 10 11 |
|
A teszttel most azt ellenőrizzük, hogy a ContactUserDetailsService
valóban ugyan azt adja-e vissza, mint a ContactUserDetailsRepository
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
|
- Meghatározzuk, hogy ha a
ContactUserRepository
save()
metódusa hívásra kerül bármilyenContactUser
típusú objektummal, akkor térjen vissza azzal az objektummal, amit átadtunk neki.
Controller tesztelés¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
|
A fenti kódrészlet egy API végpont teszt példája. Nézzük meg mit is csinál az egyes kódrészek
-
A
@SpringJUnitWebConfig
egy összetett annotáció, amely kombinálja a következő annotációkat:@ExtendWith(SpringExtension.class)
: Egy kiterjesztést definiál a JUnit tesztünkhöz@ContextConfiguration
: A spring context konfigurációját adja meg@WebAppConfiguration
: Betölti a WebApplicationContextet
-
Egy
MockMvc
példányon keresztül fogjuk a kéréseket létrehozni, valamint bizonyos elvárásokat is megfogalmazhatunk a válasszal kapcsolatban. - Ha a kikommentezett verziót választjuk, akkor betöltődik a tényleges Spring MVC konfiguráció. A nem kikommentelt verzió talán kicsit közelebb áll a UnitTesztelés filozófiájához, hiszen ekkor csak egyetlen vezérlőt tudunk egyszerre tesztelni. Manuálisan létrehozhatjuk az álfüggőségeket és ez nem jár a Spring konfiguráció betöltésével.
- Megadjuk, hogy milyen kérést szimuláljon a rendszer, valamint a válaszra tehetünk megkötéseket (válasz media type-ja, státus kód, content type, és a JSON-ben is tudunk feltételeket adni).
Hasznos linkek¶
https://reflectoring.io/unit-testing-spring-boot/ https://spring.io/guides/gs/testing-web/ https://docs.spring.io/spring/docs/current/spring-framework-reference/testing.html