Development

Documentation/it_IT/book/1.1/15-Unit-and-Functional-Testing (diff)

You must first sign up to be able to contribute.

Changes from Version 1 of Documentation/it_IT/book/1.1/15-Unit-and-Functional-Testing

Show
Ignore:
Author:
garak (IP: 85.18.214.242)
Timestamp:
10/31/08 16:01:32 (9 years ago)
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • Documentation/it_IT/book/1.1/15-Unit-and-Functional-Testing

    v0 v1  
     1{{{ 
     2#!WikiMarkdown 
     3 
     4Capitolo 15 - Test unitari e funzionali 
     5======================================= 
     6 
     7I test automatici sono la più grande innovazione nella programmazione dall'orientamento agli oggetti. Particolarmente incidenti nello sviluppo di applicazioni web, essi possono garantirne la qualità anche se i rilasci sono numerosi. Symfony mette a disposizione una varietà di strumenti per facilitare l'automazione dei test, e questo capitolo li introduce. 
     8 
     9Test automatici 
     10--------------- 
     11 
     12Ogni sviluppatore con esperienza nello sviluppo di applicazioni web conosce il tempo necessario ad un buon testing. Scrivere i test case, farli girare ed analizzare i risultati è un lavoro noioso. Inoltre i requisiti delle applicazioni web cambiano continuamente, e ciò porta ad un continuo rilascio di nuove versioni ed al bisogno di refactoring del codice. In tale contesto la nascita di nuovi errori è molto probabile. 
     13 
     14Ecco perchè i test automatici sono suggeriti, se non addirittura necessari, come parte di un ambiente di sviluppo di successo. Un insieme di test case può garantire che un'applicazione faccia veramente ciò per cui è stata pensata. Anche se il codice viene modificato di continuo, i test automatici prevengono regressioni accidentali. Inoltre, essi costringono lo sviluppatore a scrivere i test in un formato standard, rigido, che risulta comprensibile ai framework di testing. 
     15 
     16I test automatici talvolta possono sostituire la documentazione dello sviluppatore, in quanto essi spiegano chiaramente ciò che l'applicazione dovrebbe fare. Un buon insieme di test mostra quale output ci si dovrebbe aspettare per un dato set di input, il che è un buon metodo per spiegare le finalità di un metodo. 
     17 
     18Il framework symfony applica tale principio a se stesso. Il codice interno di symfony è validato tramite test automatici. Tali unit e functional test non sono inclusi nel pacchetto PEAR, ma li puoi scaricare tramite SVN o navigare all'indirizzo [http://www.symfony-project.com/trac/browser/branches/1.1/test]. 
     19 
     20 
     21### Test unitari e test funzionali 
     22 
     23I test unitari confermano che un "pezzo" di codice (componente) unitario fornisce un determinato output in funzione di un dato input. Essi convalidano il funzionamento di funzioni e metodi in ogni singolo caso. Le unit test gestiscono un caso alla volta, per cui ad esempio un singolo metodo potrebbe aver bisogno di diverse unit test se funziona diversamente in certe situazioni. 
     24 
     25I test unitari non convalidano una semplice conversione input-to-output, ma una funzionalità completa. Ad esempio, un sistema di cache può essere convalidato esclusivamente tramite test funzionali, perché esso coinvolge diversi step: la prima volta che una pagina viene richiesta essa viene renderizzata; la seconda volta essa viene recuperata dalla cache. Per cui i test funzionali convalidano un processo e richiedono uno scenario. In symfony, dovresti scrivere test funzionali per tutte le tue azioni. 
     26 
     27Per le interazioni più complesse, questi due tipi potrebbero non bastare. Ad esempio le interazioni Ajax richiedono un browser che esegua JavaScript, per cui per testarle automaticamente c'è bisogno di uno strumento speciale di terze parti. Inoltre gli effetti visuali possono essere convalidati solo da umani. 
     28 
     29Se hai un approccio estensivo ai test automatici, probabilmente avrai bisogno di usare una combinazione di tutti questi metodi. Come linee guida, ricorda di mantenere i test semplici e leggibili. 
     30 
     31>**NOTE** 
     32>I test automatici lavorano confrontando un risultato con un output previsto. In altre parole, essi valutano asserzioni (espressioni tipo `$a == 2`). Il valore di un'asserzione può essere `true` o `false`, e determina quando il test abbia successo o fallisca. La parola "asserzione" è usata comunemente quando si parla di tecniche di test automatici. 
     33</p></blockquote> 
     34` 
     35 
     36### Sviluppo test-driven  
     37 
     38Nella metodologia test-driven development (TDD), i test vengono scritti prima del codice. Scrivere un test ti aiuta a focalizzarti su ciò che la funzione deve fare effettivamente prima di svilupparla. È una buona pratica raccomandata anche da altre metodologie, come Extreme Programming (XP). Inoltre questa tecnica tiene conto dell'innegabile fatto che se non scrivi i test prima non li scriverai mai. 
     39 
     40Ad esempio, immagina di dover sviluppare una funzione di text-stripping. La funzione rimuove gli spazi bianchi all'inizio ed alla fine della stringa, sostituisce tutti i caratteri non alfabetici con un trattino basso e trasforma tutti i caratteri maiuscoli in minuscoli. In TDD, prima di tutto penseresti a tutti i casi possibili e faresti un esempio di input con l'output previsto per ogni possibile caso, come mostrato in Tabella 15-1. 
     41 
     42Tabella 15-1 - Una lista di casi di test per una funzione di text-stripping. 
     43 
     44<table> 
     45  <tr> 
     46    <th>Input</th> 
     47    <th>Expected Output</th> 
     48  </tr> 
     49  <tr> 
     50    <td>`" foo "`</td> 
     51    <td>`"foo"`</td> 
     52  </tr> 
     53  <tr> 
     54    <td>`"foo bar"`</td> 
     55    <td>`"foo_bar"`</td> 
     56  </tr> 
     57  <tr> 
     58    <td>`"-)foo:..=bar?"`</td> 
     59    <td>`"__foo____bar_"`</td> 
     60  </tr> 
     61  <tr> 
     62    <td>`"FooBar"`</td> 
     63    <td>`"foobar`"</td> 
     64  </tr> 
     65  <tr> 
     66    <td>`"Don't foo-bar me!"`</td> 
     67    <td>`"don_t_foo_bar_me_"`</td> 
     68  </tr> 
     69</table> 
     70 
     71Scriveresti i test unitari, li faresti girare e vedresti che falliscono. Poi aggiungeresti il codice necessario a gestire il primo caso, le faresti girare di nuovo, vedresti che il primo passa con successo ed andresti avanti così. Alla fine, quando tutti i test passano con successo, la funzione è corretta. Un'applicazione scritta con una metodologia test-driven finisce generalmente per avere tanto codice quanto ne è necessario per i test. Dato che non vuoi passare tempo a debuggare i casi di test, mantienili semplici. 
     72 
     73>**NOTE** 
     74>Rifattorizzare un metodo potrebbe far sorgere bug non presenti in precedenza. Ecco perché è buona pratica far girare tutti i test automatici prima del rilascio di un'applicazione in produzione; ciò è chiamato regression testing. 
     75 
     76### Il framework di testing Lime  
     77 
     78Ci sono diversi framework di testing nel mondo di PHP, di cui i più famosi sono senz'altro PhpUnit e SimpleTest. Symfony possiede il proprio, chiamato `lime`. È basato sulla libreria Perl `Test::More` ed è TAP compliant, che significa che il risultato dei test viene visualizzato secondo le specifiche del Test Anything Protocol, pensato per una migliore leggibilità dell'ouput dei test. 
     79 
     80Lime fornisce supporto per i test unitari. È più leggero degli altri framework di testing di PHP ed ha diversi vantaggi: 
     81  * Lancia i file di test in una sandbox per evitare strani effetti collaterali tra un test e l'altro. Non tutti i framework di testing garantiscono un ambiente pulito per ogni test. 
     82  * I test di lime sono molto leggibili, e così anche il loro output. Sui sistemi compatibili, lime usa colori diversi per sottolineare dati importanti. 
     83  * Symfony stesso usa i test di lime per il regression testing, per cui puoi trovare diversi esempi di test unitari e funzionali nel suo codice sorgente. 
     84  * Il core di lime è convalidato da test unitari. 
     85  * È scritto in PHP, ed è veloce e scritto bene. È contenuto in un singolo file, `lime.php`, senza alcuna dipendenza. 
     86 
     87I test descritti in seguito si basano sulla sintassi di lime, e funzionano con qualsiasi installazione di symfony. 
     88 
     89>**NOTE** 
     90>I test unitari e funzionali non sono pensati per essere lanciati in produzione. Si tratta di strumenti di sviluppo, e come tali dovrebbero essere usati sulla macchina dello sviluppatore e non sul server di produzione. 
     91 
     92Test unitari 
     93------------ 
     94 
     95I test unitari di symfony sono semplici file PHP che finiscono con `Test.php` e si trovano nella cartella `test/unit/` della tua applicazione. Essi seguono una semplice e leggibile sintassi. 
     96 
     97### Come sono fatti?  
     98 
     99Il Listato 15-1 mostra un tipico insieme di test unitari per la funzione `strtolower()`. Comincia con l'istanza dell'oggetto `lime_test` (non ti preoccupare dei parametri per ora). Ogni test unitario è una chiamata ad un metodo di tale istanza. L'ultimo parametro di tali metodi è sempre una stringa opzionale che serve come output. 
     100 
     101Listato 15-1 - Esempio di unit test, in `test/unit/strtolowerTest.php` 
     102 
     103    <?php 
     104      
     105    include(dirname(__FILE__).'/../bootstrap/unit.php'); 
     106    require_once(dirname(__FILE__).'/../../lib/strtolower.php'); 
     107      
     108    $t = new lime_test(7, new lime_output_color()); 
     109      
     110    // strtolower() 
     111    $t->diag('strtolower()'); 
     112    $t->isa_ok(strtolower('Foo'), 'string', 
     113        'strtolower() returns a string'); 
     114    $t->is(strtolower('FOO'), 'foo', 
     115        'strtolower() transforms the input to lowercase'); 
     116    $t->is(strtolower('foo'), 'foo', 
     117        'strtolower() leaves lowercase characters unchanged'); 
     118    $t->is(strtolower('12#?@~'), '12#?@~', 
     119        'strtolower() leaves non alphabetical characters unchanged'); 
     120    $t->is(strtolower('FOO BAR'), 'foo bar', 
     121        'strtolower() leaves blanks alone'); 
     122    $t->is(strtolower('FoO bAr'), 'foo bar', 
     123        'strtolower() deals with mixed case input'); 
     124    $t->is(strtolower(''), 'foo', 
     125        'strtolower() transforms empty strings into foo'); 
     126 
     127Lancia questo insieme di test dalla linea di comando con il task `test:unit`. L'output della linea di comando è molto esplicito, e ti aiuta a capire quale test è passato e quale ha fallito. Vedi l'output dell'esempio nel Listato 15-2. 
     128 
     129Listato 15-2 - Lanciare un singolo test unitario da linea di comando 
     130 
     131    php symfony test:unit strtolower 
     132         
     133    1..7 
     134    # strtolower() 
     135    ok 1 - strtolower() returns a string 
     136    ok 2 - strtolower() transforms the input to lowercase 
     137    ok 3 - strtolower() leaves lowercase characters unchanged 
     138    ok 4 - strtolower() leaves non alphabetical characters unchanged 
     139    ok 5 - strtolower() leaves blanks alone 
     140    ok 6 - strtolower() deals with mixed case input 
     141    not ok 7 - strtolower() transforms empty strings into foo 
     142    #     Failed test (.\batch\test.php at line 21) 
     143    #            got: '' 
     144    #       expected: 'foo' 
     145    # Looks like you failed 1 tests of 7. 
     146 
     147>**TIP** 
     148>L'istruzione `include` all'inizio del Listato 15-1 è opzionale, ma rende il file uno script PHP indipendente che puoi far girare senza l'ausilio della linea di comando, chiamando `test/unit/strtolowerTest.php`. 
     149 
     150### Metodi dei test unitari 
     151 
     152L'oggetto `lime_test` è dotato di una grande varietà di metodi di test, come mostrato dalla Tabella 15-2. 
     153 
     154Tabella 15-2 - Metodi dell'oggetto `lime_test` per i test unitari 
     155 
     156<table> 
     157  <tr> 
     158    <th>Metodo</th> 
     159    <th>Descrizione</th> 
     160  </tr> 
     161  <tr> 
     162    <td>`diag($msg)`</td> 
     163    <td>Outputs a comment but runs no test</td> 
     164  </tr> 
     165  <tr> 
     166    <td>`ok($test, $msg)`</td> 
     167    <td>Tests a condition and passes if it is true</td> 
     168  </tr> 
     169  <tr> 
     170    <td>`is($value1, $value2, $msg)`</td> 
     171    <td>Compares two values and passes if they are equal (`==`)</td> 
     172  </tr> 
     173  <tr> 
     174    <td>`isnt($value1, $value2, $msg)`</td> 
     175    <td>Compares two values and passes if they are not equal</td> 
     176  </tr> 
     177  <tr> 
     178    <td>`like($string, $regexp, $msg)`</td> 
     179    <td>Tests a string against a regular expression</td> 
     180  </tr> 
     181  <tr> 
     182    <td>`unlike($string, $regexp, $msg)`</td> 
     183    <td>Checks that a string doesn't match a regular expression</td> 
     184  </tr> 
     185  <tr> 
     186    <td>`cmp_ok($value1, $operator, $value2, $msg)`</td> 
     187    <td>Compares two arguments with an operator</td> 
     188  </tr> 
     189  <tr> 
     190    <td>`isa_ok($variable, $type, $msg)`</td> 
     191    <td>Checks the type of an argument</td> 
     192  </tr> 
     193  <tr> 
     194    <td>`isa_ok($object, $class, $msg)`</td> 
     195    <td>Checks the class of an object</td> 
     196  </tr> 
     197  <tr> 
     198    <td>`can_ok($object, $method, $msg)`</td> 
     199    <td>Checks the availability of a method for an object or a class</td> 
     200  </tr> 
     201  <tr> 
     202    <td>`is_deeply($array1, $array2, $msg)`</td> 
     203    <td>Checks that two arrays have the same values</td> 
     204  </tr> 
     205  <tr> 
     206    <td>`include_ok($file, $msg)`</td> 
     207    <td>Validates that a file exists and that it is properly included</td> 
     208  </tr> 
     209  <tr> 
     210    <td>`fail()`</td> 
     211    <td>Always fails--useful for testing exceptions</td> 
     212  </tr> 
     213  <tr> 
     214    <td>`pass()`</td> 
     215    <td>Always passes--useful for testing exceptions</td> 
     216  </tr> 
     217  <tr> 
     218    <td>`skip($msg, $nb_tests)`</td> 
     219    <td>Counts as `$nb_tests` tests--useful for conditional tests</td> 
     220  </tr> 
     221  <tr> 
     222    <td>`todo()`</td> 
     223    <td>Counts as a test--useful for tests yet to be written</td> 
     224  </tr> 
     225</table> 
     226 
     227La sintassi è abbastanza diretta; nota che la maggior parte dei metodi accetta un messaggio come terzo parametro. Tale messaggio viene visualizzato nell'output quando il test passa con successo. In effetti, il metodo migliore per capire questi metodi è quello di usarli, per cui dai un'occhiata al Listato 15-3, che li usa tutti. 
     228 
     229Listato 15-3 - Metodi di test dell'oggetto `lime_test`, in `test/unit/exampleTest.php` 
     230 
     231    <?php 
     232      
     233    include(dirname(__FILE__).'/../bootstrap/unit.php'); 
     234      
     235    // Stub objects and functions for test purposes 
     236    class myObject 
     237    { 
     238      public function myMethod() 
     239      { 
     240      } 
     241    } 
     242      
     243    function throw_an_exception() 
     244    { 
     245      throw new Exception('exception thrown'); 
     246    } 
     247      
     248    // Initialize the test object 
     249    $t = new lime_test(16, new lime_output_color()); 
     250      
     251    $t->diag('hello world'); 
     252    $t->ok(1 == '1', 'the equal operator ignores type'); 
     253    $t->is(1, '1', 'a string is converted to a number for comparison'); 
     254    $t->isnt(0, 1, 'zero and one are not equal'); 
     255    $t->like('test01', '/test\d+/', 'test01 follows the test numbering pattern'); 
     256    $t->unlike('tests01', '/test\d+/', 'tests01 does not follow the pattern'); 
     257    $t->cmp_ok(1, '<', 2, 'one is inferior to two'); 
     258    $t->cmp_ok(1, '!==', true, 'one and true are not identical'); 
     259    $t->isa_ok('foobar', 'string', '\'foobar\' is a string'); 
     260    $t->isa_ok(new myObject(), 'myObject', 'new creates object of the right class'); 
     261    $t->can_ok(new myObject(), 'myMethod', 'objects of class myObject do have amyMethod method'); 
     262    $array1 = array(1, 2, array(1 => 'foo', 'a' => '4')); 
     263    $t->is_deeply($array1, array(1, 2, array(1 => 'foo', 'a' => '4')), 
     264        'the first and the second array are the same'); 
     265    $t->include_ok('./fooBar.php', 'the fooBar.php file was properly included'); 
     266      
     267    try 
     268    { 
     269      throw_an_exception(); 
     270      $t->fail('no code should be executed after throwing an exception'); 
     271    } 
     272    catch (Exception $e) 
     273    { 
     274      $t->pass('exception catched successfully'); 
     275    } 
     276      
     277    if (!isset($foobar)) 
     278    { 
     279      $t->skip('skipping one test to keep the test count exact in the condition', 1); 
     280    } 
     281    else 
     282    { 
     283      $t->ok($foobar, 'foobar'); 
     284    } 
     285      
     286    $t->todo('one test left to do'); 
     287 
     288Troverai molti altri esempi dell'utilizzo di questi metodi nelle unit test di symfony. 
     289 
     290>**TIP** 
     291>Ti potresti chiedere perchè usare `is()` al posto di `ok()`. Il messaggio di errore prodotto da `is()` è molto più esplicito; mostra entrambi i membri del test, mentre `ok()` dice semplicemente che la condizione fallisce. 
     292 
     293### Parametri di test  
     294 
     295L'inizializzazione dell'oggetto `lime_test` prende come primo parametro il numero di test che devono essere eseguiti. Se il numero di test eseguiti alla fine è diverso da questo numero, l'output di lime di avvisa. Ad esempio, l'insieme di test del Listato 15-3 genera l'output del Listato 15-4. L'inizializzazione stipulava che dovessero girare 16 test, ma in effetti ne sono girati solo 15, per cui questo fatto viene indicato nell'output. 
     296 
     297Listato 15-4 - La quantità dei test da eseguire ti aiuta nella pianificazione 
     298 
     299    php symfony test:unit example 
     300    1..16 
     301    # hello world 
     302    ok 1 - the equal operator ignores type 
     303    ok 2 - a string is converted to a number for comparison 
     304    ok 3 - zero and one are not equal 
     305    ok 4 - test01 follows the test numbering pattern 
     306    ok 5 - tests01 does not follow the pattern 
     307    ok 6 - one is inferior to two 
     308    ok 7 - one and true are not identical 
     309    ok 8 - 'foobar' is a string 
     310    ok 9 - new creates object of the right class 
     311    ok 10 - objects of class myObject do have a myMethod method 
     312    ok 11 - the first and the second array are the same 
     313    not ok 12 - the fooBar.php file was properly included 
     314    #     Failed test (.\test\unit\testTest.php at line 27) 
     315    #       Tried to include './fooBar.php' 
     316    ok 13 - exception catched successfully 
     317    ok 14 # SKIP skipping one test to keep the test count exact in the condition 
     318    ok 15 # TODO one test left to do 
     319    # Looks like you planned 16 tests but only ran 15. 
     320    # Looks like you failed 1 tests of 16. 
     321 
     322Il metodo `diag()` non conta come test. Usalo per mostrare commenti, in modo che l'output dei tuoi test sia ordinato e leggibile. D'altra parte, i metodi `todo()` e `skip()` contano come test effettivi. Una combinazione `pass()/fail()` all'interno di un blocco `try/catch` conta come test singolo. 
     323 
     324Una strategia di test pianificata per bene deve contenere un previsto numero di test. Troverai molto utile convalidare i tuoi file di test, specialmente in casi complicati dove i test girano all'interno di condizioni od eccezioni. E se il test fallisce in qualche punto, te ne accorgerai subito in quanto il numero finale di test non corrisponde a quello specificato durante l'inizializzazione. 
     325 
     326Il secondo parametro del costruttore è un oggetto di output che estende la classe `lime_output`. La maggior parte delle volte, dato che i test sono pensati per girare in una CLI, l'output è un oggetto `lime_output_color`, che si avvantaggia dei colori della bash quando disponibili. 
     327 
     328### Il task test:unit 
     329 
     330Il task `test:unit`, che serve a lanciare i test unitari da linea di comando, si aspetta una lista nomi di test oppure un file pattern. Vedi il Listato 15-5 per dettagli. 
     331 
     332Listato 15-5 - Lanciare unit test 
     333 
     334    // Struttura della cartella Test 
     335    test/ 
     336      unit/ 
     337        myFunctionTest.php 
     338        mySecondFunctionTest.php 
     339        foo/ 
     340          barTest.php 
     341 
     342    > symfony test:unit myFunction                   ## Lancia myFunctionTest.php 
     343    > symfony test:unit myFunction mySecondFunction  ## Lancia both tests 
     344    > symfony test:unit 'foo/*'                      ## Lancia barTest.php 
     345    > symfony test:unit '*'                          ## Lancia all tests (recursive) 
     346 
     347### Stub, fixture e autoloading  
     348 
     349In un test unitario, la funzionalità di autoloading non è attiva per default. Ogni classe da utilizzare deve essere definita all'interno del test stesso oppure essere inclusa come dipendenza esterna. Ecco perché molto spesso i file di test cominciano con un gruppo di inclusione, come mostrato nel Listato 15-6. 
     350 
     351Listato 15-6 - Includere classi nei test 
     352 
     353    <?php 
     354      
     355    include(dirname(__FILE__).'/../bootstrap/unit.php'); 
     356    require_once($sf_symfony_lib_dir.'/util/sfToolkit.class.php'); 
     357      
     358    $t = new lime_test(7, new lime_output_color()); 
     359      
     360    // isPathAbsolute() 
     361    $t->diag('isPathAbsolute()'); 
     362    $t->is(sfToolkit::isPathAbsolute('/test'), true, 
     363        'isPathAbsolute() returns true if path is absolute'); 
     364    $t->is(sfToolkit::isPathAbsolute('\\test'), true, 
     365        'isPathAbsolute() returns true if path is absolute'); 
     366    $t->is(sfToolkit::isPathAbsolute('C:\\test'), true, 
     367        'isPathAbsolute() returns true if path is absolute'); 
     368    $t->is(sfToolkit::isPathAbsolute('d:/test'), true, 
     369        'isPathAbsolute() returns true if path is absolute'); 
     370    $t->is(sfToolkit::isPathAbsolute('test'), false, 
     371        'isPathAbsolute() returns false if path is relative'); 
     372    $t->is(sfToolkit::isPathAbsolute('../test'), false, 
     373        'isPathAbsolute() returns false if path is relative'); 
     374    $t->is(sfToolkit::isPathAbsolute('..\\test'), false, 
     375        'isPathAbsolute() returns false if path is relative'); 
     376 
     377Nei test unitari, non devi istanziare solo l'oggetto che stai testando, ma anche quello da cui dipende. Dato che le unit test devono rimanere unitarie, la dipendenza da altre classi potrebbe far fallire più di un test quando la dipendenza viene interrotta. Inoltre, impostare oggetti reali potrebbe divenire costoso, sia in termini di codice che di tempo di esecuzione. Tieni presente che la velocità è cruciale, in quanto gli sviluppatori si stancano presto di un processo lento. 
     378 
     379Ogni qualvolta cominci ad includere diversi script nei tuoi test, potresti sentire il bisogno dell'autoloading. A tale scopo, la classe `sfCore` (che deve essere inclusa manualmente) fornisce il metodo `initSimpleAutoload()`, che si aspetta un percorso assoluto come parametro. Tutte le classi in tale path saranno auto-caricate. Ad esempio, se tu volessi l'autoloading di tutte le classi nel path `$sf_symfony_lib_dir/util/`, comincia la tua unit test come segue: 
     380 
     381    [php] 
     382    require_once($sf_symfony_lib_dir.'/autoload/sfSimpleAutoload.class.php'); 
     383    $autoload = sfSimpleAutoload::getInstance(); 
     384    $autoload->addDirectory($sf_symfony_lib_dir.'/util'); 
     385    $autoload->register(); 
     386 
     387>**TIP** 
     388>Gli oggetti Propel generati si basano su una lunga cascata di classi, per cui se tu volessi testarne uno l'autoloading diventa necessario. Nota che per far funzionare Propel, devi anche includere i file della cartella `vendor/propel/` (per cui la chiamata a `sfCore` diventa `sfCore::initSimpleAutoload(array(SF_ROOT_ DIR.'/lib/model', $sf_symfony_lib_dir.'/vendor/propel'));`) ed aggiungere il core di Propel al percorso (chiamando `set_include_path($sf_symfony_lib_dir.'/vendor'.PATH_SEPARATOR.SF_ROOT_DIR.PATH_SEPARATOR.get_include_path())`). 
     389 
     390Un altro buon meotodo per l'autoloading è l'uso di stub. Uno stub è un'implementazione alternativa di una classe dove i metodi reali vengono sostituiti con semplici dati preconfezionati. Esso imita il comportamento della classe reale, ma senza pagarne il costo. Un buon esempio di stub è una connessione a database od un'interfaccia ad un web service. Nel Listato 15-7, i test unitari per un'API di basano sulla classe `WebService`. Invece di chiamare il vero metodo `fetch()` della classe effettiva, il test utilizza uno stub che restituisce dati di esempio. 
     391 
     392Listato 15-7 - Utilizzo di stub nelle unit test 
     393 
     394    [php] 
     395    require_once(dirname(__FILE__).'/../../lib/WebService.class.php'); 
     396    require_once(dirname(__FILE__).'/../../lib/MapAPI.class.php'); 
     397      
     398    class testWebService extends WebService 
     399    { 
     400      public static function fetch() 
     401      { 
     402        return file_get_contents(dirname(__FILE__).'/fixtures/data/fake_web_service.xml'); 
     403      } 
     404    } 
     405      
     406    $myMap = new MapAPI(); 
     407      
     408    $t = new lime_test(1, new lime_output_color()); 
     409      
     410    $t->is($myMap->getMapSize(testWebService::fetch(), 100)); 
     411 
     412I dati di test possono essere più complicati di una stringa o di una chiamata ad un metodo. Complessi dati di test vengono spesso chiamati fixture. Per chiarezza di codice, è spesso molto meglio tenere le fixture in file separati, specialmente quando devono venire utilizzati da diverse unit test. Inoltre non dimenticare che symfony può trasformare facilmente un file YAML in un array tramite il metodo `sfYAML::load()`. Ciò significa che invece di scrivere lunghi array in PHP, puoi scrivere i tuoi dati di test in un file YAML, come mostrato nel Listato 15-8. 
     413 
     414Listato 15-8 - Utilizzo di fixture nei test unitari 
     415 
     416    // In fixtures.yml: 
     417    - 
     418      input:   '/test' 
     419      output:  true 
     420      comment: isPathAbsolute() returns true if path is absolute 
     421    - 
     422      input:   '\\test' 
     423      output:  true 
     424      comment: isPathAbsolute() returns true if path is absolute 
     425    - 
     426      input:   'C:\\test' 
     427      output:  true 
     428      comment: isPathAbsolute() returns true if path is absolute 
     429    - 
     430      input:   'd:/test' 
     431      output:  true 
     432      comment: isPathAbsolute() returns true if path is absolute 
     433    - 
     434      input:   'test' 
     435      output:  false 
     436      comment: isPathAbsolute() returns false if path is relative 
     437    - 
     438      input:   '../test' 
     439      output:  false 
     440      comment: isPathAbsolute() returns false if path is relative 
     441    - 
     442      input:   '..\\test' 
     443      output:  false 
     444      comment: isPathAbsolute() returns false if path is relative 
     445      
     446    // In testTest.php 
     447    <?php 
     448      
     449    include(dirname(__FILE__).'/../bootstrap/unit.php'); 
     450    require_once($sf_symfony_lib_dir.'/util/sfToolkit.class.php'); 
     451    require_once($sf_symfony_lib_dir.'/yaml/sfYaml.class.php'); 
     452      
     453    $testCases = sfYaml::load(dirname(__FILE__).'/fixtures.yml'); 
     454      
     455    $t = new lime_test(count($testCases), new lime_output_color()); 
     456      
     457    // isPathAbsolute() 
     458    $t->diag('isPathAbsolute()'); 
     459    foreach ($testCases as $case) 
     460    { 
     461      $t->is(sfToolkit::isPathAbsolute($case['input']), $case['output'],$case['comment']); 
     462    } 
     463 
     464### Testare le classi Propel 
     465 
     466Testare le classi Propel è un po' più complesso, perché gli oggetti Propel generati si basano su una lunga serie di classi. Inoltre, devi fornire una connessione a database valida a Propel e devi anche inserire alcuni dati di test nel database. 
     467 
     468Fortunatamente, è abbastanza facile perché symfony ti fornisce tutto ciò di cui hai bisogno: 
     469 
     470  * Per l'autoloading, hai bisogno di inizializzare un oggetto configurazione 
     471  * Per ottenere una connessione a database, hai bisogno di inizializzare la classe `sfDatabaseManager` 
     472  * Per caricare dei dati di test, puoi usare la classe `sfPropelData` 
     473 
     474Un tipo file di test di Propel è mostrato nel Listato 15-9. 
     475 
     476Listato 15-9 - Classi di test per Propel 
     477 
     478    <?php 
     479      
     480    include(dirname(__FILE__).'/../bootstrap/unit.php'); 
     481      
     482    new sfDatabaseManager(ProjectConfiguration::getApplicationConfiguration('frontend', 'test', true)); 
     483    $loader = new sfPropelData(); 
     484    $loader->loadData(sfConfig::get('sf_data_dir').'/fixtures'); 
     485      
     486    $t = new lime_test(1, new lime_output_color()); 
     487      
     488    // begin testing your model class 
     489    $t->diag('->retrieveByUsername()'); 
     490    $user = UserPeer::retrieveByUsername('fabien'); 
     491    $t->is($user->getLastName(), 'Potencier', '->retrieveByUsername() returns the User for the given username'); 
     492 
     493Test funzionali 
     494--------------- 
     495 
     496I test funzionali convalidano parti delle tue applicazioni. Essi simulano una sessione di browsing, eseguono richieste, controllano gli elementi delle risposte, proprio come tu faresti manualmente per controllare che un'azione faccia correttamente ciò per cui è pensata. Nei test funzionali, fai girare uno scenario corrispondente ad un caso d'uso. 
     497 
     498### Come sono fatti i test funzionali?  
     499 
     500Potresti far girare i tuoi test funzionali con un browser di testo e una grande quantità di asserzioni di espressioni regolari, ma ciò risulterebbe in una immensa perdita di tempo. Symfony mette a disposizione un oggetto speciale, chiamato `sfBrowser`, che si comporta come un browser collegato ad un'applicazione symfony senza avere realmente il bisogno di un server, e senza il rallentamento dovuto al protocollo HTTP. Esso fornisce accesso agli oggetti di core per ogni richiesta (richiesta, sessione, contesto, risposta). Symfony mette a disposizione inoltre un'estensione di tale classe chiamata `sfTestBrowser`, pensata esattamente per i test funzionali, che include tutte le capacità di `sfBrowser` con l'aggiunta di qualche metodo intelligente. 
     501 
     502Un test funzionale comincia tradizionalmente con l'inizializzazione di un oggetto test browser. Tale oggetto esegue una richiesta ad un'azione e controlla la presenza di alcuni elementi nella risposta. 
     503 
     504Ad esempio, ogni volta che generi lo scheletro di un modulo tramite i task `generate:module` o `propel:generate-crud`, symfony crea un semplice test funzionale per tale modulo. Il test fa una richiesta all'azione di default del modulo e controlla il codice di stato della risposta, modulo ed azioni calcolati dal sistema di routing, e la presenza di una certa frase nel contenuto della risposta. Per un modulo `foobar`, il file generato `foobarActionsTest.php` è fatto come quello del Listato 15-9. 
     505 
     506Listato 15-9 - Test funzionale di default per un nuovo modulo, in `tests/functional/frontend/foobarActionsTest.php` 
     507 
     508    <?php 
     509      
     510    include(dirname(__FILE__).'/../../bootstrap/functional.php'); 
     511      
     512    // Create a new test browser 
     513    $browser = new sfTestBrowser(); 
     514      
     515    $browser-> 
     516      get('/foobar/index')-> 
     517      isStatusCode(200)-> 
     518      isRequestParameter('module', 'foobar')-> 
     519      isRequestParameter('action', 'index')-> 
     520      checkResponseElement('body', '!/This is a temporary page/') 
     521    ; 
     522 
     523>**TIP** 
     524>I metodi del browser restituiscono un oggetto `sfTestBrowser`, per cui puoi concatenare le chiamate dei tuoi file di test per una migliore leggibilità. Ciò viene chiamato interfaccia fluida all'oggetto, in quanto niente ferma le chiamate al metodo. 
     525 
     526Un test funzionale può contenere diverse richieste e asserzioni più complesse; ne scoprirai tutte le possibilità nelle sezioni a venire. 
     527 
     528Per lanciare un test funzionale, usa il task `test:functional` della linea di comando, come mostrato nel Listato 15-10. Questo task si aspetta come parametri un nome di applicazione ed un nome di test (senza il suffisso `Test.php`). 
     529 
     530Listato 15-10 - Lanciare un test funzionale singolo dalla linea di comando 
     531 
     532    php symfony test:functional frontend foobarActions 
     533    # get /comment/index 
     534    ok 1 - status code is 200 
     535    ok 2 - request parameter module is foobar 
     536    ok 3 - request parameter action is index 
     537    not ok 4 - response selector body does not match regex /This is a temporary page/ 
     538    # Looks like you failed 1 tests of 4. 
     539    1..4 
     540 
     541I test funzionali generati per un nuovo modulo non passano per default. Ciò in quanto in un nuovo modulo, l'azione `index` fa un forward ad una pagina di congratulazioni (inclusa nel modulo `default` di symfony) che contiene la frase "This is a temporary page." Se non modifichi l'azione `index`, i test per questo modulo falliscono, e ciò garantisce che un modulo non completato passi i test. 
     542 
     543>**NOTE** 
     544>Nei test funzionali l'autoloading è attivo, per cui non devi includere i file a mano. 
     545 
     546### Browsing con l'oggetto sfTestBrowser  
     547 
     548Il browser di test è capace di eseguire richieste in POST ed in GET. In entrambi i casi, usa una URI reale come parametro. Il Listato 15-11 mostra come scrivere chiamate all'oggetto `sfTestBrowser` per simulare richieste. 
     549 
     550Listato 15-11 - Simulare richieste tramite l'oggetto `sfTestBrowser` 
     551 
     552    [php] 
     553    include(dirname(__FILE__).'/../../bootstrap/functional.php'); 
     554      
     555    // Crea un nuovo browser di test 
     556    $b = new sfTestBrowser(); 
     557    $b->initialize(); 
     558      
     559    $b->get('/foobar/show/id/1');                   // Richiesta in GET 
     560    $b->post('/foobar/show', array('id' => 1));     // Richiesta in POST 
     561      
     562    // I metodi get() e post() sono shortcut al metodo call() 
     563    $b->call('/foobar/show/id/1', 'get'); 
     564    $b->call('/foobar/show', 'post', array('id' => 1)); 
     565      
     566    // Il metodo call() può simulare richieste con qualsiasi metodo 
     567    $b->call('/foobar/show/id/1', 'head'); 
     568    $b->call('/foobar/add/id/1', 'put'); 
     569    $b->call('/foobar/delete/id/1', 'delete'); 
     570 
     571Una sessione di browsing tipica non contiene solo richieste ad azioni specifiche, ma anche click su link e sui pulsanti del browser. Come mostrato nel Listato 15-12, l'oggetto `sfTestBrowser` può simulare anche tali click. 
     572 
     573Listato 15-12 - Simulare la navigazione tramite l'oggetto `sfTestBrowser` 
     574 
     575    [php] 
     576    $b->get('/');                  // Richiesta per la home page 
     577    $b->get('/foobar/show/id/1'); 
     578    $b->back();                    // Indietro di una pagina nella history 
     579    $b->forward();                 // Avanti di una pagina nella history 
     580    $b->reload();                  // Ricarica la pagina corrente 
     581    $b->click('go');               // Cerca il pulsante o link 'go' e lo clicca 
     582 
     583Il browser di test gestisce anche stack di chiamate, per cui i metodo `back()` e `forward()` funzionano esattamente come nei browser reali. 
     584 
     585>**TIP** 
     586>Il browser di test possiede il proprio meccanismo per gestire le sessioni (`sfTestStorage`) ed i cookie. 
     587 
     588Fra tutte le interazioni che hanno bisogno di essere testate, sicuramente quelle associate alla form sono al primo posto. Per simulare l'input di dati e l'invio della form, hai tre possibilità. Puoi fare una richiesta in POST con i parametri che desideri spedire, chiamare `click()` con i parametri della form sotto forma di array o riempire i campi a uno a uno e cliccare il pulsante submit. Ad ogni modo, ognuno di essi sfocia in una richiesta in POST. Il Listato 15-13 ne mostra un esempio. 
     589 
     590Listato 15-13 - Simulare l'invio di form tramite l'oggetto `sfTestBrowser` 
     591 
     592    // Template di esempio in modules/foobar/templates/editSuccess.php 
     593    <?php echo form_tag('foobar/update') ?> 
     594      <?php echo input_hidden_tag('id', $sf_params->get('id')) ?> 
     595      <?php echo input_tag('name', 'foo') ?> 
     596      <?php echo submit_tag('go') ?> 
     597      <?php echo textarea('text1', 'foo') ?> 
     598      <?php echo textarea('text2', 'bar') ?> 
     599    </form> 
     600      
     601    // Esempio di test funzionale per questa form 
     602    $b = new sfTestBrowser(); 
     603    $b->initialize(); 
     604    $b->get('/foobar/edit/id/1'); 
     605      
     606    // Opzione 1: richiesta in POST 
     607    $b->post('/foobar/update', array('id' => 1, 'name' => 'dummy', 'commit' => 'go')); 
     608      
     609    // Opzione 2: Clicca il pulsante submit con parametri 
     610    $b->click('go', array('name' => 'dummy')); 
     611      
     612    // Opzione 3: riempi i campi a uno a uno e poi clicca su submit 
     613    $b->setField('name', 'dummy')-> 
     614        click('go'); 
     615 
     616>**NOTE** 
     617>Con la seconda e terza opzione, i valori di default della form sono inclusi automaticamente nella submit, per cui non c'è bisogno di specificare il target. 
     618 
     619Quando un'azione finisce con un `redirect()`, il browser di test non la segue automaticamente; lo devi fare a mano tramite `followRedirect()`, come mostrato dal Listato 15-14. 
     620 
     621Listato 15-14 - Il browser di test non segue automaticamente il forward 
     622 
     623    // Esempio di azione in modules/foobar/actions/actions.class.php 
     624    public function executeUpdate($request) 
     625    { 
     626      // ... 
     627      $this->redirect('foobar/show?id='.$request->getParameter('id')); 
     628    } 
     629      
     630    // Esempio di test funzionale per questa azione 
     631    $b = new sfTestBrowser();    
     632    $b->get('/foobar/edit?id=1')-> 
     633        click('go', array('name' => 'dummy'))-> 
     634        isRedirected()->   // Controlla se esiste un forward 
     635        followRedirect();    // Segue il forward 
     636 
     637C'è infine un ultimo metodo molto utile di cui dovresti essere a conoscenza: `restart()` reimposta la history di navigazione, la sessione ed i cookie, proprio come se tu riavviassi il tuo browser. 
     638 
     639Dopo aver fatto la prima richiesta, `sfTestBrowser` fornisce accesso agli oggetti richiesta, contesto e risposta. Significa che puoi controllare molte cose, dal contenuto agli header della risposta, i parametri di richiesta e le configurazioni: 
     640 
     641    $request  = $b->getRequest(); 
     642    $context  = $b->getContext(); 
     643    $response = $b->getResponse(); 
     644 
     645>**SIDEBAR** 
     646>L'oggetto sfBrowser 
     647> 
     648>Tutti i metodi di browsing descritti nei Listati dal 15-10 al 15-13 sono anche disponibili al di fuori delle finalità di testing, tramite l'oggetto `sfBrowser`. Puoi chiamarlo così: 
     649> 
     650>    // Crea un nuovo browser 
     651>    $b = new sfBrowser(); 
     652>    $b->initialize(); 
     653>    $b->get('/foobar/show/id/1')-> 
     654>        setField('name', 'dummy')-> 
     655>        click('go'); 
     656>    $content = $b()->getResponse()->getContent(); 
     657>    // ... 
     658> 
     659>L'oggetto `sfBrowser` è uno strumento molto utile per script batch, ad esempio, se tu volessi navigare una lista di pagine per crearne una versione in cache (consulta il Capitolo 18 per un esempio dettagliato). 
     660 
     661### Utilizzare asserzioni  
     662 
     663Dato che l'oggetto `sfTestBrowser` ha accesso alla risposta ed agli altri componenti della richiesta, puoi eseguire test su di essi. Potresti creare un nuovo oggetto `lime_test` a questo scopo, ma fortunatamente `sfTestBrowser` propone un metodo `test()` che restituisce un oggetto `lime_test` dove puoi chiamare i metodi di asserzione descritti precedentemente. Controlla il Listato 15-15 per vedere come fare asserzioni tramite `sfTestBrowser`. 
     664 
     665Listato 15-15 - Il browser di test fornisce funzionalità di testing tramite il metodo `test()` 
     666 
     667    $b = new sfTestBrowser(); 
     668    $b->get('/foobar/edit/id/1'); 
     669    $request  = $b->getRequest(); 
     670    $context  = $b->getContext(); 
     671    $response = $b->getResponse(); 
     672      
     673    // Avere accesso ai metodi lime_test tramite test() 
     674    $b->test()->is($request->getParameter('id'), 1); 
     675    $b->test()->is($response->getStatuscode(), 200); 
     676    $b->test()->is($response->getHttpHeader('content-type'), 'text/html;charset=utf-8'); 
     677    $b->test()->like($response->getContent(), '/edit/'); 
     678 
     679>**NOTE** 
     680>I metodi `getResponse(), getContext(), getRequest()`, e `test()` non restituiscono un oggetto `sfTestBrowser`, perciò dopo di essi non puoi concatenare altre chiamate al metodo `sfTestBrowser`. 
     681 
     682Puoi facilmente controllare cookie entranti ed uscenti tramite gli oggetti richiesta e risposta, come mostrato dal Listato 15-16. 
     683 
     684Listato 15-16 - Testare i cookie tramite `sfTestBrowser` 
     685 
     686    $b->test()->is($request->getCookie('foo'), 'bar');     // Incoming cookie 
     687    $cookies = $response->getCookies(); 
     688    $b->test()->is($cookies['foo'], 'foo=bar');            // Outgoing cookie 
     689 
     690Utilizzare il metodo `test()` per testare gli elementi della richiesta richiede linee lunghe di codice. Fortunatamente, `sfTestBrowser` contiene una manciata di metodi proxy che ti aiutano a mantenere i tuoi test funzionali leggibili e corti; inoltre restituiscono a loro volta un oggetto `sfTestBrowser`. Ad esempio, puoi riscrivere il Listato 15-15 come il Listato 15-17. 
     691 
     692Listato 15-17 - Testare direttamente tramite `sfTestBrowser` 
     693 
     694    $b = new sfTestBrowser(); 
     695    $b->get('/foobar/edit/id/1')-> 
     696        isRequestParameter('id', 1)-> 
     697        isStatutsCode()-> 
     698        isResponseHeader('content-type', 'text/html; charset=utf-8')-> 
     699        responseContains('edit'); 
     700 
     701Lo stato 200 è il valore di default previsto da `isStatusCode()`, per cui puoi chiamare tale metodo senza alcun parametro per testare una risposta che passi con successo. 
     702 
     703Un ulteriore vantaggio dei metodi proxy è che non hai bisogno di specificare un testo di output come faresti con i metodi `lime_test`. I messaggi vengono generati automaticamente dai metodi proxy, e l'output risulta chiaro e leggibile. 
     704 
     705    # get /foobar/edit/id/1 
     706    ok 1 - request parameter "id" is "1" 
     707    ok 2 - status code is "200" 
     708    ok 3 - response header "content-type" is "text/html" 
     709    ok 4 - response contains "edit" 
     710    1..4 
     711 
     712In pratica, i metodi proxy del Listato 15-17 coprono la maggior parte dei test usuali, per cui utilizzerai raramente il metodo `test()` di un oggetto `sfTestBrowser`. 
     713 
     714Il Listato 15-14 mostrava che l'oggetto `sfTestBrowser` non segue automaticamente le redirezioni. Ciò ha un vantaggio: le puoi testare. Per esempio, il Listato 15-18 mostra come testare la risposta del Listato 15-14. 
     715 
     716Listato 15-18 - Testare le redirezioni con `sfTestBrowser` 
     717 
     718    $b = new sfTestBrowser(); 
     719    $b-> 
     720        get('/foobar/edit/id/1')-> 
     721        click('go', array('name' => 'dummy'))-> 
     722        isStatusCode(200)-> 
     723        isRequestParameter('module', 'foobar')-> 
     724        isRequestParameter('action', 'update')-> 
     725      
     726        isRedirected()->      // Controlla che la risposta è un redirect 
     727        followRedirect()->    // Segue il redirect 
     728      
     729        isStatusCode(200)-> 
     730        isRequestParameter('module', 'foobar')-> 
     731        isRequestParameter('action', 'show'); 
     732 
     733### Utilizzare selettori CSS  
     734 
     735Diversi test funzionali convalidano una pagina controllando che sia presente un certo testo all'interno del contenuto. Con l'aiuto del metodo delle espressioni regolari `responseContains()`, puoi controllare testo visualizzato, attributi dei tag o valori. Ma se tu volessi testare qualcosa profondamente nascosto nel DOM della risposta, le espressioni regolari non sono più il metodo ideale. 
     736 
     737Ecco perchè l'oggetto `sfTestBrowser` supporta il metodo `getResponseDom()`. Esso restituisce un oggetto libXML2 DOM, molto più facile di un file di testo da parsare e da testare. Nel Listato 15-19 trovi un esempio di utilizzo di questo metodo. 
     738 
     739Listato 15-19 - Il browser di test fornisce accesso al contenuto della risposta come oggetto DOM 
     740 
     741    $b = new sfTestBrowser(); 
     742    $b->get('/foobar/edit/id/1'); 
     743    $dom = $b->getResponseDom(); 
     744    $b->test()->is($dom->getElementsByTagName('input')->item(1)->getAttribute('type'),'text'); 
     745 
     746Ma fare il parsing di un documento HTML con i metodi DOM di PHP è ancora non abbastanza facile e veloce. Se hai familiarità con i CSS, sai che essi sono un metodo ancora più potente per recuperare elementi da un documento HTML. Symfony fornisce una classe chiamata `sfDomCssSelector` che si aspetta un documento DOM come parametro del costruttore. Possiede un metodo `getTexts()` che restituisce un array di stringhe secondo un selettore CSS, ed il metodo `getElements()` che restituisce un array di elementi DOM. Un esempio nel Listato 15-20. 
     747 
     748Listato 15-20 - Il browser di test fornisce accesso al contenuto della risposta come oggetto `sfDomCssSelector` 
     749 
     750    $b = new sfTestBrowser(); 
     751    $b->get('/foobar/edit/id/1'); 
     752    $c = new sfDomCssSelector($b->getResponseDom()) 
     753    $b->test()->is($c->getTexts('form input[type="hidden"][value="1"]'), array(''); 
     754    $b->test()->is($c->getTexts('form textarea[name="text1"]'), array('foo')); 
     755    $b->test()->is($c->getTexts('form input[type="submit"]'), array('')); 
     756 
     757Nella sua costante ricerca di brevità e chiarezza, symfony fornisce uno shortcut per questo: il metodo proxy `checkResponseElement()`. Esso fa diventare il Listato 15-20 equivalente al 15-21. 
     758 
     759Listato 15-21 - Il browser di test fornisce accesso agli elementi della risposta tramite selettori CSS 
     760 
     761    $b = new sfTestBrowser(); 
     762    $b->get('/foobar/edit/id/1')-> 
     763        checkResponseElement('form input[type="hidden"][value="1"]', true)-> 
     764        checkResponseElement('form textarea[name="text1"]', 'foo')-> 
     765        checkResponseElement('form input[type="submit"]', 1); 
     766 
     767Il comportamento del metodo `checkResponseElement()` dipende dal tipo del secondo argomento che riceve: 
     768  * Se è un booleano, controlla che l'elemento che corrisponde al selettore CSS esista. 
     769  * Se è un intero, controlla che il selettore CSS restituisca tale numero di risultati. 
     770  * Se è un'espressione regolare, controlla che corrisponda al primo elemento trovato dal selettore CSS. 
     771  * Se è un'espressione regolare preceduta da !, controllare che il primo elemento non corrisponda al pattern. 
     772  * Per gli altri casi, controlla che il primo elemento trovato dal selettore CSS corrisponda al secondo argomento come stringa. 
     773 
     774Il metodo accetta un terzo parametro opzionale, nella forma di array associativo. Ti permette di testare non il primo elemento restituito dal selettore (se più di uno) ma un altro ad una certa posizione, come mostrato dal Listato 15-22. 
     775 
     776Listato 15-22 - Utilizzare l'opzione di posizione 
     777 
     778    $b = new sfTestBrowser(); 
     779    $b->get('/foobar/edit?id=1')-> 
     780        checkResponseElement('form textarea', 'foo')-> 
     781        checkResponseElement('form textarea', 'bar', array('position' => 1)); 
     782 
     783L'array opzionale può anche essere usata per far girare due test allo stesso momento. Puoi testare che ci sia un elemento corrispondente ad un selettore e quanti ce ne sono, come mostrato nel Listato 15-23. 
     784 
     785Listato 15-23 - Utilizzare l'opzione `count` per contare le corrispondenze 
     786 
     787    $b = new sfTestBrowser(); 
     788    $b->get('/foobar/edit?id=1')-> 
     789        checkResponseElement('form input', true, array('count' => 3)); 
     790 
     791Questo strumento è molto potente. Accetta la maggior parte dei selettori CSS 3, e lo puoi usare per query complesse come quelle del Listato 15-24. 
     792 
     793Listato 15-24 - Esempio di selettori CSS complessi accettati da `checkResponseElement()` 
     794 
     795    $b->checkResponseElement('ul#list li a[href]', 'click me'); 
     796    $b->checkResponseElement('ul > li', 'click me'); 
     797    $b->checkResponseElement('ul + li', 'click me'); 
     798    $b->checkResponseElement('h1, h2', 'click me'); 
     799    $b->checkResponseElement('a[class$="foo"][href*="bar.html"]', 'my link'); 
     800    $b->checkResponseElement('p:last ul:nth-child(2) li:contains("Some text")'); 
     801 
     802### Testare gli errori 
     803 
     804A volte le tue azioni o i tuoi modelli sollevano delle eccezioni appositamente (ad esempio per mostrare una pagina 404). Anche se potresti usare un selettore CSS per verificare che uno specifico messaggio di errore sia nel codice HTML generato, è meglio usare il metodo `throwsException` per verificare che un'eccezione sia stata sollevata, come mostrato nel Listato 15-25. 
     805 
     806Listing 15-25 - Testare le eccezioni 
     807 
     808    [php] 
     809    $b = new sfTestBrowser(); 
     810    $b-> 
     811        get('/foobar/edit/id/1')-> 
     812        click('go', array('name' => 'dummy'))-> 
     813        isStatusCode(200)-> 
     814        isRequestParameter('module', 'foobar')-> 
     815        isRequestParameter('action', 'update')-> 
     816      
     817        throwsException()->                   // Checks that the last request threw an exception 
     818        throwsException('RuntimeException')-> // Checks the class of the exception 
     819        throwsException(null, '/error/');     // Checks that the content of the exception message matches the regular expression 
     820 
     821### Lavorare nell'ambiente di test  
     822 
     823L'oggetto `sfTestBrowser` usa uno speciale front controller, impostato sull'ambiente `test`. La configurazione di default per questo ambiente è mostrata nel Listato 15-26. 
     824 
     825Listato 15-26 - Configurazione di default dell'ambiente `test`, in `myapp/config/settings.yml` 
     826 
     827    test: 
     828      .settings: 
     829        error_reporting:        <?php echo (E_ALL | E_STRICT & ~E_NOTICE)."\n" ?> 
     830        cache:                  off 
     831        web_debug:              off 
     832        no_script_name:         off 
     833        etag:                   off 
     834 
     835La cache e la web debug toolbar sono disabilitate in questo ambiente. Comunque, l'esecuzione del codice lascia ancora tracce in un file di log, distinto dai file di log di `prod` e `dev`, in modo che lo puoi controllare indipendentemente (`myproject/log/myapp_test.log`). In questo ambiente, le eccezioni non fermano l'esecuzione degli script, così puoi far girare un intero insieme di test anche se uno fallisce. Puoi avere impostazioni specifiche di connessione al database, ad esempio, per usarne un altro riempito con dati di test. 
     836 
     837Prima di usare l'oggetto `sfTestBrowser`, lo devi inizializzare. Nel caso ne avessi bisogno, potresti specificare un hostname per l'applicazione ed un indirizzo IP per il client, nel caso la tua applicazione controlli questi due parametri. Il Listato 15-27 mostra come fare. 
     838 
     839Listato 15-27 - Impostare il test browser con hostname e IP 
     840 
     841    $b = new sfTestBrowser(); 
     842    $b->initialize('myapp.example.com', '123.456.789.123'); 
     843 
     844### Il task test:functional  
     845 
     846Il task `test:functional` può far girare uno o più test funzionali, dipendentemente dal numero di argomenti ricevuti. Le regole sembrano quelle del task `test:unit`, tranne per il fatto che il task `test:functional` si aspetta sempre il nome di un'applicazione come primo argomento, come mostrato dal Listato 15-28. 
     847 
     848Listato 15-28 - Sntassi del task `test:functional` 
     849 
     850    // Struttra della cartella `Test` 
     851    test/ 
     852      functional/ 
     853        frontend/ 
     854          myModuleActionsTest.php 
     855          myScenarioTest.php 
     856        backend/ 
     857          myOtherScenarioTest.php 
     858 
     859    ## Esegue tutti i test funzionali per un'applicazione, ricorsivamente 
     860    > php symfony test:functional frontend 
     861 
     862    ## Esegue un dato test funzionale 
     863    > php symfony test:functional frontend myScenario 
     864 
     865    ## Esegue diversi test funzionali, secondo un pattern 
     866    > php symfony test:functional frontend my* 
     867 
     868 
     869Pratiche di nomenclatura dei test 
     870--------------------------------- 
     871 
     872Questa sezione elenca qualche buona abitudine per tenere i tuoi test organizzati e facili da manutenere. I suggerimenti riguardano l'organizzazione dei file, i test unitari ed i test funzionali. 
     873 
     874Per la struttura dei file, dovresti chiamare i test unitari con il nome delle classi che essi devono testare, ed i test funzionali secondo il modulo o scenario di cui si occupano. Un esempio nel Listato 15-29. La tua cartella `test/` conterrà presto molti file, e se non segui queste linee guida potrebbe divenire difficoltoso trovare i file. 
     875 
     876Listato 15-29 - Esempio di nomi dei file 
     877 
     878    test/ 
     879      unit/ 
     880        myFunctionTest.php 
     881        mySecondFunctionTest.php 
     882        foo/ 
     883          barTest.php 
     884      functional/ 
     885        frontend/ 
     886          myModuleActionsTest.php 
     887          myScenarioTest.php 
     888        backend/ 
     889          myOtherScenarioTest.php 
     890 
     891Per i test unitari, una buona abitudine è quella di raggruppare i test per funzione o metodo, e cominciare ogni gruppo di test con una chiamata `diag()`. I messaggi di ogni test unitario dovrebbero contenere il nome della funzione o metodo testato, seguito da un verbo ed una proprietà, in modo che l'output sembri una frase che descriva una proprietà dell'oggetto. Il Listato 15-30 mostra un esempio. 
     892 
     893Listato 15-30 - Esempio di nomi dei file per i test unitari 
     894 
     895    // srttolower() 
     896    $t->diag('strtolower()'); 
     897    $t->isa_ok(strtolower('Foo'), 'string', 'strtolower() returns a string'); 
     898    $t->is(strtolower('FOO'), 'foo', 'strtolower() transforms the input to lowercase'); 
     899      
     900    # strtolower() 
     901    ok 1 - strtolower() returns a string 
     902    ok 2 - strtolower() transforms the input to lowercase 
     903 
     904I test funzionali dovrebbero essere raggruppati per pagina e cominciare con una richiesta. Il Listato 15-31 illustra questa pratica. 
     905 
     906Listato 15-31 - Esempio di nomi di test funzionali 
     907 
     908    $browser-> 
     909      get('/foobar/index')-> 
     910      isStatusCode(200)-> 
     911      isRequestParameter('module', 'foobar')-> 
     912      isRequestParameter('action', 'index')-> 
     913      checkResponseElement('body', '/foobar/') 
     914    ; 
     915      
     916    # get /comment/index 
     917    ok 1 - status code is 200 
     918    ok 2 - request parameter module is foobar 
     919    ok 3 - request parameter action is index 
     920    ok 4 - response selector body matches regex /foobar/ 
     921 
     922Se segui questa convenzione, l'ouput dei tuoi test sarò chiaro abbastanza da poter essere usato come documentazione di sviluppo; in certi casi chiaro abbastanza da rendere inutile ulteriore documentazione. 
     923 
     924 
     925Esigenze particolari nei test 
     926----------------------------- 
     927 
     928Gli strumenti forniti da symfony per i test funzionali e per i test unitari dovrebbero essere sufficienti nella maggior parte dei casi. Qualche funzionalità ulteriore viene qui elencata per risolvere problemi comuni nei test automatici: lanciare test in ambienti isolati, accedere al database senza test, testare la cache ed interazioni di test lato client. 
     929 
     930### Eseguire test in un test harness  
     931 
     932I task `test:unit` e `test:functional` possono lanciare un test singolo od un insieme di test. Ma se chiami questi task senza alcun parametro, essi lanciano tutte le unit e functional test nella cartella `test/`. Viene incluso un meccanismo particolare per isolare ogni test in una sandbox indipendente, al fine di evitare rischi di contaminazione fra test. Inoltre i risultati dei test vengono compattati in viste sintetiche; ecco perchè l'esecuzione di una grande quantità di file di test usa un test harness, ovvero un framework di test automatici con speciali funzionalità. Un test harness si basa su un componente del framework lime chiamato `lime_harness`. Mostra lo stato dei test file per file, ed alla fine una panoramica del numero dei test passati rispetto al totale, come mostrato nel Listato 15-32. 
     933 
     934Listato 15-32 - Lanciare i test in un test harness 
     935 
     936    php symfony test:unit 
     937    unit/myFunctionTest.php................ok 
     938    unit/mySecondFunctionTest.php..........ok 
     939    unit/foo/barTest.php...................not ok 
     940 
     941    Failed Test                     Stat  Total   Fail  List of Failed 
     942    ------------------------------------------------------------------ 
     943    unit/foo/barTest.php               0      2      2  62 63 
     944    Failed 1/3 test scripts, 66.66% okay. 2/53 subtests failed, 96.22% okay. 
     945 
     946I test vengono eseguiti nello stesso modo come quando li chiami ad uno ad uno, solo l'ouput viene accorciato per essere davvero utile. In particolare, l'ultima tabella è orientata ai test falliti e ti aiuta a capire quali. 
     947 
     948Puoi lanciare tutti i test con una singola chiamata tramite il task `test:all`, il quale a sua volta usa un test harness, come mostrato nel Listato 15-33. Questa è una cosa suggerita prima del trasferimento in produzione, per assicurarsi che non ci sia alcuna regressione dall'ultimo rilascio. 
     949 
     950Listato 15-33 - Lanciare tutti i test di un progetto 
     951 
     952    php symfony test:all 
     953 
     954### Accedere ad un database  
     955 
     956I test unitari hanno spesso bisogno di accedere ad un db. Una connessione viene inizializzata automaticamente alla prima chiamata di `sfTestBrowser::get()`. Ad ogni modo, se tu volessi accedervi anche prima tramite `sfTestBrowser`, dovresti inizializzare l'oggetto `sfDabataseManager` manualmente, come mostrato nel Listato 15-34. 
     957 
     958Listato 15-34 - Inizializzare un db in un test 
     959 
     960    $databaseManager = new sfDatabaseManager($configuration); 
     961    $databaseManager->loadConfiguration(); 
     962      
     963    // Opzionalmente, puoi recuperare la connessione a db corrente 
     964    $con = Propel::getConnection(); 
     965 
     966Prima di cominciare i test dovresti popolare il db con fixture. Ciò può essere fatto tramite l'oggetto `sfPropelData`. Tale oggetto può caricare dati da un file, proprio come il task `propel:data-load`, o da un'array, come mostrato nel Listato 15-35. 
     967 
     968Listato 15-35 - Popolare un db da un file 
     969 
     970    $data = new sfPropelData(); 
     971      
     972    // Caricare i dati da un file 
     973    $data->loadData(sfConfig::get('sf_data_dir').'/fixtures/test_data.yml'); 
     974      
     975    // Caricare i dati da un array 
     976    $fixtures = array( 
     977      'Article' => array( 
     978        'article_1' => array( 
     979          'title'      => 'foo title', 
     980          'body'       => 'bar body', 
     981          'created_at' => time(), 
     982        ), 
     983        'article_2'    => array( 
     984          'title'      => 'foo foo title', 
     985          'body'       => 'bar bar body', 
     986          'created_at' => time(), 
     987        ), 
     988      ), 
     989    ); 
     990    $data->loadDataFromArray($fixtures); 
     991 
     992Dopodiché, usa gli oggetti Propel come faresti in un'applicazione normale, secondo i tuoi bisogni di testing. Ricorda di includere i loro file nei test unitari (puoi usare la classe `sfSimpleAutoload`, come spiegato precedentemente nella sezione "Stub, Fixture e Autoloading"). Gli oggetti Propel vengono auto-caricati nei test funzionali. 
     993 
     994### Testare la cache  
     995 
     996Quando abiliti la cache per un'applicazione, i test funzionali dovrebbero verificare che le azioni in cache funzionino come ci si aspetta. 
     997 
     998La prima cosa da fare è abilitare la cache nell'ambiente `test` (nel file `settings.yml`). Quindi, qualora tu voglia testare quando una pagina arriva dalla cache o quando viene generata, devi usare il metodo `isCached()` fornito dall'oggetto `sfTestBrowser`. Il Listato 15-36 mostra questo metodo. 
     999 
     1000Listato 15-36 - Testare la cache con il metodo `isCached()` 
     1001 
     1002    <?php 
     1003      
     1004    include(dirname(__FILE__).'/../../bootstrap/functional.php'); 
     1005      
     1006    // Create a new test browser 
     1007    $b = new sfTestBrowser(); 
     1008    $b->initialize(); 
     1009      
     1010    $b->get('/mymodule'); 
     1011    $b->isCached(true);       // Controlla che la risposta venga dalla cache 
     1012    $b->isCached(true, true); // Controlla che la risposta in cache arrivi con layout 
     1013    $b->isCached(false);      // Controlla che la risposta non venga dalla cache 
     1014 
     1015>**NOTE** 
     1016>Non hai bisogno di pulire la cache all'inizio di un test funzionale; lo script di bootstrap lo fa per te. 
     1017 
     1018### Testare interazioni lato client  
     1019 
     1020Il più grande svantaggio delle tecniche mostrate finora è che esse non possono simulare JavaScript. Per interazioni molto complesse, ad esempio come quelle Ajax, hai bisogno di riprodurre esattamente il mouse e gli input da tastiera di un eventuale utente, nonchè l'esecuzione di script lato client. Di solito questi test vengono riprodotti a mano, ma sono una vera perdita di tempo e soggetti a errori. 
     1021 
     1022La soluzione si chiama Selenium ([http://www.openqa.org/selenium/]), che è un framework di test scritto interamente in JavaScript. Esegue una serie di azioni nella pagina proprio come farebbe un utente normale, utilizzando la finestra del browser corrente. Il vantaggio rispetto a `sfBrowser` è che Selenium è in grado di simulare JavaScript, per cui puoi anche testare interazioni Ajax. 
     1023 
     1024Selenium non è incluso in symfony per default. Per installarlo, devi creare una cartella `selenium/` all'interno della cartella `web/`, e scompattarci l'archivio di Selenium ([http://www.openqa.org/selenium-core/download.action]). Questo perchè Selenium si basa su JavaScript, e dato che le impostazioni di sicurezza di default di molti browser ne bloccano l'esecuzione a meno che non sia disponibile sullo stesso host e sulla stessa porta della tua applicazione. 
     1025 
     1026>**CAUTION** 
     1027>Fai attenzione a non copiare la cartella `selenium/` sul server in produzione, in quanto risulterebbe accessibile a chiunque dalla webroot. 
     1028 
     1029I test di Selenium sono scritti in HTML e memorizzati nella cartella `web/selenium/tests/`. Ad esempio, il Listato 15-37 mostra un test funzionale dove viene caricata la home page, viene cliccato il link "click me" e viene cercato nella risposta il testo "Hello, World". Ricorda che per poter accedere all'applicazione nell'ambiente `test`, devi specificare il front controller `myapp_test.php`. 
     1030 
     1031Listato 15-37 - Esempio di test Selenium, in `web/selenium/test/testIndex.html` 
     1032 
     1033    <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> 
     1034    <html> 
     1035    <head> 
     1036      <meta content="text/html; charset=UTF-8" http-equiv="content-type"> 
     1037      <title>Index tests</title> 
     1038    </head> 
     1039    <body> 
     1040    <table cellspacing="0"> 
     1041    <tbody> 
     1042      <tr><td colspan="3">First step</td></tr> 
     1043      <tr><td>open</td>              <td>/frontend_test.php/</td> <td>&nbsp;</td></tr> 
     1044      <tr><td>clickAndWait</td>      <td>link=click me</td>    <td>&nbsp;</td></tr> 
     1045      <tr><td>assertTextPresent</td> <td>Hello, World!</td>    <td>&nbsp;</td></tr> 
     1046    </tbody> 
     1047    </table> 
     1048    </body> 
     1049    </html> 
     1050 
     1051Un caso di test è rappresentato da un documento HTML che contiene una tabella con tre colonne: comando, target e valore. Non tutti i comandi hanno un valore, comunque. In questo caso, lascia la colonna vuota o usa `&nbsp;` perchè la tabella goda comunque di un buon look. Consulta il sito di Selenium per una lista completa dei comandi. 
     1052 
     1053Hai anche bisogno di aggiungere questo test all'insieme globale inserendo una nuova linea nella tabella del file `TestSuite.html`, situato nella stessa cartella. Il Listato 15-38 mostra come. 
     1054 
     1055Listato 15-38 - Aggiungere un file di test alla suite di test, in `web/selenium/test/TestSuite.html` 
     1056 
     1057  ... 
     1058  <tr><td><a href='./testIndex.html'>My First Test</a></td></tr> 
     1059  ... 
     1060 
     1061Per eseguire il test, naviga semplicemente su 
     1062 
     1063    http://myapp.example.com/selenium/index.html 
     1064 
     1065Seleziona Main Test Suite, clicca il pulsante per eseguire tutti i test, e guarda il browser riprodurre gli step che tu gli hai detto di eseguire. 
     1066 
     1067>**NOTE** 
     1068>Dato che i test Selenium girano su un browser reale, ti permettono anche di testarne le incoerenze. Costruisci i tuoi test con un browser, e poi testali con tutti quelli con cui il tuo sito dovrebbe funzionare. 
     1069 
     1070Il fatto che i test Selenium siano scritti in HTML potrebbe rendere una seccatura il loro procedimento di scrittura. Ma grazie all'estensione Firefox Selenium ([http://selenium-ide.openqa.org/]), tutto ciò di cui hai bisogno per creare un test è di eseguirlo una volta in una sessione registrata. Finché navighi nella stessa sessione, puoi aggiungere test come tipi di asserzioni semplicemente cliccando con il tasto destro nella finestra del browser e selezionando il check giusto nel menu Append Selenium Command. 
     1071 
     1072Puoi salvare il test inun file HTML per costruire una suite di test per la tua applicazione. L'estensione per Firefox ti permette anche di eseguire test Selenium registrati precedentemente. 
     1073 
     1074>**NOTE** 
     1075>Non dimenticare di reinizializzare i dati di test prima di lanciare i test Selenium. 
     1076 
     1077 
     1078Sommario 
     1079-------- 
     1080 
     1081I test automatici includono test unitari per convalidare metodi o funzioni e test funzionali per convalidare funzionalità. Symfony si basa sul framework di testing `lime` per i test unitari e fornisce una classe speciale `sfTestBrowser` per i test funzionali. Entrambi mettono a disposizione molti metodi di asserzione, da quelli base fino ai più avanzati, come i selettori CSS. Usa la linea di comandi per lanciare i test, uno ad uno (con i task `test:unit` e `test:functional`) o in un test harness (con il task `test:all`). Quando gestisci i dati, i test automatici usano fixture e stub, e questo è facilmente eseguibile con i test unitari di symfony. 
     1082 
     1083Se ti assicuri di scrivere abbastanza test unitari da coprire gran parte della tua applicazione (usando la metodologia TDD), ti sentirai al sicuro durante le operazioni di refactoring o di aggiunta di nuove funzionalità, e potresti anche guadagnare tempo sulla documentazione. 
     1084 
     1085}}}