Az adatbázis tesztelésének fontossága

Amikor adatbázistesztelésről beszélünk, sokszor csak az adott séma ellenőrzésére gondolunk. Ebben a cikkben nagyobb rálátást kapunk arról, hogy mi mindent kell tennünk, ha az adatbázisunkat le akarjuk tesztelni. Hogyan fonódik össze ez a funkcionális teszteléssel.

Számos könyv szól a tesztvezérelt fejlesztésről (test driven development). Ezek a könyvek általában a munkaegységek tesztelésére fókuszálnak. A munkaegységek számos módon értelmezhetőek, de általában egy osztályt jelentenek.

Így a könyvben a következő áll: írj egy rakás tesztet, a kódot pedig úgy írd meg, hogy sikeresek legyenek a tesztek. Minden külső forrást ki kell zárni, hogy csak ezt az egységet tesztelhesd.

Ez mind szép, de sajnos ebben a pillanatban általában véget is ér a tesztelés. Van ugyan néhány lekérdezés (kézzel írott vagy ORM által generált), de ezeket általában nem nevezhetjük tesztelésnek. A programozók a „teszteljünk könnyedén” módszert szeretik alkalmazni – az ORM elintéz mindent, akár hátra is dőlhetek.

Az adatbázis általában a cégek legértékesebb tulajdona. Az alkalmazásokat időről időre újraírják. A régi alkalmazásoktól megszabadulnak, helyükre újak kerülnek. De még az alkalmazások cseréjénél sem dob el senki egy adatokkal teli adatbázist. Az adatbázisokat gondosan átköltöztetik. Vannak rendszerek, ahol számos különböző alkalmazás használja ugyanazt az adatbázist azonos időben. Ezért fontos egy jól szabályozott adatbázismodell használata, és annak megfelelő kezelése. Ugye még véletlenül se szeretnéd tönkretenni az adatok összefüggéseit, mert azzal komoly kárt okoznál a cégednek.

Ez a cikk a gyakran kihagyott adatbázis-tesztelésről szól. Nem számít igazán, hogy milyen adatbázismotort használsz. Lehet az PostgreSQL, MySQL, Oracle, vagy noSQL adatbázis, mint a MongoDB. Az alábbi szabályok minden adatbázisra és alkalmazásra érvényesek.

Az alkalmazásod általában számos különböző részből áll. Van benne egy kis <az általad választott nyelv kerül ide> kód, néhány konfigurációs fájl, némi SQL-lekérdezés és néhány külső rendszer. Egy alkalmazás tesztelésekor minden egyes részt külön tesztelünk (mivel ez megkönnyíti a hibák feltárását), és leteszteljük, hogyan dolgozik együtt a program összes része. Az adatbázis is egy ilyen külön egység, ezért alaposan le kell tesztelni.

Tesztelés nélküli adatbázis

Az első és legsúlyosabb hiba, ha egyáltalán nem teszteljük az adatbázist. Írsz valami kódot, ami az adatbázist használja. Még unit teszteket is írsz az osztályokra, valami minta adatbázis-kapcsolatot használva.

De mi a helyzet az integrációs tesztekkel? A tesztekkel az alkalmazást az éles környezetben teszteljük. A tesztek egyetlen célja, hogy megbizonyosodjunk az alkalmazás működéséről, miután kikerült az éles környezetre. Ha nem teszteled a valós adatbázison, nem tudhatod, hogy valóban működik-e. A minta adatbázis-kapcsolatodnak hála bármilyen kérést is küldesz, csak a várt értékeket fogod visszakapni. Az integrációs tesztek nélkül igazándiból nem is teszteled az alkalmazásodat.

Tesztelés nélküli adatbázisséma

Az általam megfigyelt csapatok általában használtak valamiféle integrációs tesztet. Leginkább a „teszteljünk könnyedén” módszert alkalmazták: itt van az ORM, felállítunk egy objektumot, az ORM elvégzi a többit, remek, hátra is dőlhetek.

