Komplex példa - Teszteljünk! 1.1 - Seeder osztályok

Attila | 2022. 03. 28. 09:17 | Olvasási idő: 2 perc

Címkék: #Adatbázis (Database) #Adatbetöltő (Seeder) #Adatgyár (Factory) #Eloquent #Laravel #Laravel 9 #Query Builder #Tesztelés (Testing)

Miután az előző blogbejegyzés készítése során sikeresen töröltem az adattábláim tartalmát, ez rávilágított számomra arra, hogy mindenképpen készítenem kell (legalább) egy Seeder osztályt. Az ilyen osztályokkal lehet előre definiált gyárainkat működésre bírni, amelyek adatokkal képesek feltölteni az adattábláinkat úgy, hogy mindössze egyetlen parancsot kell utána kiadnunk. De nézzük meg, hogy mit kell ehhez tennünk!
laravel-9-seeder

Tesztadatokra nagyon sokszor szükségünk van, már csak azért is, hogy a webalkalmazásunk funkcionalitásait is tudjuk vizsgálni működés szempontjából. Sikeresen tudom-e lekérni a légitársaságokhoz tartozó repülőjáratokat, vagy a járatok utasait meg tudom-e jeleníteni az oldalon... ezekhez mindig tesztadatokra van szükségünk. Én készítettem újra tesztadatokat az alkalmazásomhoz, de most megnézzük közösen, összefoglalóan, hogy hogyan is működnek a Seeder osztályok, amelyekkel pont ezt tudjuk elérni nagyon egyszerűen. De még mielőtt ezt megtennénk, előtte nézzük meg, hogy milyen lehetőségünk volt eddig tesztadatok létrehozására.


Adatbázis lekérdezés - INSERT

Elég fapados módszer volt, de működött az, ha létrehoztunk egy új útvonalat a web.php -ban, a tartalmát pedig feltöltöttük egy olyan Query Builder-rel készült lekérdezéssel, ami a háttérben gyakorlatilag egy beszúrást hajtott végre.

Route::get('dbseeder', function () {
  DB::table('flights')->insert([
    'name' => Str::random(10),
    'number' => Str::random(10),
    'price' => rand(100,500),
    'captain_id' => 1
  ]);
});

(A szükséges importálásokat persze hajtsuk végre: DB, Str) Ekkor a dbseeder útvonal meglátogatásával már be is szúródott egy sor a flights adattáblánkba.

Ha véletlenszerű (random) adatokkal történt meg a feltöltés, akkor gyakorlatilag egy ciklusba is betehettük volna ezt a lekérdezést, és ezekután a ciklus lefutásának száma határozta meg, hogy mennyi adatsor töltődik be az adattáblába.

Ellenőrizhetjük, hogy beszúródott-e a sor és szerencsére azt látjuk, hogy a flights táblánkba bekerült az új elem, viszont a created_at és az updated_at mezők nem kaptak értéket, pedig az jó lett volna. Ez abból adódik, hogy nincs nekik beállítva alapértelmezett érték, amit akár módosíthatnánk is egy új migrációs fájlban, de talán jobb megoldás lesz az, ha az adatbázis gyárat alkalmazzuk a tesztadatokhoz, lásd a következő alfejezetet.

Megjegyzés: nem akarok előre rohanni, de ugyanezt a lekérdezést viszont betehetjük az újonnan létrejövő Seeder osztályunk run() metódusába és akkor a php artisan db:seed parancs lefutásával ez a lekérdezés is végrehajtódna.


Adatgyárak - Factory

Adatgyárat már használtunk, méghozzá úgy, hogy a Tinker-ben hajtottuk végre őket. Tudjuk, hogy működnek, hiszen leteszteltük őket. Miért ne tehetnénk meg, hogy az új útvonalunkban a már jól működő gyárak lefutásait meghívnánk? Ezt megtehetjük, nézzük, hogy hogyan.

Route::get('dbseeder', function () {
  Airline::factory()->count(10)->create();
  City::factory()->count(5)->create();
  Flight::factory()->count(20)->create();
  Passenger::factory()->count(100)->create();
});

