Unit testing – první krůčky

před 10 lety

Ondřej Mirtes
Člen | 1539
+
0
-

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íky RobotLoaderu jsem si na tyhle věci odvykl :)

Díky.

před 10 lety

DocX
Člen | 154
+
0
-
  • 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)

před 10 lety

David Grudl
Nette Core | 6871
+
0
-

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?

před 10 lety

jiriknesl
Člen | 63
+
0
-

Chci vědět názvy šablon a předané proměnné.

před 10 lety

jiriknesl
Člen | 63
+
0
-

Děkuju moc.

před 10 lety

Honza Marek
Člen | 1674
+
0
-

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() ?

před 10 lety

DocX
Člen | 154
+
0
-

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)

před 10 lety

Patrik Votoček
Člen | 2249
+
0
-

spíš

$request = new PresenterRequest(...);
$presenterLoader = new PresenterLoader(...);
$presenter = $presenterLoader->getPresenter();
$response = $presenter->run();

před 10 lety

DocX
Člen | 154
+
0
-

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ý.

před 9 lety

Ped
Člen | 65
+
0
-

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)

před 8 lety

michal.sanger
Člen | 4
+
0
-

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

před 8 lety

deter
Člen | 5
+
0
-

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

před 8 lety

Patrik Votoček
Člen | 2249
+
0
-

Musíš ještě

/** @var $context Nette\DI\Container */
$this->object->setContext($context);

před 8 lety

joe
Člen | 252
+
0
-

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)

před 8 lety

Nox
Člen | 381
+
0
-

Kanonizace možná…

Napovídala co? pokud je to něco co si předtím newoval, mělo by napovídat. Pokud nemá odkud brát, tak /* @var $promenna Typ */. Pokud asserty, tak je to tím že navzdory manuálu je správná verze self::, metody jsou statické

Editoval Nox (25. 8. 2011 7:44)

před 8 lety

Ondřej Brejla
Člen | 748
+
0
-

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…)

před 8 lety

Filip Procházka
Moderator | 4693
+
0
-

V panelu projektu máš Source Files, … Include Path. Tak klikneš pravým na Include Pathproperties a tam si přidáš knihovnu. :P

před 8 lety

joe
Člen | 252
+
0
-

Dík, už funguje všechno, co jsem potřeboval

(první $this->presenter->autoCanonicalize = FALSE;)

před 8 lety

2bfree
Člen | 247
+
0
-

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?

před 8 lety

Filip Procházka
Moderator | 4693
+
0
-

Já ti poradím, přečti si http://www.aceblog.cz/…te-testovat/