Sohasem láttam olyan csapatot, aki az adatbázissémát tesztelte volna. Képzeld csak el, hogy készítened kell egy indexet az adatbázison, mivel az éles környezetben néhány kérés lassú. Szükséged van erre az indexre, és meg kell győződnöd róla, hogy az alkalmazás következő frissítésekor az index biztosan ott lesz. Miért ne írhatnánk egy egyszerű tesztet, hogy ellenőrizze, létezik-e az index?

Számos dolgot lehet még tesztelni az indexek mellett:

  • elsődleges kulcsok
  • idegen kulcsok
  • ellenőrzések – hogy például az „ár” oszlopban ne legyenek negatív értékek
  • az egyedi oszlopok ellenőrzése – senki se szeretne két felhasználóhoz egy fiókot rendelni

Az éles verziótól eltérő motoron való tesztelés

Az alkalmazás fejlesztésekor adatbázisok széles köréből válogathatsz.

Általában a legjobbat választod közülük, amit a csapat a legjobban ismer, vagy amit a menedzsment választ (néha elég érdekes indokkal). Időnként az alkalmazás több adatbázismotort is használhat egy időben. Néha az alkalmazásokat felkészítik különböző motorok használatára, hogy a vásárló a neki tetsző adatbázist választhassa.

Ha jól írtuk meg a teszteket, akkor az adatbázismotor nem igazán számít.

A programozók lusták, ezért minél gyorsabban végezni akarnak a tesztekkel. Nem akarnak hosszasan várni az eredményekre. Éppen ezért sokan jó memóriakezeléssel rendelkező adatbázist használnak, mint a HSQLBD. Sokkal gyorsabbak, mivel a RAM memóriában vannak tárolva, kihagyva a merevlemezt.

Egy újabb, gyakorta elfelejtett szabály: A tesztelések során az éles környezetben is használt adatbázismotort kell alkalmazni,

Számos programozó használ valami más motort. Aminek az oka egyszerű: „Az éles adatbázisunk túl lassú, így a fejlesztés üteme miatt memóriában dolgozó adatbázismotort kell használnunk.”. Ez nem valami jó ötlet, mivel egy másik motort tesztelsz, nem az éles környezetben használtat. Így gyakorlatilag nem is teszteled le az alkalmazásodat.

Volt egyszer egy problémánk. Adatbázis-lekérdezéseket kellett optimalizálnunk egy session-változó segítségével az adatbázisra való csatlakozás után. Az alkalmazás csak az éles környezetben használt Oracle-t. A változó beállítása után az összes teszt sikertelen lett. Mint kiderült, HSQLDB-nél nem használhatok ilyen változót, mivel egyszerűen nem létezik. Így írhattam egy borzalmas kódot: kapcsolódás után ellenőrizd az adatbázismotort, és állítsd be a változót, ha van.

Még ha nem is okoz problémát több motor használata, tartsd észben, hogy amikor más adatbázismotoron tesztelsz, mint ami az éles környezetben van, akkor egyáltalán nem teszteled az alkalmazásodat.

A tesztek nem készítik elő az adatbázist

A tesztelésnek van egy jól ismert, meghatározott folyamata, ami nagyon egyszerű:

  • Készítsd elő a környezetet
  • Futtasd le a tesztet
  • Ellenőrizd az eredményeket
  • Térj vissza az 1. pontra

Próbáld csak meg az ellenkezőjét, és máris nagy bajok lesznek. Feltűnt, hogy a tesztek után nincs takarítás?

Ez nagyon fontos: a tesztelési környezetet a tesztek előtt, nem pedig utána kell előkészíteni. Nem lehetsz biztos benne, hogy a teszt tisztán lefut. Az alkalmazás leállhat egy hibával, megszakadhat a hálózati kapcsolat vagy elszállhat az alkalmazás (pl: túl kevés a memória). Nem számít, hogy állt le a teszt. Ami számít, az az, hogy a teszt minden egyes alkalommal azonosan előkészített környezetben fusson.

