Keď som naposledy písal o testovaní nezmienil som žiaden testovací nástroj pre PHP. Pretože som nikdy v PHP testy nepísal. Dobre, nejaké som napísal, ale bolo to zlé. Pri programovaní v PHP používam framework CodeIgniter. Preto, keď som potreboval, tak najjednoduchšie bolo použiť jeho Unit Test triedu. Potom, keď som začal programovať v Node,js, začal som aj húfne písať testy. Teraz som však mal na chvíľku dôvod písať v PHP a chcel som v písaní testov pokračovať. Zistil som, že pre PHP nie je žiaden dobrý nástroj na testovanie, žiaden mi nevyhovoval. Tak som si napísal vlastný, volá sa Sugar.php. V tomto príspevku by som chcel ukázať nejaký príklad a popísať jeho výhody.
describe( '#htmlspecialchars', function() { it ('should replace chosen characters', function() { a('htmlspecialchars')->with('<')->should->be('<'); a('htmlspecialchars')->with('>')->should->be('>'); a('htmlspecialchars')->with('&')->should->be('&'); a('htmlspecialchars')->with('"')->should->be('"'); a('htmlspecialchars')->with('\'')->should->be('\''); }); it ('should leave double quotes when flag ENT_NOQUOTES is set', function() { a('htmlspecialchars')->with('"', ENT_NOQUOTES)->should->be('"'); }); it ('should protect single quote when flag ENT_QUOTES is set', function() { a('htmlspecialchars')->with('\'', ENT_QUOTES )->should->be('''); }); // ... }); sugar();
Výhody
Sugar.php je navrhnutý tak, aby ste mohli písať testy čo najrýchlejšie – žiadne volanie tried, len veľmi krátke funkcie. Tak isto sa snaží, aby každý test vyzeral ako veta, takže už len čítaním viete povedať, čo daný test robí. Zabaľuje jednotlivé testy do väčších celkov, čím zvyšuje celkovú prehľadnosť a tiež šetrí váš čas popisovaním každého jedného testu. Keď to zhrnieme:
- Písanie testov skoro ľudskou rečou
- Väčšia prehľadnosť kódu rozčlenením testov do skupín
- Kratší zápis
- Chytanie chýb
- Spätné dohľadanie chyby (error tracking)
- Možnosť spustiť len špecifické testy
Začíname so Sugar.php
Predstavme si, že testujeme model, ktorý sa stará o objednávky. Má niekoľko metód (create, read, update, delete) a pracuje s databázou. Začnime metódou create. Jej úlohou je vložiť novú objednávku do systému. Prvou vecou ktorú spravíme je volanie describe(). Describe slúži na popísanie aktuálne testovaných funkcií, v našom prípade funkcie create. Druhým parametrom je callback (úplne presne closure), ktorý obsahuje jednotlivé testy tejto funkcie.
require 'sugar.php'; require 'order_model.php'; // Model pre objednávky require 'db.php'; // Trieda starajúca sa o DB, v kóde bude fungovať ako Active Record v Codeigniter describe( '#create', function(){});
Teraz by sme radi vytvorili nejaké testy, tie vytvárame pomocou it(). V popise uvedieme, ako by sme chceli, aby sa funkcia správala, a ako druhý parameter uvedieme jednotlivé testy, ktoré overujú, že sa tak naozaj správa.
// ... requires describe( '#create', function() { it( 'should add new entry, when data are ok', function() { a( [ $model, 'create' ] )->with( [ 'seller_id' => 5, 'product_id' => 6 ] )->should->be->ok(); $order = $db->get( 'order' )->result_array(); a( $order )->should->have->length( 1 ); // V DB by mal byť iba jedna objednávka a( $order[0] )->should->have->properties( [ 'seller_id', 'product_id' ] ); a( $order[0]->seller_id )->should->be( 5 ); a( $order[0]->buyer_id )->should->be( 10 ); }); it( 'should add new entry, when product_id is missing', function() { a( [ $model, 'create' ] )->with( [ 'seller_id' => 5 ] )->should->fail(); $order = $db->get( 'order' )->result_array(); a( $order )->should->have->length( 0 ); // V DB by nemala byť ani jedna objednávka }); });
Ako je možné vidieť, jednotlivé testy sa zapisujú pomocou funkcie a(), do ktorej vložíme požadovanú hodnotu, a nato ďalej nabaľujeme požadovanú funkčnosť. Avšak druhý test nám neprejde. A to preto, lebo v DB bude jedna objednávka a to tá z prvého testu. Ak chceme aby sa nám nejaký kód, napríklad vyprázdnenie DB, spustil pred každým testom použijeme funkciu before_each(), ktorú napíšeme hneď za describe:
// ... requires describe( '#create', function() { before_each(function() { $db->truncate( 'order' ); // Vyprázdni tabuľku order }); it( 'should add new entry, when data are ok', function() { a( [ $model, 'create' ] )->with( [ 'seller_id' => 5, 'product_id' => 6 ] )->should->be->ok(); $order = $db->get( 'order' )->result_array(); a( $order )->should->have->length( 1 ); // V DB by mal byť iba jedna objednávka a( $order[0] )->should->have->properties( [ 'seller_id', 'product_id' ] ); a( $order[0]->seller_id )->should->be( 5 ); a( $order[0]->product_id )->should->be( 6 ); }); it( 'should add new entry, when product_id is missing', function() { a( [ $model, 'create' ] )->with( [ 'seller_id' => 5 ] )->should->fail(); $order = $db->get( 'order' )->result_array(); a( $order )->should->have->length( 0 ); // V DB by nemala byť ani jedna objednávka }); });
Na podobnom princípe funguje after_each().
Closures
Môže sa stať, že vo svojich testoch budete potrebovať nejakú premennú “z vonku”. Môžete použiť globálne premenné, ale to by bolo zbytočné. Sugar.php totižto pracuje na systéme Closures. Neviem to preložiť, ale princíp je v tom, že closure sa správa ako funkcia ale berie ešte parametre “z vonku”, poďme si ukázať príklad (všimnite si use() pri volaní it()):
describe( '#read', function() { $buyer_id = 10; it( 'should find order when after it was created', function() use( $buyer_id ) { // ... create order a( [ $model, 'read' ] )->with( [ 'user_id' => $buyer_id ] )->should->be->ok(); // .. another tests }); });
Takto jednoducho. Samozrejme use() akceptuje viac parametrov.
Testujeme funkcie a metódy
Okrem bežných hodnôt môžeme so Sugar.php testovať aj priebeh funkcií – či zlyhali alebo či vrátili očakávaný výstup. Používame k tomu kľúčové slovo .with(). Do a() vložíme názov funkcie a do with() vložíme parametre. Vyzerá to ako kód vyššie:
a('htmlspecialchars')->with('<')->should->be('<');
Ak chceme testovať metódu nejakého objektu, vložíme callbackový zápis.
a( array( $object, 'method' ) )->with( 'hello' )->should->be( 'hello' );
Záver
Zvyšok je už o skladaní funkcií dokopy. Popis a použitie jednotlivých funkcií nájdete na stránke projektu: https://github.com/tomasbonco/sugar.php.