Most, hogy gőzerővel készülök az Integral Vision Workshop rendezvénysorozatunkra, úgy gondoltam megosztok egy pár érdekességet, gondolatot a Drupal 7 verziójáról.

Ma, ahogyan ígértem arról lesz szó, hogyan tudjuk tesztelni az elkészített modulunkat.

A Drupal hetes egyik legnagyobb újításai közé tartozik az, hogy a minőség biztosítása érdekében bevezették az automata tesztelést. Mielőtt rátérnénk arra, hogy ez miért jó és hogyan használható, előtte tegyünk egy kis kitérőt a programozott tesztelés felé.

Az elmúlt hónapokban Marhefka István aka info@ és Zsoldos Péter alias zsepi kalauzolásával merültem el a Test Driven Development világában. Mindketten igen komoly nagyvállalati tapasztalattal rendelkeznek e téren. Egyikük a Javas világot ismeri jobban, míg másikuk a .Net fejlesztőeszközök használatában jártas. Azonban, amit tanultam tőlük, az jóval több mint eszközök puszta ismerte.

A módszer röviden annyi, hogy mielőtt nekiállnánk a kód írásának, a kód automata tesztelését lehetővé tevő teszteseteket írjuk meg. Egy új funkció bevezetése a következő lépésekből kell, hogy álljon:

Első hallásra a világ legnagyobb hülyeségének tűnik, hogy az ember először tök feleslegesnek látszó kódsorok gyártásával foglalkozzon és csak utána lásson neki a tényleges hasznot hajtó kódsorok bepötyögésének. Amennyiben az ember ráveszi magát, hogy egyedül kipróbálja ezt a metodikát, biztosan elmegy tőle a kedve. Elmegy, hisz éppen egy tanulási folyamat elején vagyunk, így az egy-két hónapos gyakorlás utáni egy perces munkákkal az elején, akár egy napot is elbíbelődhetünk.

Amennyiben belevágunk egy ilyen kalandba nem baj, ha megfelelő motiváltsággal, kitartással és egy tapasztalt segítőtárssal vágunk bele. Nekem ez utóbbiból szerencsére kettő is akadt.

Nézzük milyen előnyökkel jár, ha ezt a metodikát követjük.

Az első nyereség az abból adódik, hogy mint mindig, a legelején meg kell terveznünk azt, hogy mit fogunk elkészíteni. Ennek a tervnek a végeredménye tud lenni egy olyan kis program, ami leírja azt, hogy mi az elvárt működése a programunknak. Gondoljunk bele. Már egy egyszerű függvénynél is meg szoktuk mondani, hogy milyen bemeneti paraméterekre milyen kimenetet várunk. Miért ne tennénk ezt úgy, hogy azt később is fel tudjuk használni? Később, amikor már nem lesz kedvünk minden egyes javításkor az összes lehetséges esetet végigpróbálni. Ekkor rettentő jól fog jönni, hogy ezt egy gépre, egy automatára bízhatjuk.

Ha a fenti elképzelés nem is szimpatikus, akkor is valahogyan ki kell próbálnunk az általunk elkészített kódot. Gondoljunk bele, ilyet mi is csinálunk. Folyamatosan készítünk apróbb tesztkódokat, amiket azután kitörlünk. Miért ne őrizhetnénk meg ezeket? Miért kéne kidobálnunk ezt a munkánkat?

Tudom, hogy hihetetlennek hangzik, de amennyiben elég jól ismerünk egy teszt keretrendszert, vagyis, megfelelő gyakorlatunk van a használatában, a plusz munka költsége gyakorlatilag nulla.