Egyszer elkövettem ezt a hibát: számtalan integrációs tesztet futtattam, melyek minden teszt végén eltakarították a változásokat. Debugger-t használtam a tesztek során, és megálltam a közepén, ha találtam egy hibát. Ezután bármelyik teszt, ami lefutott, kiszámíthatatlan és véletlenszerű eredményeket hozott, mivel olyan környezetben futottak, melyeket megváltoztatott egy előző teszt, de nem rakta rendbe őket a teszt végeztével.

A tesztek előkészítik az adatbázist, de nem ellenőrzik azt

Az előzőekben sokat írtam az adatbázis előkészítéséről. Ehhez fűznék hozzá még valamit. Az adatbázis előkészítése nem elég. Amikor előkészíted az adatbázist a felesleges táblák törlésével, javítások betöltésével, stb., még le kell ellenőrizned azt. Ez az adatbázis előtesztelésének része kell, hogy legyen.

Biztosra kell menned, hogy mindent megfelelően előkészítettél. Ezzel később sok időt spórolhatsz meg. Kiszűrheted vele azt, hogy felbukkanjon egy hiba, amit egy hátrahagyott, ki nem törölt adat okozott.

Az adatbázist létrehozó scripteket nem tesztelik

Minden alkalmazásnak rendelkeznie kell valamiféle telepítési eljárással. Mindig van egy első alkalom, amikor létrehozod az adatbázist.

A programozók hajlamosak kézzel állítgatni az adatbázist, néhány ad hoc adatdefiníciós kéréssel. Nem írják le későbbre, és egyszerűen elfelejtik. Nem frissítik a telepítő szkriptet. Sok csapat nem használ verziókezelő szkriptet (pl: a migrációk Ruby on Rails-nél vagy a Liquibase a Java világában).

A legjobb módja a telepítő szkript tesztelésének az adatbázis újbóli felállítása a tesztkör megkezdése előtt. Nem kell minden egyes teszteset előtt, csak futtasd le egyszer, mielőtt futtatnád a tesztkört.

Tesztelés nélküli idegen kulcsok

Az idegen kulcsok használata a konzisztens adatbázis biztosításának egyik alapvető módja. Egy jó relációs adatbázissémában meg kell lennie ezeknek a kulcsoknak. Ha nincsenek, nos, az egy komoly probléma jele lehet. Bár ez függ az adatmodelltől is, de általában az idegen kulcsok hiánya a rossz tervezés jele.

Az idegen kulcsok tesztelése egyszerű. Csak adj néhány sort egy táblához olyan idegen kulcsokkal, amit még nem rögzítettél a referencia táblában. Hibaüzenetet kell kapnod. Aztán törölj néhány sort a referencia táblából. Ekkor vagy hibát kapsz, vagy nem (ez a kulcs definíciójától függ). Ellenőrizned kell a várt viselkedést, bármelyik is legyen az.

Tesztelés nélküli alapértékek

Egy jó adatbázistervben meg kell határozni néhány értelmes alapértéket. Sokszor ezek egyszerűen csak NULL értékek. De ezeket is tesztelni kell. Nem feltételezheted, hogy csak a te alkalmazásod fog adatokat módosítani az adatbázisban.

Két kérdés:

  • Mi van akkor, ha valaki készít egy javítást és frissít néhány adatot adhoc SQL-lekérdezéseket használva?

  • Mi van akkor, ha egy nap valaki elindít egy másik alkalmazást, mely megváltoztathatja az adatokat, és az új alkalmazás nem a te ORM térképezésedet, vagy DAO osztályaidat fogja használni?

Ha értelmetlen vagy rossz alapértékeket adtál meg, azzal akár az adatot is törölheted, ami sokkal rosszabb, mint egy egyszerű alkalmazáshiba.

Tesztelés nélküli korlátozások (constraints)

Egy adatbázisban sokkal több kikötés van, mint az elsődleges- és idegen kulcsok. Lehetnek olyan oszlopaid, amik egyediek vagy nem üresek. Kikötheted, hogy egy oszlop csak előre definiált értékeket tartalmazzon. Biztosra mehetsz, hogy az ár soha se legyen kisebb, mint 0.

