Lo ammetto, pietà di me: non sono un grandissimo esperto di test. Non ne ho mai fatti troppi e probabilmente neppure troppo belli.
Nel mio ultimo progetto, iniziato da poco, ho rimesso mano alla questione e unilateralmente deciso di applicarmi meglio a farli funzionare. Il progetto è scritto in PHP ed usa Zend Framework (da qui in avanti solo ZF). Premetto che quello che segue è probabilmente inutile a chi non ha già una certa esperienza con quel framework.
ZF è sempre stato testabile predilegendo tra i vari framework PHPUnit. Questa scelta si è spinta recentemente fino ad offrire un componente (Zend_Test) che estende le classi base dei test di PHPUnit in modo da esporre ai test dei controller tutti gli oggetti di alto livello necessari per lavorare in modo estremamente pratico. Questi oggetti sono la $request e la $response, ma anche una pletora di nuovi assert() tra cui alcuni fantastici che usano le estensioni XPath e DOM per verificare il contenuto di una pagina durante un test utilizzando appunto XPath o selettori CSS.
Quando scriveremo un test per un controller di una nostra applicazione, faremo dunque derivare il nostro test dalla classe Zend_Test_PHPUnit_ControllerTestCase.
Quelli che seguono sono consigli derivati dalla mia recente esperienza e non vogliono assolutamente essere una trattazione esaustiva sui test dei quali, come innanzi confessavo, non sono un grande esperto. Ma chi non è un grande esperto di test fa fatica a trovare documentazione di base sui test con Zend Framework.. per cui eccomi qua :)
Prima di tutto risolviamo il problema più grave: PHPUnit va installato (non viene distribuito insieme a ZF).
Si può scegliere di farlo in tre modi:
- via PEAR
- installare il pacchetto della propria distribuzione GNU/Linux
- andarsi a prendere direttamente il pacchetto di sorgenti dal sito.
Su Ubuntu 9.04, la mia attuale distribuzione, l’unica possibilità è la 3. Infatti la versione del pacchetto che arriva con la distribuzione è troppo vecchia e Zend_Test fa uso di feature piuttosto recenti (non ci puoi fare niente: ti beccherai subito un errore riguardante il metodo sconosciuto incrementAssertionCounter(). Show stopper.). Anche la 1 non mi è parsa percorribile a causa del mio “PEAR” troppo vecchio per la versione di PhpUnit (non ho indagato moltissimo, a dire il vero).
Ho preso dunque il tgz dal sito di PhpUnit, l’ho scompattato e ho messo tutta la sua directory PHPUnit nella stessa directory dove risiede lo ZF (directory ./library, per la cronaca). In questo modo le classi di PHPUnit sono già visibili senza che sia necessario toccare gli include path, grazie agli autoloader di ZF stesso.
Ho creato una directory “tests” allo stesso livello della directory “application” della mia applicazione, dunque fuori dalla DocumentRoot.
All’interno di questa directory ho messo, per ora: un file AllTests.php che serve come entry point per eseguire tutti i test e un file TestConfiguration.php che in pratica sostituisce le operazioni da fare nell’index.php del sito.
Ecco per esempio come potrebbe essere un primo AllTests.php:
<?php
require 'TestConfiguration.php';
$suite = new PHPUnit_Framework_TestSuite();
$suite->setName('MyApp');
$suite->addTestSuite('controllers_IndexControllerTest');
$suite->addTestSuite('controllers_UsersControllerTest');
PHPUnit_TextUI_TestRunner::run($suite, array());
Ed ecco invece parte del mio TestConfiguration.php
<?php
TestConfiguration::setUp();
class TestConfiguration
{
static function setUp()
{
define('APPLICATION_ENVIRONMENT', 'test');
define('APPLICATION_PATH', realpath(dirname(__FILE__) . '/../application/'));
set_include_path(implode(PATH_SEPARATOR, array(
realpath(APPLICATION_PATH . '/../library'),
realpath(APPLICATION_PATH . '/library'),
realpath(APPLICATION_PATH . '/models'),
realpath(APPLICATION_PATH . '/modules/default/forms'),
get_include_path(),
)));
require_once 'Zend/Loader/Autoloader.php';
$loader = Zend_Loader_Autoloader::getInstance();
$loader->setFallbackAutoloader(true);
}
static function setUpDatabase()
{
// TODO
}
}
Nella directory tests/controllers metterò i test dei controller. Per esempio, un inizio di un test per una home page potrebbe essere:
class controllers_IndexControllerTest extends Zend_Test_PHPUnit_ControllerTestCase
{
public function testHomePageIsASuccessfulRequest()
{
$this->dispatch('/');
// Tests there are no exceptions on the home page
$this->assertFalse($this->response->isException());
$this->assertController('session');
$this->assertAction('new');
$this->assertQueryCount('#LoginForm', 1);
}
}
A questo punto non resta che eseguire i test. Da linea comando sarà sufficiente lanciare un php AllTests.php direttamente dalla directory tests.
Un altro punto di attenzione è dovuto al fatto che PHPUnit inizia molto presto a scrivere in console e questo potrebbe dare fastidio a particolari operazioni di ZF. Nella fattispecie mi sto riferendo al malefico errore “Headers already sent”.
Quello che succedeva a me era che in una preDispatch() di un action plugin, a fronte di una particolare situazione, effettuavo un header(“Location …”). Questa operazione non andava a buon fine proprio per il motivo di cui sopra (e tutti i test fallivano miseramente); per cui, se la vostra applicazione ha un redirect HTTP in un plugin prima dell’effettivo dispatch, potreste incorrere in un errore del genere. La soluzione (almeno per me) è stata quella di non fare la redirect ma di modificare il controller e la action della corrente request. Stesso risultato, ed anche pienamente testabile.
Additional comments powered by BackType