Persze, ha valaki erre azt mondja, hogy ez nem igaz, annak igazat kell adnom. Nem nulla, annál jóval kevesebb. Kevesebb, hisz ebben a pillanatban a rövid távú nyereség mellett hosszú távú nyereségeink is lesznek. Ki gondolná ugyanis, hogy egy jól kitesztelt helyen a kódban még egy hiba felbukkanhatna? Senki. Ha jobban belegondolunk, mi is tudunk ilyen eseteket mondani, ráadásul valószínűleg arra is emlékszünk, hogy ezeknek a hibáknak a megtalálására mennyivel több időt kellett anno fordítanunk. Ezeket a régebben kidobált időket is megnyerhetjük.

Amikor azt mondom refaktor, lehet többek ereiben meghűl a vér. Azonban ha azt is közlöm, hogy én refaktor alatt arra gondolok, hogy átírom a kódomat pusztán azért, hogy szebb és tisztább legyen, már többek nemtetszését is kivívhatom. Kivívhatom, hisz azért dolgozzak, hogy se gyorsabb, se jobb ne legyen a kódom, csak más? Puszta programozói kivagyiságból? Csak azért, hogy élvezettel nyúlhassak hozzá és ne kelljen fintorognom az egy két hónappal ezelőtt írt szörnyszülöttem miatt? Miért nem írtam már meg az elején jól? Azért, mert én lennék a legszomorúbb, ha két hét múlva ugyanaz a programozó lennék. Ugyanazokkal a módszerekkel gyártanám az ugyanolyan kódot. Ekkor nem programozó lennék, hanem kisipari kódsegédmunkás. Természetesen nem beszéltünk arról, hogy mi van, ha csapatban dolgozunk. Akkor ezek az igények hatványozottan jelentkeznek, hisz nehéz úgy közösen dolgozni, hogy mindenki csak a saját gondolatmenetének megfelelő kódot gyárt.

Egy ilyen refaktor hosszú távon nagy nyereséggel kecsegtet, de automata tesztek nélkül elképzelhetetlen, hogy ennek a munkának az ember nekilásson. Ezért is jó a menet közbeni tesztkódokat megőrizni, hisz nem kerül semmibe se (a kezdeti tanulási időszakon kívül természetesen), csak nyereségünk lesz belőle.

A harmadik lehetőség az, hogy egy hiba kijavítása után írunk egy rövidke kis tesztet, mely biztosítja azt, hogy a hiba megléte esetén jajveszékeljen a rendszerünk, míg a javítás utá szép csendben lefussanak a tesztek. Ez utóbbit használják elsősorban a Drupal hetes fejlesztése során.

Ezzel a módszerrel el lehet kerülni azt, hogy egy már, a rendszerbe bekerült hibajavítás onnan, valamilyen véletlen folytán kikerüljön, valamint csökkenteni lehet annak az esélyét, hogy egy hibajavítás újabb hibák garmadát generálja.

Amint láttuk, lehet a fejlesztés előtt, a fejlesztés alatt és a fejlesztés végén is használni ezt a módszert. Tapasztalt barátaim szerint azonban a fejlesztés utánra már semmi esetre sem érdemes hagyni, mert akkor nagyon nagy valószínűséggel csak egy frusztráló, kellemetlen hiányérzetünk lesz csak egy el nem végzett feladat után. Nem beszélve arról, hogy olyankor már tényleg csak nyűg lesz ez a feladat és semmi előnyét nem fogjuk élvezni.

Nézzünk meg, hogy a tegnapi kis próba modulomban én hogyan használtam a fent leírtakat.

Első körben szükségünk lesz egy .test a kiterjesztésű fájlra. A Drupal hetesbe integrált Simpletest teszt keretrendszer az összes ilyen fájlt betölti és a benne található osztályokat megjegyzi. Ezt azért fontos tudnunk, mert amennyiben egy újabb osztályt, vagyis tesztesetet írunk, minden esetben ürítenünk kell a gyorstárat.