Egy jó adatbázissémának sok kikötést kell tartalmaznia. Ezeket is tesztelni kell. Ha szeretnéd, hogy az ár oszlopban csak pozitív értékek legyenek, mi történik akkor, ha megpróbálsz -1 Ft-ot beírni? Ezt miért nem tesztelted?

Nem feltételezheted, hogy csak a te alaposan tesztelt alkalmazásod fogja használni az adatot, és ezek az ellenőrzések a védelem utolsó bástyái az ilyen hibákkal szemben.

A párhuzamos tesztelés megváltoztathatja az adatbázist

Az adatbázistesztek általában megváltoztatják az adatbázist. Egyszerre akár több tesztet is futtathatsz, de ügyelned kell arra, hogy ezek ne zavarják egymást. Biztosra kell menned, hogy ha az egyik teszt beír valamit az adatbázisba, azt nem fogja egy másik teszt használni.

Ezt általában elég egyszerű elrontani, de itt egy apró tanács: ne futtass több tesztet egyszerre. Ami azt is jelenti, hogy nem futtathatsz azonos tesztsorokat más gépekről.

Ha sok fejlesztőd van, akik futtatnák a tesztjeiket, akkor mindegyiküknek külön adatbázisa legyen, ahová írhatnak a tesztek. Ha van olyan adatbázisod, amely csak olvasható, azt gond nélkül használhatja egyszerre több gép is. De ha olyan szituációba keveredsz, ahol az összes programozó egyazon adatbázist teszteli, akkor előreláthatatlan teszteredményekre számíthatsz.

Mit csinál egy programozó, ha a teszt során hibát talál? Nos, egy jó programozó megpróbálja kijavítani. Ha viszont a teszt csak azért ad hibát, mert egy másik programozó futtatja a saját tesztjét egyazon adatbázison, akkor annak javítása csak pazarolja a programozó idejét.

Nincs Nagy Piros Gomb

A jó programozók lusták. A jó programozók egyre frusztráltabbak lesznek, ha ugyanannak a feladatnak az ismétlésére utasítod őket. A jó programozók automatizálják az ilyen folyamatokat.

Minden egyes projekt esetén létre kell hozni valamit a tesztelési környezetben. Ez mennyi időbe telik? Valóban arra akarod pazarolni a programozók idejét, hogy alkalmazásokat telepítsenek újra, és adatbázisokat töltsenek be minden egyes alkalommal?

Ezért kell a projekthez egy Nagy Piros Gomb. A gomb, amit a programozó megnyomhat, elugorhat egy kávéra, majd a munkába visszatérve néhány perc múlva rádöbben, hogy a Nagy Piros Gomb minden feladatot elvégzett, és minden készen áll.

A Nagy Piros Gombbal sok időt spórolhatunk. Mondhatod, hogy túl lassú mindezt automatizálni. Nos, igazából nem az. Épp ellenkezőleg. Olyan, mintha azt állítanánk, hogy a tesztvezérelt fejlesztés lassú. A legelején lassú, de ahogy egyre összetettebbé válik a projekt, sokkal több idő marad a már meglévő tesztek vagy gombok miatt. Sokféle Nagy Piros Gomb létezhet – használhatod őket alkalmazások telepítésére, tesztek futtatására, meg ehhez hasonlóakra.

Néha a Jenkinst (azaz a Hudsont) is erre használják. Valóban, ez egy remek célszoftver a Nagy Piros Gombos dolgokra. Ami a lényeg, hogy minden programozónak rendelkeznie kell a saját feladatsorával, az összes alkalmazást a saját környezetében telepíteni, ahol kedvére játszhat vele anélkül, hogy másokat zavarna, vagy éppen őt zavarnák.

Eszközök

Számos eszközzel lehet adatbázist tesztelni. Írhatsz egy egyszerű integrációs tesztet az egész séma letesztelésére. A PostgreSQL számára ott van a pgTAP, ami integrálható a Jenkins-be a TAP plug-innel, így a Jenkins is futtathat tesztelést, ami azt ellenőrzi, hogy az adatbázis (beleértve az éles adatbázist is) rendben van-e.

Szerző:
Szymon Guz

<< Vissza