Unit testing – první krůčky
- Ondřej Mirtes
- Člen | 1536
Ahoj,
vím, že má zítra David na PS přednášku (kde bohužel nebudu a doufám,
že ji někdo natočí :)) a abych si to trochu vynahradil, jal jsem se do
zkoušení unit testingu v Nette sám. Nainstaloval jsem přes PEAR phpUnit,
vytvořil si v projektu složku tests a v něm vytvořil jakýsi bootstrap
(nemůžu využít bootstrap z app/, protože obsahuje
$application->run()
):
// absolute filesystem path to the web root
define('WWW_DIR', dirname(__FILE__));
// absolute filesystem path to the application root
define('APP_DIR', WWW_DIR . '/../app');
// absolute filesystem path to the libraries
define('LIBS_DIR', WWW_DIR . '/../libs');
require_once('c:\Program Files (x86)\Apache\php\PEAR\PHPUnit\Framework.php');
require_once(LIBS_DIR . '/Nette/loader.php');
$autoload = new RobotLoader;
$autoload->addDirectory(array(LIBS_DIR, APP_DIR));
$autoload->register();
Environment::loadConfig(APP_DIR . '/config.ini');
Environment::getApplication()->setRouter(new SimpleRouter(array(
'module' => 'Web',
'presenter' => 'Default',
'action' => 'default',
)));
V config.ini jsem musel vytvořit prázdnou sekci
[console < development]
.
Vytvořil jsem si runTests.bat (bohužel jsem nepřišel na to, jak po provedení testů automaticky pustit firefox s vygenerovaným reportem):
phpunit --coverage-html ./report ./tests
A napsal první test:
require_once(dirname(__FILE__) . '/../init.php');
class Web_DefaultPresenterTest extends PHPUnit_Framework_TestCase {
public function testRun() {
$presenter = new Web_DefaultPresenter;
$response = $presenter->run(new PresenterRequest('Web:Default', 'GET', array()));
$this->assertType('RenderResponse', $response);
}
}
Mám otázky :)
- Jdu na to správně? Nešel by třeba nějak zrecyklovat
app/bootstrap.php
místo abych ho defacto zkopíroval a promazal? - Jak moc důkladně testovat presentery? Tuším, že assertType nestačí, měl bych asi testovat i podobu samotného outputu a další věci (?)
- Máte nějaký nápad, jak si ušetřit psaní
require_once(dirname(__FILE__) . '/../init.php');
na začátku každého testu? DíkyRobotLoaderu
jsem si na tyhle věci odvykl :)
Díky.
- DocX
- Člen | 154
- Jak moc důkladně testovat presentery? Tuším, že assertType nestačí, měl bych asi testovat i podobu samotného outputu a další věci (?)
Osobně si myslím, že akce vedoucí pouze k renderu stránky, je blbost testovat na konkrétní podobu výstupu. Ale spíš jen jestli nevyhazují nějakou vyjímku (tzn. všechny odkazy, ziskani dat, apod jsou OK) Případně při požadavku na ‚forbidden‘ akci (třeba editaci nevlastního článku) se vyhodí správná vyjímka, přesměruje nebo to co se prostě chce.
Naproti tomu akce nebo signály vedoucí k nějaké změně, bych testoval na to, jestli se při jejich spuštění požadovaná změna opravdu provede. Případně, jestli si po POST pošle redirect (pokud se to chce), apod. Samozřejmě kontrola vyjímek a stejně jako nahoře i ta kontrola přístupu apod.
Tohle mě jen tak na rychlo napadá, protože testovat presentery jako takové je asi vůbec ve světě PHP docela novinka :)
Editoval DocX (25. 9. 2009 12:48)
- David Grudl
- Nette Core | 8218
Z bootstrapu bych odstranil všechno, co není pro test potřebné, což je … všechno ;) Jinak na to jdeš správně.
Co všechno testovat záleží na konkrétním případu – stačí ti vědět, že presenter vrací nějaký renderovací výstup, nebo chceš vědět obsah určité položky šablony?
- Panda
- Člen | 569
https://api.nette.org/…esponse.html#…
https://api.nette.org/…emplate.html#…
https://api.nette.org/…emplate.html#…
Editoval Panda (5. 10. 2009 21:16)
- Honza Marek
- Člen | 1664
David Grudl napsal(a):
Z bootstrapu bych odstranil všechno, co není pro test potřebné, což je … všechno ;)
No a jak se zbavím toho $application->run()
?
- DocX
- Člen | 154
Honza M. napsal(a):
David Grudl napsal(a):
Z bootstrapu bych odstranil všechno, co není pro test potřebné, což je … všechno ;)
No a jak se zbavím toho
$application->run()
?
Možná
<?php
require APP_DIR . '/Presenters/SomePresenter.php';
...
$request = new PresenterRequest(...);
$presenter = new SomePresenter($request);
$response = $presenter->run();
...
?>
?
Editoval DocX (7. 10. 2009 22:38)
- Patrik Votoček
- Člen | 2221
spíš
$request = new PresenterRequest(...);
$presenterLoader = new PresenterLoader(...);
$presenter = $presenterLoader->getPresenter();
$response = $presenter->run();
- DocX
- Člen | 154
vrtak-cz napsal(a):
spíš
$request = new PresenterRequest(...); $presenterLoader = new PresenterLoader(...); $presenter = $presenterLoader->getPresenter(); $response = $presenter->run();
Ou, jasně, místo Presenter
jsem chtěl napsat
SomePresenter
. A předpokládat, že by tam někde byl include.
Ale tohle je taky pěkný.
- Ped
- Člen | 64
Jen pro zajimavost aktualni pomocni funkce z me „class Presenters_TestCase extends PHPUnit_Framework_TestCase“ tridy… (ocekavam ze to jeste zmenim trochu, asi si udelam prece jen 2 metody pro GET/POST osobitne, ale prozatim mi to umoznuje delat unit testy presenteru presne tak jak potrebuji)
<?php
...
protected function p_run( $_presenter, $_action = 'default', $_params = array() )
{
//prepare request data
$method = count( $_params ) ? 'POST' : 'GET';
$fullparams = array_merge( array( 'action' => $_action ), $_params );
$request = new Nette\Application\PresenterRequest( $_presenter, $method, $fullparams, $_params );
//get desired presenter class
global $application;
$pname = $request->getPresenterName();
$pclassname = $application->getPresenterLoader()->getPresenterClass( $pname );
$request->setPresenterName( $pname );
$request->freeze();
$pclass = new $pclassname;
//execute request (presenter)
$pclass->autoCanonicalize = FALSE; //OPRAVA c.1
return $pclass->run( $request );
}
...
//a nejake telo unit testu pro ukazku:
public function testLogoutWorks()
{
self::doLogin();
$r = $this->p_run( 'Homepage', 'default', array( 'do' => 'logout' ) );
$this->assertFalse( Nette\Environment::getUser()->isAuthenticated() ); //is logged out
$this->assertType('Nette\Application\RedirectingResponse', $r); //app should redirect
}
?>
Jestli se vam neco na tom nelibi, nebo mate vylepseni, budu vdecny za inspiraci.
Timto dekuji za inspiraci, bez vas bych to resil asi mnohem vetsi oklikou.
- edit – oprava c.1
- po pridani druheho presenteru jsem zjistil ze se mi porad vraci #301 s kanonizovanou URI a vzhledem k tomu ze se mi nepovedlo pripravit umely request ktery by neskoncil kanonizaci, tak jsem alespon nasel jak ji vypnout. Neni to tak ciste reseni jako kanonicky request, ale prozatim mne osobne staci.
Editoval Ped (20. 4. 2010 15:31)
- michal.sanger
- Člen | 4
Funguje vám to s aktuální dev verzí Nette? Já dostávám:
Fatal error: Call to a member function getService() on a non-object in Nette\Application\UI\Presenter.php on line 1257
- deter
- Člen | 5
Nerozebehnul nekdo phpunit testovani na verzi se jmennymi prostory? I s nejjednodussim testem
<?php
$this->object = new GuestbookPresenter;
$request = new \Nette\Application\PresenterRequest('Guestbook', 'POST', $requestData);
$response = $this->object->run($request);
?>
vyhazuje 3.radek Fatal error: Call to a member function getService() on a non-object in Nette\Application\Presenter.php on line 1254
- Patrik Votoček
- Člen | 2221
Musíš ještě
/** @var $context Nette\DI\Container */
$this->object->setContext($context);
- joe
- Člen | 313
Tak jsem se taky pustil do testování a po chvilce hraní, kdy jsem
testování konečně zprovoznil mě trochu zarazilo testování
následujícího presenteru (když jsem snad ve všech možných ukázkách
viděl testování právě na RenderResponse
– v nové verzi
Nette tedy na \Nette\Application\Responses\TextResponse
)
class TestPresenter extends \BasePresenter {
public function renderDefault() { /* nic */ }
public function renderTest($id) { /* nic */ }
}
Kód testů:
<?php
// trida BaseTest nacita index.php z document_root
require_once dirname(__FILE__) . '/../../../BaseTest.php';
class TestPresenterTest extends BaseTest {
private $presenter;
protected function setUp() {
$this->presenter = new \FrontModule\TestPresenter();
// (da se tady dostat ke kontextu i jinak?)
$this->presenter->setContext(\Nette\Environment::getContext());
}
public function testRenderDefault() {
$request = new \Nette\Application\Request('Front:Test', 'GET',
array('lang' => 'cs'));
$response = $this->presenter->run($request);
// projde
$this->assertType('Nette\Application\Responses\RedirectResponse', $response);
}
public function testRenderTest() {
$request = new \Nette\Application\Request('Front:Test', 'GET',
array('action' => 'test', 'lang' => 'cs', 'id' => 5));
$response = $this->presenter->run($request);
// neprojde
$this->assertType('\Nette\Application\Responses\TextResponse', $response);
}
}
Proč teda dostávám RedirectResponse
? Může mi to někdo
prosím vysvětlit? Mám snad zase někde něco špatně?
… jinak mi taky celkem dlouho trvalo, než jsem přišel na to, že do
konstruktoru \Nette\Application\Request
musím psát název modulu
bez první dvojtečky :)
ještě OT, jde NetBeans donutit, aby mi napovídala (code completition)?
Editoval joe (25. 8. 2011 6:32)
- Ondřej Brejla
- Člen | 746
joe: v NetBeans: Tools → Options → PHP → General → Global Include Path → Add Folder (a tady vyberes folder s PHPUnit, treba /usr/share/php/PHPUnit, nebo nevim kde ho mas…)
- Filip Procházka
- Moderator | 4668
V panelu projektu máš Source Files
, …
Include Path
. Tak klikneš pravým na Include Path
→
properties
a tam si přidáš knihovnu. :P
- 2bfree
- Člen | 248
S UnitTesty začínám a moc tomu nerozumím. Podle manuálu jsem si všechno nainstaloval, nakonfiguroval a teď se pokouším o svůj první test presenteru.
Mám jednoduchý presenter
<?php
/**
* Easy presenter
* @author [...]
*/
class EasyPresenter extends BasePresenter {
/**
* Set template variables
* @author [...]
* @return void
*/
public function renderDefault() {
// set value of test variable
$this->template->test = 'value';
}
}
?>
a k němu jednoduchý test
<?php
/**
* Easy presenter test
* @author [...]
*/
class EasyPresenterTest extends PHPUnit_Framework_TestCase {
public function testRenderDefault(){
$presenter = new EasyPresenter;
$presenter->setContext(Nette\Environment::getContext());
$request = new Nette\Application\Request('Easy', 'GET');
$response = $presenter->run($request);
$this->assertType(
'Nette\Application\Responses\TextResponse',
$response
);
}
}
?>
Nicméně když pustím v NetBeans test nad EasyPresenter.php, tak mi to do output vygeneruje chybu
PHP Fatal error: Class 'BasePresenter' not found in /private/var/www/test/app/presenters/EasyPresenter.php on line 6
PHP Stack trace:
PHP 1. {main}() /usr/bin/phpunit:0
PHP 2. PHPUnit_TextUI_Command::main() /usr/bin/phpunit:46
PHP 3. PHPUnit_TextUI_Command->run() /usr/lib/php/pear/PHPUnit/TextUI/Command.php:126
PHP 4. PHPUnit_TextUI_Command->handleArguments() /usr/lib/php/pear/PHPUnit/TextUI/Command.php:135
PHP 5. ReflectionClass->newInstanceArgs() /usr/lib/php/pear/PHPUnit/TextUI/Command.php:712
PHP 6. PHPUnit_Util_Skeleton_Test->__construct() /usr/lib/php/pear/PHPUnit/TextUI/Command.php:712
PHP 7. include_once() /usr/lib/php/pear/PHPUnit/Util/Skeleton/Test.php:120
Poradili byste co s tím prosím?