Általában egy teszteset egy osztály, de én azt javaslom lássunk neki, ezzel most ne törődjünk és majd idővel csiszolódni fog a stílusunk. Van olyan vaskalapos szemlélet, amely szerint egy ilyen osztály egyetlen egy hiba jelzésére szolgálhat csak, de ezt az elején ne próbáljuk meg tartani. Idővel úgyis eljön majd a pillanat, amikor a kilométer hosszúra dagadt tesztosztályunkat darabokra törjük majd. Tíz-tizenöt ilyen darabolás után el fogunk jutni ahhoz az optimumhoz, amellyel csapatunk már kényelmesen tud dolgozni.

Nézzük a modult:

class EntityProbaBaseTestCase extends DrupalWebTestCase {
  ...
}

Először is nekünk csak egy alap tesztesetünk lesz, amiben azt vizsgáljuk, hogy egy, az adatbázisba beszúrt sort vissza tudunk-e olvasni ez entity_load() függvénnyel. Talán nem árulok el titkot, ha elmondom, hogy ezt a tesztet a modul fejlesztésének egy igen korai szakaszában készítettem el, hogy könnyű legyen a fejlesztés.

Ennél a modulnál azért is esett egyértelműen az automata tesztelésre a választásom, mert a modul működését csak igen körülményes módon tudtam volna máshogyan tesztelni.

  public static function getInfo() {
    return array(
      'name' => 'Entity Proba Base',
      'description' => 'Test Entity proba base functionality.',
      'group' => 'EntityProba',
    );
  }

Minden tesztnek kell lennie egy getInfo() függvényének, ami adatokat szolgáltat arról, hogy az adott teszt mit is vizsgál. Ha nincs ilyen függvényünk, akkor a tesztünket nem fogjuk tudni futtatni.

  public function setUp() {
    parent::setUp('entity_proba');  // Enable any modules required for the test
  }

A setUp() függvényben tudjuk bekapcsolni a különböző modulokat, hozhatjuk létre és léptethetjük be a különböző felhasználókat stb. Egyszóval, minden olyan fontos dolgot itt tehetünk meg, ami a tesztünk futásához elengedhetetlenül szükséges. Talán könnyebb megérteni, ha mini telepítési profilra gondolunk itt.

Végül a függvény, ami a tényleges tesztelést végzi:

  public function testEntityProbaLoad() {
    $epid = db_insert('entity_proba')
            ->fields(array(
                'title' => 'Proba',
              ))
            ->execute();

   $entity_proba = entity_load('entity_proba', array($epid));
   $this->assertTrue($entity_proba, 'Jött vissza adat.');
   $this->assertTrue(count($entity_proba) == 1, 'Egy elem jött vissza.');
   $this->assertTrue(isset($entity_proba[$epid]->title), 'Van title tulajdonsága.');
   $this->assertTrue($entity_proba[$epid]->title == 'Proba', 'Az van benne amit beleraktunak.');
  }

Fontos, hogy a tesztfüggvényeink nevét mindig a test szócskával kezdjük. Innen tudja a teszt keretrendszer, hogy ezeket a függvényeket kell futtatni.

A modul első, legzagyvábbnak tűnő részében szúrom be a sort az adatbázisba. Hogy ez milyen varázslás, arról lesz szó majd az elkövetkező napokban. Legyen elég annyi, hogy ez az adatbázis megfelelő táblájába egy megfelelő sort fog beleszúrni.

Ezután az $entity_proba változóba betöltjük az entitásunkat és utána indulnak a vizsgálatok.

Minden ilyen vizsgálat az assert szóval kezdődik, majd annak az eseménynek a leírása következik, aminek meg kell valósulnia. Jelenleg egyfajta vizsgálatom van, méghozzá az, hogy minden értéknek igaznak kell lennie. Bármelyik érték is hamis, a rendszer azonnal fog nekünk jelezni. Tudnád jobban, egyszerűbben, bővebben? Itt a blogomon várom a javaslataidat!

Ha minden jól megy, akkor bekapcsolt simpletest modul esetén máris futtathatjuk a teszteket. Ha bővebben érdekel a téma, vagy további praktikákra vágysz szeretettel látlak a workshopon.