Ma nem fogsz találni olyan alkalmazást, amely ne lenne tele tesztekkel, így ez a téma minden eddiginél aktuálisabb lesz a kezdő fejlesztők számára: tesztek nélkül nem lehetsz sikeres. Nézzük meg, hogy milyen típusú tesztelést alkalmaznak elvileg, majd részletesen megvizsgáljuk mindazt, amit az egységtesztelésről tudni lehet.
A tesztelés típusai
Mi az a teszt? A Wikipédia szerint: "A szoftvertesztelés egy szoftver- vagy rendszerkomponens végrehajtását foglalja magában egy vagy több érdekes tulajdonság értékelése céljából." Más szóval, rendszerünk helyességének ellenőrzése bizonyos helyzetekben. Nos, lássuk, milyen típusú tesztek léteznek általában:- Egységteszt – Olyan tesztek, amelyek célja a rendszer minden moduljának külön-külön történő ellenőrzése. Ezeket a teszteket a rendszer legkisebb atomi részeire, pl. modulokra kell alkalmazni.
- Rendszertesztelés – Magas szintű tesztelés az alkalmazás egy nagyobb részének vagy a rendszer egészének működésének ellenőrzésére.
- Regressziós tesztelés – Tesztelés, amellyel ellenőrizhető, hogy az új funkciók vagy hibajavítások befolyásolják-e az alkalmazás meglévő funkcióit, vagy bevezetnek-e régi hibákat.
- Funkcionális tesztelés – Annak ellenőrzése, hogy az alkalmazás egy része megfelel-e a specifikációkban, felhasználói történetekben stb.
A funkcionális tesztelés típusai:
- White-box tesztelés — Annak ellenőrzése, hogy az alkalmazás egy része megfelel-e a követelményeknek, miközben ismeri a rendszer belső megvalósítását;
- Black-box tesztelés – Annak ellenőrzése, hogy az alkalmazás egy része megfelel-e a követelményeknek a rendszer belső megvalósításának ismerete nélkül.
- Teljesítményteszt – Olyan tesztek, amelyek annak meghatározására szolgálnak, hogy a rendszer vagy a rendszer része hogyan teljesít bizonyos terhelés mellett.
- Terhelési tesztelés — A rendszer normál terhelés melletti stabilitásának ellenőrzésére szolgáló tesztek, valamint annak a maximális terhelésnek a megállapítása, amelynél az alkalmazás továbbra is megfelelően működik.
- Stressz-teszt – Az alkalmazás nem szabványos terhelések melletti teljesítményének ellenőrzésére és a rendszer meghibásodása előtti maximális terhelés meghatározására szolgáló tesztelés.
- Biztonsági tesztelés – A rendszer biztonságának ellenőrzésére használt tesztek (hackerek, vírusok, bizalmas adatokhoz való jogosulatlan hozzáférés és egyéb kellemes támadások).
- Lokalizációs tesztelés — Az alkalmazás lokalizációjának tesztjei.
- Használhatósági tesztelés – A használhatóság, érthetőség, vonzerő és tanulhatóság ellenőrzését célzó tesztelés.
- Egység — Ez a szakasz az egységtesztekre vonatkozik, amelyeket az alkalmazás különböző rétegeiben alkalmaznak. Az alkalmazáslogika legkisebb osztható egységét tesztelik. Például osztályok, de leggyakrabban módszerek. Ezek a tesztek általában megpróbálják a lehető legjobban elkülöníteni a tesztelteket minden külső logikától. Vagyis megpróbálják azt az illúziót kelteni, hogy az alkalmazás többi része a várakozásoknak megfelelően fut.
Mindig sok ilyen tesztnek kell lennie (több, mint bármely más típusnak), mivel kis darabokat tesztelnek, és nagyon könnyűek, nem fogyasztanak sok erőforrást (értsd: RAM-ot és időt).
- Integráció – Ez a szakasz az integrációs tesztelésre vonatkozik. Ez a tesztelés a rendszer nagyobb darabjait ellenőrzi. Vagyis vagy több logikai elemet (több metódust vagy osztályt) kombinál, vagy egy külső komponenssel ellenőrzi a kölcsönhatás helyességét. Ezek a tesztek általában kisebbek, mint az egységtesztek, mert nehezebbek.
Az integrációs tesztre példa lehet az adatbázishoz való csatlakozás, és az azzal való munkavégzés módszereinek helyes működésének ellenőrzése.
- UI — Ez a rész a felhasználói felület működését ellenőrző tesztekre vonatkozik. Ezek az alkalmazás minden szintjén magukban foglalják a logikát, ezért nevezik végponttól végpontig terjedő teszteknek is. Általában jóval kevesebb van belőlük, mert ezek a legnehezebbek, és a legszükségesebb (használt) útvonalakat kell ellenőrizniük.
A fenti képen azt látjuk, hogy a háromszög különböző részei eltérő méretűek: a valós munkában megközelítőleg azonos arányok vannak a különböző típusú tesztek számában.
Ma a legelterjedtebb teszteket, egységteszteket nézzük meg közelebbről, hiszen minden önmagát tisztelő Java fejlesztőnek tudnia kell ezeket alapszinten használni.
Kulcsfogalmak az egységtesztben
A tesztlefedettség (kódlefedettség) az egyik fő mérőszáma annak, hogy egy alkalmazást mennyire tesztelnek. Ez a kód azon százaléka, amelyet a tesztek lefednek (0-100%). A gyakorlatban sokan ezt a százalékot követik céljukként. Ezzel nem értek egyet, mivel ez azt jelenti, hogy a teszteket ott kezdik alkalmazni, ahol nincs rájuk szükség. Tegyük fel például, hogy vannak szabványos CRUD (create/get/update/delete) műveletek a szolgáltatásunkban további logika nélkül. Ezek a metódusok pusztán közvetítők, amelyek a tárhellyel dolgozó rétegre ruházzák át a munkát. Ebben a helyzetben nincs mit tesztelnünk, kivéve talán azt, hogy az adott metódus hív-e DAO metódust, de ez vicc. A tesztek lefedettségének felmérésére általában további eszközöket használnak: JaCoCo, Cobertura, Clover, Emma stb. A téma részletesebb tanulmányozása érdekében A TDD a tesztvezérelt fejlesztés rövidítése. Ebben a megközelítésben, mielőtt bármi mást tenne, írjon egy tesztet, amely ellenőrzi az adott kódot. Ez fekete doboz tesztelésnek bizonyul: tudjuk, hogy a bemenet van, és tudjuk, hogy mi legyen a kimenet. Ez lehetővé teszi a kódduplikáció elkerülését. A tesztvezérelt fejlesztés az alkalmazás minden egyes funkciójához tesztek tervezésével és fejlesztésével kezdődik. A TDD megközelítésben először egy tesztet hozunk létre, amely meghatározza és teszteli a kód viselkedését. A TDD fő célja, hogy a kódot érthetőbbé, egyszerűbbé és hibamentessé tegye. A megközelítés a következőkből áll:- Megírjuk a tesztünket.
- Futtatjuk a tesztet. Nem meglepő módon meghiúsul, mivel még nem valósítottuk meg a szükséges logikát.
- Adja hozzá a kódot, amely a teszt sikerességét okozza (újra futtatjuk a tesztet).
- Refaktoráljuk a kódot.
A tesztelés szakaszai
A teszt három szakaszból áll:- Adja meg a tesztadatokat (fixtures).
- Gyakorolja a tesztelés alatt álló kódot (hívja a tesztelt módszert).
- Ellenőrizze az eredményeket, és hasonlítsa össze a várt eredményekkel.
Tesztkörnyezetek
Szóval, most a lényegre. Számos tesztkörnyezet (keretrendszer) érhető el a Java számára. Ezek közül a legnépszerűbb a JUnit és a TestNG. Az itt végzett áttekintésünkhöz a következőket használjuk: A JUnit teszt egy olyan metódus egy osztályban, amelyet csak tesztelésre használnak. Az osztály neve általában ugyanaz, mint a tesztelt osztály, a végéhez fűzve a „Test” szót. Például CarService -> CarServiceTest. A Maven build rendszer automatikusan bevonja az ilyen osztályokat a tesztkörbe. Valójában ezt az osztályt tesztosztálynak nevezik. Nézzük röviden az alapvető megjegyzéseket:- A @Test azt jelzi, hogy a módszer egy teszt (alapvetően az ezzel a megjegyzéssel jelölt módszer egységteszt).
- A @Before egy olyan metódust jelöl, amelyet minden teszt előtt végrehajtanak. Például egy osztály feltöltésére tesztadatokkal, bemeneti adatok olvasására stb.
- Az @After az egyes tesztek után meghívott metódusok megjelölésére szolgál (pl. adatok törlésére vagy alapértelmezett értékek visszaállítására).
- A @BeforeClass a @Before-hoz hasonló metódus fölé kerül. De egy ilyen metódust csak egyszer hívják meg az adott osztály összes tesztje előtt, ezért statikusnak kell lennie. Erőforrás-igényesebb műveletek végrehajtására szolgál, mint például egy tesztadatbázis felpörgetése.
- Az @AfterClass a @BeforeClass ellentéte: az adott osztálynál egyszer fut le, de csak az összes teszt után. Például állandó erőforrások törlésére vagy az adatbázisról való leválasztásra használható.
- Az @Ignore azt jelzi, hogy egy módszer le van tiltva, és figyelmen kívül hagyja a teljes tesztfutás során. Ezt különféle helyzetekben használják, például ha az alapmódszert megváltoztatták, és a tesztet még nem dolgozták át a változásokhoz. Ilyen esetekben célszerű leírást is hozzáadni, azaz @Ignore("Néhány leírás").
- A @Test(expected = Exception.class) negatív tesztekhez használatos. Ezek olyan tesztek, amelyek azt ellenőrzik, hogy a metódus hogyan viselkedik hiba esetén, vagyis a teszt azt várja, hogy a metódus valamiféle kivételt dobjon. Az ilyen módszert a @Test annotáció jelzi, de jelzi, hogy melyik hibát kell elkapni.
- A @Test(timeout = 100) ellenőrzi, hogy a metódus végrehajtása legfeljebb 100 ezredmásodperc alatt történik-e.
- A @Mock a mező felett egy álobjektum hozzárendelésére szolgál (ez nem JUnit annotáció, hanem a Mockitotól származik). Szükség szerint közvetlenül a tesztmódszerben állítjuk be a modell viselkedését egy adott szituációhoz.
- A @RunWith(MockitoJUnitRunner.class) egy osztály fölé kerül. Ez a megjegyzés arra utasítja a JUnit-et, hogy hívja meg a teszteket az osztályban. Különféle futók léteznek, köztük ezek: MockitoJUnitRunner, JUnitPlatform és SpringRunner. A JUnit 5-ben a @RunWith annotációt az erősebb @ExtendWith annotáció váltotta fel.
- assertEquals(Object expects, Object facts) — ellenőrzi, hogy az átadott objektumok egyenlőek-e.
- assertTrue(boolean flag) — ellenőrzi, hogy az átadott érték igaz-e.
- assertFalse(boolean flag) — ellenőrzi, hogy az átadott érték hamis-e.
- assertNull(Object object) — ellenőrzi, hogy az átadott objektum null-e.
- assertSame(Object firstObject, Object secondObject) — ellenőrzi, hogy az átadott értékek ugyanarra az objektumra vonatkoznak-e.
- állítsd, hogy(T t, Matcher
matcher) — Ellenőrzi, hogy t megfelel-e a matcherben megadott feltételnek.
Tesztelés a gyakorlatban
Most nézzük meg a fenti anyagot egy konkrét példán. Teszteljük egy szolgáltatás frissítési módszerét. Nem vesszük figyelembe a DAO réteget, mivel az alapértelmezettet használjuk. Adjunk hozzá egy indítót a tesztekhez:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>2.2.2.RELEASE</version>
<scope>test</scope>
</dependency>
És itt van a szolgáltatási osztály:
@Service
@RequiredArgsConstructor
public class RobotServiceImpl implements RobotService {
private final RobotDAO robotDAO;
@Override
public Robot update(Long id, Robot robot) {
Robot found = robotDAO.findById(id);
return robotDAO.update(Robot.builder()
.id(id)
.name(robot.getName() != null ? robot.getName() : found.getName())
.cpu(robot.getCpu() != null ? robot.getCpu() : found.getCpu())
.producer(robot.getProducer() != null ? robot.getProducer() : found.getProducer())
.build());
}
}
8. sor – húzza ki a frissített objektumot az adatbázisból. 9-14. sorok – objektum létrehozása az építtetőn keresztül. Ha a bejövő objektumnak van mezője, állítsa be. Ha nem, akkor hagyjuk, ami az adatbázisban van. Most nézze meg tesztünket:
@RunWith(MockitoJUnitRunner.class)
public class RobotServiceImplTest {
@Mock
private RobotDAO robotDAO;
private RobotServiceImpl robotService;
private static Robot testRobot;
@BeforeClass
public static void prepareTestData() {
testRobot = Robot
.builder()
.id(123L)
.name("testRobotMolly")
.cpu("Intel Core i7-9700K")
.producer("China")
.build();
}
@Before
public void init() {
robotService = new RobotServiceImpl(robotDAO);
}
1. sor – a futónk. 4. sor – elszigeteljük a szolgáltatást a DAO rétegtől egy ál helyett. 11. sor – beállítunk egy tesztentitást (azt, amelyet tengerimalacként fogunk használni) az osztályhoz. 22. sor – beállítjuk a szolgáltatásobjektumot, amit tesztelni fogunk.
@Test
public void updateTest() {
when(robotDAO.findById(any(Long.class))).thenReturn(testRobot);
when(robotDAO.update(any(Robot.class))).then(returnsFirstArg());
Robot robotForUpdate = Robot
.builder()
.name("Vally")
.cpu("AMD Ryzen 7 2700X")
.build();
Robot resultRobot = robotService.update(123L, robotForUpdate);
assertNotNull(resultRobot);
assertSame(resultRobot.getId(),testRobot.getId());
assertThat(resultRobot.getName()).isEqualTo(robotForUpdate.getName());
assertTrue(resultRobot.getCpu().equals(robotForUpdate.getCpu()));
assertEquals(resultRobot.getProducer(),testRobot.getProducer());
}
Itt azt látjuk, hogy a tesztnek három egyértelmű felosztása van: 3-9. sorok – rögzítőelemek megadása. 11. sor – a tesztelés alatt lévő kód végrehajtása. 13-17. sor – az eredmények ellenőrzése. Részletesebben: 3-4. sor – állítsa be a DAO modell viselkedését. 5. sor – állítsa be a frissítendő példányt a szabványunkon felül. 11. sor – használja a metódust, és vegye fel a kapott példányt. 13. sor – ellenőrizze, hogy nem nulla-e. 14. sor – hasonlítsa össze az eredmény azonosítóját és a megadott metódus argumentumokat. 15. sor – ellenőrizze, hogy a név frissült-e. 16. sor – lásd a CPU eredményét. 17. sor – nem adtuk meg ezt a mezőt a példányban, így ennek változatlannak kell maradnia. Itt ellenőrizzük ezt a feltételt. Futtassuk:A teszt zöld! Megkönnyebbülten fellélegezhetünk :) Összefoglalva, a tesztelés javítja a kód minőségét, rugalmasabbá és megbízhatóbbá teszi a fejlesztési folyamatot. Képzelje el, mennyi erőfeszítést igényel a több száz osztályfájlt magában foglaló szoftver újratervezése. Ha mindegyik osztályhoz megírtuk az egységteszteket, akkor magabiztosan tudunk refaktorálni. És ami a legfontosabb, segít könnyen megtalálni a hibákat a fejlesztés során. Srácok és lányok, ma ennyi van. Nyomj egy lájkot és írj kommentet :)
GO TO FULL VERSION