(A szükséges importálásokat persze hajtsuk végre: Airline, City, Flight, Passenger) A dbseeder útvonal egyszeri meglátogatásával a böngészőben most már létrejön 10 helyett 10+20=30 légitársaság (mivel a repülőjáratokhoz mindig új légitársaság jön létre), 5 város, 20 repülőjárat, 100 utas. Az adatbázis Seeder osztályok logikája nagyon hasonló, csak ott más esemény váltja ki ezeknek a gyáraknak a beindulását és működését: itt ugye egy útvonal meglátogatása volt az esemény, ott pedig majd egy parancs kiadása lesz az esemény, ami beindítja a tesztadatok gyártását.


Automatikus adatbetöltő - Seeder

Ezek a Seeder osztályok csak egyetlen egy metódust tartalmaznak, ami a run(). Ez a metódus kerül meghívásra, amikor a

php artisan db:seed

parancsot futtatjuk, alapértelmezetten a database/seeders/DatabaseSeeder osztály run metódusa fog végrehajtódni. Tudjuk így futtatni az összes Seeder osztály mindegyik run() metódusát (mindjárt meg is nézzük, hogyan... de előbb:) vagy pedig egyesével is ki tudjuk jelölni, hogy melyik adatbetöltő hajtódjon végre, ha a --class kapcsolót használjuk és egyelővé tesszük a Seeder osztály nevével (pl.: FlightSeeder).

Töröljük (vagy kommenteljük ki) a web.php -ból a dbseeder útvonalunkat, mivel inkább Seeder osztályt használjunk a nagyobb mennyiségű tesztadatok létrehozásához.

php artisan make:seeder FlightSeeder

A névkonvcencióra itt is érdemes figyelni, hiszen a Laravel ezután már tudni is fogja, hogy mi a Flight Model osztályhoz szeretnénk adatbetöltőt készíteni. Be is kerül a parancs kiadásának hatására az új osztályunk a database/seeders mappába. (Megjegyzés: a Laravel segít amúgy, mutat példát, hiszen ebben a mappában már ott van egy DatabaseSeeder.php fájl, aminek a run() metódusában ott van kikommentelve egy adatgyáras sor, szóval mi is ezt fogjuk csinálni.)

Adjuk hozzá a run() metódus magjához azokat az adatgyáras parancsokat, amelyek korábban a dbseeder útvonalnál szerepeltek a web.php-ban és természetesen adjuk hozzá a fájlhoz a szükséges importálásokat is. Majd futtassuk a következő parancsot:

php artisan db:seed --class=FlightSeeder

Említettem, hogy több Seeder osztály run metódusát is tudjuk futtatni egyidejűleg. Ehhez hajtsuk végre a következő pontokat:

  1. A FlightSeeder run() metódusából vegyük ki a Passenger gyár működtetését végző sort.
  2. Hozzunk létre egy új Seeder osztályt: PassengerSeeder névvel: php artisan make:seeder PassengerSeeder
  3. Helyezzük bele az új PassengerSeeder osztály run() metódusába a Passenger gyáras sort (importálást emellett ne feledjük!).
  4. Az alapértelmezetten létező DatabaseSeeder run() metódusát fogjuk szerkeszteni azért, hogy mind a FlightSeeder, mind a PassengerSeeder adatbetöltése elinduljon.
public function run()
{
  $this->call([
    FlightSeeder::class,
    PassengerSeeder::class
  ]);
}

Végül adjuk ki a php artisan db:seed parancsot az indításhoz. Így mindkét Seeder osztály run() metódusa meghívásra kerül, mi pedig sok-sok tesztadattal gazdagodtunk az adatbázisunkban.

A blogbejegyzéshez tartozó változások elérhetők ebben a Github commit-ben.

Tipp: ha egy Model-hez a Laravel-ben szeretnénk egyből factory és seeder osztályt is létrehozni, akkor a következő parancsot alkalmazhatjuk:

php artisan make:model ModelName -fs