Development

Documentation/it_IT/book/1.0/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.0/15-Unit-and-Functional-Testing

Show
Ignore:
Author:
Andrea.Giorgini (IP: 213.136.171.99)
Timestamp:
05/28/07 09:20:07 (10 years ago)
Comment:

--

Legend:

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

    v0 v1  
     1= Capitolo 15 - Unit test e test funzionali = 
     2 
     3I 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. 
     4 
     5 
     6== Test automatici == 
     7 
     8Ogni 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. 
     9 
     10Ecco 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. 
     11 
     12I 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. 
     13 
     14Il 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 nella distribuzione standard di symfony, ma li puoi scaricare tramite SVN o navigare all'indirizzo [http://www.symfony-project.com/trac/browser/trunk/test]. 
     15 
     16 
     17=== Unit e functional test === 
     18 
     19Le unit test 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. 
     20 
     21Le unit test 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 functional test convalidano un processo e richiedono uno scenario. In symfony, dovresti scrivere test funzionali per tutte le tue azioni. 
     22 
     23Per 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. 
     24 
     25Se 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. 
     26 
     27{{{ 
     28#!html 
     29<blockquote style="padding: 5px 20px 5px 40px; margin: 10px 0; background: #ffc url(http://www.symfony-project.com/images/note.gif) no-repeat 5px 10px; border: 1px solid #ddd;"> 
     30<p> 
     31I test automatici lavorano confrontando un risultato con un output previsto. In altre parole, essi valutano asserzioni (espressioni tipo <code>$a == 2</code>). Il valore di un'assezione può essere <code>true</code> o <code>false</code>, e determina quando il test abbia successo o fallisca. La parola "asserzione" è usata comunemente quando si parla di tecniche di test automatici. 
     32</p></blockquote> 
     33}}} 
     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. E' 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 underscore 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 
     42''Tabella 15-1 - Una lista di casi di test per una funzione di text-stripping.'' 
     43 
     44{{{ 
     45#!html 
     46<table> 
     47<thead> 
     48<tr> 
     49  <th>Input</th> 
     50  <th>Expected Output</th> 
     51</tr> 
     52 
     53</thead> 
     54<tbody> 
     55<tr> 
     56  <td><code>" foo "</code></td> 
     57  <td><code>"foo"</code></td> 
     58</tr> 
     59<tr> 
     60  <td><code>"foo bar"</code></td> 
     61  <td><code>"foo_bar"</code></td> 
     62 
     63</tr> 
     64<tr> 
     65  <td><code>"-)foo:..=bar?"</code></td> 
     66  <td><code>"__foo____bar_"</code></td> 
     67</tr> 
     68<tr> 
     69  <td><code>"FooBar"</code></td> 
     70  <td><code>"foobar</code>"</td> 
     71 
     72</tr> 
     73<tr> 
     74  <td><code>"Don't foo-bar me!"</code></td> 
     75  <td><code>"don_t_foo_bar_me_"</code></td> 
     76</tr> 
     77</tbody> 
     78</table> 
     79}}} 
     80 
     81Scriveresti le unit test, le 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. 
     82 
     83{{{ 
     84#!html 
     85<blockquote style="padding: 5px 20px 5px 40px; margin: 10px 0; background: #ffc url(http://www.symfony-project.com/images/note.gif) no-repeat 5px 10px; border: 1px solid #ddd;"> 
     86<p> 
     87Fare il refactoring di 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. 
     88</p></blockquote> 
     89}}} 
     90 
     91 
     92=== Il framework di testing Lime === 
     93 
     94Ci 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}}}. E' 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. 
     95 
     96Lime fornisce supporto per le unit testing. E' più leggero degli altri framework di testing di PHP ed ha diversi vantaggi: 
     97 * 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. 
     98 * I test di lime sono molto leggibili, e così anche il loro output. Sui sistemi compatibili, lime usa colori diversi per sottolineare dati importanti. 
     99 * Symfony stesso usa i test di lime per il regression testing, per cui puoi trovare diversi esempi di unit e functional test nel suo codice sorgente. 
     100 * Il core di lime è convalidato da unit test. 
     101 * E' scritto in PHP, ed è veloce e scritto bene. E' contenuto in un singolo file, {{{lime.php}}}, senza alcuna dipendenza. 
     102 
     103I test descritti in seguito si basano sulla sintassi di lime, e funzionano con qualsiasi installazione di symfony. 
     104 
     105{{{ 
     106#!html 
     107<blockquote style="padding: 5px 20px 5px 40px; margin: 10px 0; background: #ffc url(http://www.symfony-project.com/images/note.gif) no-repeat 5px 10px; border: 1px solid #ddd;"> 
     108<p> 
     109Le unit e functional test non sono pensate per essere lanciate in produzione. Si tratta di strumenti di sviluppo, e come tali dovrebbero essere usati sulla macchina dello sviluppatore e non sul server di produzione. 
     110</p></blockquote> 
     111}}} 
     112 
     113 
     114== Unit Test == 
     115 
     116Le unit test 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. 
     117 
     118 
     119=== Come sono fatti? === 
     120 
     121Il Listato 15-1 mostra un tipico insieme di unit test per la funzione {{{strtolower()}}}. Comincia con l'istanziazione dell'oggetto {{{lime_test}}} (non ti preoccupare dei parametri per ora). Ogni unit test è una chiamata ad un metodo di tale istanza. L'ultimo parametro di tali metodi è sempre una stringa opzionale che serve come output. 
     122 
     123''Listato 15-1 - Esempio di unit test, in {{{test/unit/strtolowerTest.php}}}'' 
     124{{{ 
     125#!php 
     126<?php 
     127  
     128include(dirname(__FILE__).'/../bootstrap/unit.php'); 
     129require_once(dirname(__FILE__).'/../../lib/strtolower.php'); 
     130  
     131$t = new lime_test(7, new lime_output_color()); 
     132  
     133// strtolower() 
     134$t->diag('strtolower()'); 
     135$t->isa_ok(strtolower('Foo'), 'string', 
     136    'strtolower() returns a string'); 
     137$t->is(strtolower('FOO'), 'foo', 
     138    'strtolower() transforms the input to lowercase'); 
     139$t->is(strtolower('foo'), 'foo', 
     140    'strtolower() leaves lowercase characters unchanged'); 
     141$t->is(strtolower('12#?@~'), '12#?@~', 
     142    'strtolower() leaves non alphabetical characters unchanged'); 
     143$t->is(strtolower('FOO BAR'), 'foo bar', 
     144    'strtolower() leaves blanks alone'); 
     145$t->is(strtolower('FoO bAr'), 'foo bar', 
     146    'strtolower() deals with mixed case input'); 
     147$t->is(strtolower(''), 'foo', 
     148    'strtolower() transforms empty strings into foo'); 
     149}}} 
     150 
     151Lancia 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. 
     152 
     153''Listato 15-2 - Lanciare un singolo unit test dalla linea di comando'' 
     154{{{ 
     155#!html 
     156<div style="height: 270px; background-color:#333; color:#eee;"><div style="float: left; font-family: Monospace; margin-top: 6px; margin-left: 10px;"> 
     157&gt; symfony test-unit strtolower<br /><br /> 
     1581..7<br /> 
     159# strtolower()<br /> 
     160ok 1 - strtolower() returns a string<br /> 
     161ok 2 - strtolower() transforms the input to lowercase<br /> 
     162ok 3 - strtolower() leaves lowercase characters unchanged<br /> 
     163ok 4 - strtolower() leaves non alphabetical characters unchanged<br /> 
     164ok 5 - strtolower() leaves blanks alone<br /> 
     165ok 6 - strtolower() deals with mixed case input<br /> 
     166not ok 7 - strtolower() transforms empty strings into foo<br /> 
     167#     Failed test (.\batch\test.php at line 21)<br /> 
     168#            got: ''<br /> 
     169#       expected: 'foo'<br /> 
     170# Looks like you failed 1 tests of 7.<br /> 
     171</div></div> 
     172}}} 
     173 
     174{{{ 
     175#!html 
     176<blockquote style="padding: 5px 20px 5px 40px; margin: 10px 0; background: #ffc url(http://www.symfony-project.com/images/tip.gif) no-repeat 5px 10px; border: 1px solid #ddd;"> 
     177<p> 
     178Lo statement <code>include</code> 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 <code>test/unit/strtolowerTest.php</code>. 
     179</p></blockquote> 
     180}}} 
     181 
     182 
     183=== Metodi delle Unit Test === 
     184 
     185L'oggetto {{{lime_test}}} è dotato di una gande varietà di metodi di test, come mostrato dalla Tabella 15-2. 
     186 
     187''Tabella 15-2 - Metodi dell'oggetto {{{lime_test}}} per le unit testing'' 
     188{{{ 
     189#!html 
     190<table> 
     191<thead> 
     192<tr> 
     193  <th>Method</th> 
     194  <th>Description</th> 
     195</tr> 
     196</thead> 
     197<tbody> 
     198<tr> 
     199  <td><code>diag($msg)</code></td> 
     200  <td>Outputs a comment but runs no test</td> 
     201 
     202</tr> 
     203<tr> 
     204  <td><code>ok($test, $msg)</code></td> 
     205  <td>Tests a condition and passes if it is true</td> 
     206</tr> 
     207<tr> 
     208  <td><code>is($value1, $value2, $msg)</code></td> 
     209  <td>Compares two values and passes if they are equal (<code>==</code>)</td> 
     210 
     211</tr> 
     212<tr> 
     213  <td><code>isnt($value1, $value2, $msg)</code></td> 
     214  <td>Compares two values and passes if they are not equal</td> 
     215</tr> 
     216<tr> 
     217  <td><code>like($string, $regexp, $msg)</code></td> 
     218  <td>Tests a string against a regular expression</td> 
     219</tr> 
     220 
     221<tr> 
     222  <td><code>unlike($string, $regexp, $msg)</code></td> 
     223  <td>Checks that a string doesn't match a regular expression</td> 
     224</tr> 
     225<tr> 
     226  <td><code>cmp_ok($value1, $operator, $value2, $msg)</code></td> 
     227  <td>Compares two arguments with an operator</td> 
     228</tr> 
     229<tr> 
     230 
     231  <td><code>isa_ok($variable, $type, $msg)</code></td> 
     232  <td>Checks the type of an argument</td> 
     233</tr> 
     234<tr> 
     235  <td><code>isa_ok($object, $class, $msg)</code></td> 
     236  <td>Checks the class of an object</td> 
     237</tr> 
     238<tr> 
     239  <td><code>can_ok($object, $method, $msg)</code></td> 
     240 
     241  <td>Checks the availability of a method for an object or a class</td> 
     242</tr> 
     243<tr> 
     244  <td><code>is_deeply($array1, $array2, $msg)</code></td> 
     245  <td>Checks that two arrays have the same values</td> 
     246</tr> 
     247<tr> 
     248  <td><code>include_ok($file, $msg)</code></td> 
     249  <td>Validates that a file exists and that it is properly included</td> 
     250 
     251</tr> 
     252<tr> 
     253  <td><code>fail()</code></td> 
     254  <td>Always fails--useful for testing exceptions</td> 
     255</tr> 
     256<tr> 
     257  <td><code>pass()</code></td> 
     258  <td>Always passes--useful for testing exceptions</td> 
     259</tr> 
     260 
     261<tr> 
     262  <td><code>skip($msg, $nb_tests)</code></td> 
     263  <td>Counts as <code>$nb_tests</code> tests--useful for conditional tests</td> 
     264</tr> 
     265<tr> 
     266  <td><code>todo()</code></td> 
     267  <td>Counts as a test--useful for tests yet to be written</td> 
     268 
     269</tr> 
     270</tbody> 
     271</table> 
     272}}} 
     273 
     274La 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. 
     275 
     276''Listato 15-3 - Metodi di test dell'oggetto {{{lime_test}}}, in {{{test/unit/exampleTest.php}}}'' 
     277{{{ 
     278#!php 
     279<?php 
     280  
     281include(dirname(__FILE__).'/../bootstrap/unit.php'); 
     282  
     283// Stub objects and functions for test purposes 
     284class myObject 
     285{ 
     286  public function myMethod() 
     287  { 
     288  } 
     289} 
     290  
     291function throw_an_exception() 
     292{ 
     293  throw new Exception('exception thrown'); 
     294} 
     295  
     296// Initialize the test object 
     297$t = new lime_test(16, new lime_output_color()); 
     298  
     299$t->diag('hello world'); 
     300$t->ok(1 == '1', 'the equal operator ignores type'); 
     301$t->is(1, '1', 'a string is converted to a number for comparison'); 
     302$t->isnt(0, 1, 'zero and one are not equal'); 
     303$t->like('test01', '/test\d+/', 'test01 follows the test numbering pattern'); 
     304$t->unlike('tests01', '/test\d+/', 'tests01 does not follow the pattern'); 
     305$t->cmp_ok(1, '<', 2, 'one is inferior to two'); 
     306$t->cmp_ok(1, '!==', true, 'one and true are not identical'); 
     307$t->isa_ok('foobar', 'string', '\'foobar\' is a string'); 
     308$t->isa_ok(new myObject(), 'myObject', 'new creates object of the right class'); 
     309$t->can_ok(new myObject(), 'myMethod', 'objects of class myObject do have amyMethod method'); 
     310$array1 = array(1, 2, array(1 => 'foo', 'a' => '4')); 
     311$t->is_deeply($array1, array(1, 2, array(1 => 'foo', 'a' => '4')), 
     312    'the first and the second array are the same'); 
     313$t->include_ok('./fooBar.php', 'the fooBar.php file was properly included'); 
     314  
     315try 
     316{ 
     317  throw_an_exception(); 
     318  $t->fail('no code should be executed after throwing an exception'); 
     319} 
     320catch (Exception $e) 
     321{ 
     322  $t->pass('exception catched successfully'); 
     323} 
     324  
     325if (!isset($foobar)) 
     326{ 
     327  $t->skip('skipping one test to keep the test count exact in the condition', 1); 
     328} 
     329else 
     330{ 
     331  $t->ok($foobar, 'foobar'); 
     332} 
     333  
     334$t->todo('one test left to do'); 
     335}}} 
     336 
     337Troverai molti altri esempi dell'utilizzo di questi metodi nelle unit test di symfony. 
     338 
     339{{{ 
     340#!html 
     341<blockquote style="padding: 5px 20px 5px 40px; margin: 10px 0; background: #ffc url(http://www.symfony-project.com/images/tip.gif) no-repeat 5px 10px; border: 1px solid #ddd;"> 
     342<p> 
     343Ti potresti chiedere perchè usare <code>is()</code> al posto di <code>ok()</code>. Il messaggio di errore prodotto da <code>is()</code> è molto più esplicito; mostra entrambi i membri del test, mentre <code>ok()</code> dice semplicemente che la condizione fallisce. 
     344</p></blockquote> 
     345}}} 
     346 
     347 
     348=== Parametri di test === 
     349 
     350L'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. 
     351 
     352''Listato 15-4 - La quantità dei test da eseguire ti aiuta nella pianificazione'' 
     353{{{ 
     354#!html 
     355<div style="height: 410px; background-color:#333; color:#eee;"><div style="float: left; font-family: Monospace; margin-top: 6px; margin-left: 10px;"> 
     356&gt; symfony test-unit example<br /><br /> 
     3571..16<br /> 
     358# hello world<br /> 
     359ok 1 - the equal operator ignores type<br /> 
     360ok 2 - a string is converted to a number for comparison<br /> 
     361ok 3 - zero and one are not equal<br /> 
     362ok 4 - test01 follows the test numbering pattern<br /> 
     363ok 5 - tests01 does not follow the pattern<br /> 
     364ok 6 - one is inferior to two<br /> 
     365ok 7 - one and true are not identical<br /> 
     366ok 8 - 'foobar' is a string<br /> 
     367ok 9 - new creates object of the right class<br /> 
     368ok 10 - objects of class myObject do have a myMethod method<br /> 
     369ok 11 - the first and the second array are the same<br /> 
     370not ok 12 - the fooBar.php file was properly included<br /> 
     371#     Failed test (.\test\unit\testTest.php at line 27)<br /> 
     372#       Tried to include './fooBar.php'<br /> 
     373ok 13 - exception catched successfully<br /> 
     374ok 14 # SKIP skipping one test to keep the test count exact in the condition<br /> 
     375ok 15 # TODO one test left to do<br /> 
     376# Looks like you planned 16 tests but only ran 15.<br /> 
     377# Looks like you failed 1 tests of 16.<br /> 
     378</div></div> 
     379}}} 
     380 
     381Il 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. 
     382 
     383Una 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. 
     384 
     385Il 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. 
     386 
     387 
     388=== Il task test-unit === 
     389 
     390Il task {{{test-unit}}}, che serve a lanciare le unit test dalla linea di comando, si aspetta una lista nomi di test oppure un file pattern. Vedi il Listato 15-5 per dettagli. 
     391 
     392''Listato 15-5 - Lanciare unit test'' 
     393{{{ 
     394#!php 
     395// Struttura della cartella Test 
     396test/ 
     397  unit/ 
     398    myFunctionTest.php 
     399    mySecondFunctionTest.php 
     400    foo/ 
     401      barTest.php 
     402 
     403> symfony test-unit myFunction                   ## Lancia myFunctionTest.php 
     404> symfony test-unit myFunction mySecondFunction  ## Lancia both tests 
     405> symfony test-unit 'foo/*'                      ## Lancia barTest.php 
     406> symfony test-unit '*'                          ## Lancia all tests (recursive) 
     407}}} 
     408 
     409 
     410=== Stub, ficture e autoloading === 
     411 
     412In una unit test, 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. 
     413 
     414''Listato 15-6 - Includere classi nei test'' 
     415{{{ 
     416#!php 
     417<?php 
     418  
     419include(dirname(__FILE__).'/../bootstrap/unit.php'); 
     420include(dirname(__FILE__).'/../../config/config.php'); 
     421require_once($sf_symfony_lib_dir.'/util/sfToolkit.class.php'); 
     422  
     423$t = new lime_test(7, new lime_output_color()); 
     424  
     425// isPathAbsolute() 
     426$t->diag('isPathAbsolute()'); 
     427$t->is(sfToolkit::isPathAbsolute('/test'), true, 
     428    'isPathAbsolute() returns true if path is absolute'); 
     429$t->is(sfToolkit::isPathAbsolute('\\test'), true, 
     430    'isPathAbsolute() returns true if path is absolute'); 
     431$t->is(sfToolkit::isPathAbsolute('C:\\test'), true, 
     432    'isPathAbsolute() returns true if path is absolute'); 
     433$t->is(sfToolkit::isPathAbsolute('d:/test'), true, 
     434    'isPathAbsolute() returns true if path is absolute'); 
     435$t->is(sfToolkit::isPathAbsolute('test'), false, 
     436    'isPathAbsolute() returns false if path is relative'); 
     437$t->is(sfToolkit::isPathAbsolute('../test'), false, 
     438    'isPathAbsolute() returns false if path is relative'); 
     439$t->is(sfToolkit::isPathAbsolute('..\\test'), false, 
     440    'isPathAbsolute() returns false if path is relative'); 
     441}}} 
     442 
     443Nelle unit test, 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. 
     444 
     445Ogni 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: 
     446 
     447{{{ 
     448#!php 
     449require_once($sf_symfony_lib_dir.'/util/sfCore.class.php'); 
     450sfCore::initSimpleAutoload($sf_symfony_lib_dir.'/util'); 
     451}}} 
     452 
     453{{{ 
     454#!html 
     455<blockquote style="padding: 5px 20px 5px 40px; margin: 10px 0; background: #ffc url(http://www.symfony-project.com/images/tip.gif) no-repeat 5px 10px; border: 1px solid #ddd;"> 
     456<p> 
     457Gli 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 <code>vendor/propel/</code> (per cui la chiamata a <code>sfCore</code> diventa <code>sfCore::initSimpleAutoload(array(SF_ROOT_ DIR.'/lib/model', $sf_symfony_lib_dir.'/vendor/propel'));</code>) ed aggiungere il core di Propel al percorso (chiamando <code>set_include_path($sf_symfony_lib_dir.'/vendor'.PATH_SEPARATOR.SF_ROOT_DIR.PATH_SEPARATOR.get_include_path())</code>). 
     458</p></blockquote> 
     459}}} 
     460 
     461Un 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, le unit test 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. 
     462 
     463''Listato 15-7 - Utilizzo di stub nelle unit test'' 
     464{{{ 
     465#!php 
     466require_once(dirname(__FILE__).'/../../lib/WebService.class.php'); 
     467require_once(dirname(__FILE__).'/../../lib/MapAPI.class.php'); 
     468  
     469class testWebService extends WebService 
     470{ 
     471  public static function fetch() 
     472  { 
     473    return file_get_contents(dirname(__FILE__).'/fixtures/data/fake_web_service.xml'); 
     474  } 
     475} 
     476  
     477$myMap = new MapAPI(); 
     478  
     479$t = new lime_test(1, new lime_output_color()); 
     480  
     481$t->is($myMap->getMapSize(testWebService::fetch(), 100)); 
     482}}} 
     483 
     484I 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. 
     485 
     486''Listato 15-8 - Utilizzo di fixture in unit test'' 
     487{{{ 
     488#!php 
     489// In fixtures.yml: 
     490- 
     491  input:   '/test' 
     492  output:  true 
     493  comment: isPathAbsolute() returns true if path is absolute 
     494- 
     495  input:   '\\test' 
     496  output:  true 
     497  comment: isPathAbsolute() returns true if path is absolute 
     498- 
     499  input:   'C:\\test' 
     500  output:  true 
     501  comment: isPathAbsolute() returns true if path is absolute 
     502- 
     503  input:   'd:/test' 
     504  output:  true 
     505  comment: isPathAbsolute() returns true if path is absolute 
     506- 
     507  input:   'test' 
     508  output:  false 
     509  comment: isPathAbsolute() returns false if path is relative 
     510- 
     511  input:   '../test' 
     512  output:  false 
     513  comment: isPathAbsolute() returns false if path is relative 
     514- 
     515  input:   '..\\test' 
     516  output:  false 
     517  comment: isPathAbsolute() returns false if path is relative 
     518  
     519// In testTest.php 
     520<?php 
     521  
     522include(dirname(__FILE__).'/../bootstrap/unit.php'); 
     523include(dirname(__FILE__).'/../../config/config.php'); 
     524require_once($sf_symfony_lib_dir.'/util/sfToolkit.class.php'); 
     525require_once($sf_symfony_lib_dir.'/util/sfYaml.class.php'); 
     526  
     527$testCases = sfYaml::load(dirname(__FILE__).'/fixtures.yml'); 
     528  
     529$t = new lime_test(count($testCases), new lime_output_color()); 
     530  
     531// isPathAbsolute() 
     532$t->diag('isPathAbsolute()'); 
     533foreach ($testCases as $case) 
     534{ 
     535  $t->is(sfToolkit::isPathAbsolute($case['input']), $case['output'],$case['comment']); 
     536} 
     537}}} 
     538 
     539 
     540== Test funzionali == 
     541 
     542I 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. 
     543 
     544 
     545=== Come sono fatti i test funzionali? === 
     546 
     547Potresti 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. 
     548 
     549Un 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. 
     550 
     551Ad esempio, ogni volta che generi lo scheletro di un modulo tramite i task {{{init-module}}} o {{{propel-init-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. 
     552 
     553''Listato 15-9 - Test funzionale di default per un nuovo modulo, in {{{tests/functional/frontend/foobarActionsTest.php}}}'' 
     554{{{ 
     555#!php 
     556<?php 
     557  
     558include(dirname(__FILE__).'/../../bootstrap/functional.php'); 
     559  
     560// Create a new test browser 
     561$browser = new sfTestBrowser(); 
     562$browser->initialize(); 
     563  
     564$browser-> 
     565  get('/foobar/index')-> 
     566  isStatusCode(200)-> 
     567  isRequestParameter('module', 'foobar')-> 
     568  isRequestParameter('action', 'index')-> 
     569  checkResponseElement('body', '!/This is a temporary page/') 
     570; 
     571}}} 
     572 
     573{{{ 
     574#!html 
     575<blockquote style="padding: 5px 20px 5px 40px; margin: 10px 0; background: #ffc url(http://www.symfony-project.com/images/tip.gif) no-repeat 5px 10px; border: 1px solid #ddd;"> 
     576<p> 
     577I metodi del browser restituiscono un oggetto <code>sfTestBrowser</code>, 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. 
     578</p></blockquote> 
     579}}} 
     580 
     581Un test funzionale può contenere diverse richieste e asserzioni più complesse; ne scoprirai tutte le possibilità nelle sezioni a venire. 
     582 
     583Per 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}}}). 
     584 
     585''Listato 15-10 - Lanciare un test funzionale singolo dalla linea di comando'' 
     586{{{ 
     587#!html 
     588<div style="height: 170px; background-color:#333; color:#eee;"><div style="float: left; font-family: Monospace; margin-top: 6px; margin-left: 10px;"> 
     589&gt; symfony test-functional frontend foobarActions<br /><br /> 
     590# get /comment/index<br /> 
     591ok 1 - status code is 200<br /> 
     592ok 2 - request parameter module is foobar<br /> 
     593ok 3 - request parameter action is index<br /> 
     594not ok 4 - response selector body does not match regex /This is a temporary page/<br /> 
     595# Looks like you failed 1 tests of 4.<br /> 
     5961..4<br /> 
     597</div></div> 
     598}}} 
     599 
     600I 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. 
     601 
     602{{{ 
     603#!html 
     604<blockquote style="padding: 5px 20px 5px 40px; margin: 10px 0; background: #ffc url(http://www.symfony-project.com/images/note.gif) no-repeat 5px 10px; border: 1px solid #ddd;"> 
     605<p> 
     606Nei test funzionali l'autoloading è attivo, per cui non devi includere i file a mano. 
     607</p></blockquote> 
     608}}} 
     609 
     610 
     611=== Browsing con l'oggetto sfTestBrowser === 
     612 
     613Il 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. 
     614 
     615''Listato 15-11 - Simulare richieste tramite l'oggetto {{{sfTestBrowser}}}'' 
     616{{{ 
     617#!php 
     618include(dirname(__FILE__).'/../../bootstrap/functional.php'); 
     619  
     620// Crea un nuovo browser di test 
     621$b = new sfTestBrowser(); 
     622$b->initialize(); 
     623  
     624$b->get('/foobar/show/id/1');                   // Richiesta in GET 
     625$b->post('/foobar/show', array('id' => 1));     // Richiesta in POST 
     626  
     627// I metodi get() e post() sono shortcut al metodo call() 
     628$b->call('/foobar/show/id/1', 'get'); 
     629$b->call('/foobar/show', 'post', array('id' => 1)); 
     630  
     631// Il metodo call() può simulare richieste con qualsiasi metodo 
     632$b->call('/foobar/show/id/1', 'head'); 
     633$b->call('/foobar/add/id/1', 'put'); 
     634$b->call('/foobar/delete/id/1', 'delete'); 
     635}}} 
     636 
     637Una 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. 
     638 
     639''Listato 15-12 - Simulare la navigazione tramite l'oggetto {{{sfTestBrowser}}}'' 
     640{{{ 
     641#!php 
     642$b->get('/');                  // Richiesta per la home page 
     643$b->get('/foobar/show/id/1'); 
     644$b->back();                    // Indietro di una pagina nella history 
     645$b->forward();                 // Avanti di una pagina nella history 
     646$b->reload();                  // Ricarica la pagina corrente 
     647$b->click('go');               // Cerca il pulsante o link 'go' e lo clicca 
     648}}} 
     649 
     650Il browser di test gestisce anche stack di chiamate, per cui i metodo {{{back()}}} e {{{forward()}}} funzionano esattamente come nei browser reali. 
     651 
     652{{{ 
     653#!html 
     654<blockquote style="padding: 5px 20px 5px 40px; margin: 10px 0; background: #ffc url(http://www.symfony-project.com/images/tip.gif) no-repeat 5px 10px; border: 1px solid #ddd;"> 
     655<p> 
     656Il browser di test possiede il proprio meccanismo per gestire le sessioni (<code>sfTestStorage</code>) ed i cookie. 
     657</p></blockquote> 
     658}}} 
     659 
     660Fra 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. 
     661 
     662''Listato 15-13 - Simulare l'invio di form tramite l'oggetto {{{sfTestBrowser}}}'' 
     663{{{ 
     664#!php 
     665// Template di esempio in modules/foobar/templates/editSuccess.php 
     666<?php echo form_tag('foobar/update') ?> 
     667  <?php echo input_hidden_tag('id', $sf_params->get('id')) ?> 
     668  <?php echo input_tag('name', 'foo') ?> 
     669  <?php echo submit_tag('go') ?> 
     670  <?php echo textarea('text1', 'foo') ?> 
     671  <?php echo textarea('text2', 'bar') ?> 
     672</form> 
     673  
     674// Esempio di test funzionale per questa form 
     675$b = new sfTestBrowser(); 
     676$b->initialize(); 
     677$b->get('/foobar/edit/id/1'); 
     678  
     679// Opzione 1: richiesta in POST 
     680$b->post('/foobar/update', array('id' => 1, 'name' => 'dummy', 'commit' => 'go')); 
     681  
     682// Opzione 2: Clicca il pulsante submit con parametri 
     683$b->click('go', array('name' => 'dummy')); 
     684  
     685// Opzione 3: riempi i campi a uno a uno e poi clicca su submit 
     686$b->setField('name', 'dummy')-> 
     687    click('go'); 
     688}}} 
     689 
     690{{{ 
     691#!html 
     692<blockquote style="padding: 5px 20px 5px 40px; margin: 10px 0; background: #ffc url(http://www.symfony-project.com/images/note.gif) no-repeat 5px 10px; border: 1px solid #ddd;"> 
     693<p> 
     694Con 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. 
     695</p></blockquote> 
     696}}} 
     697 
     698Quando 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. 
     699 
     700''Listato 15-14 - Il browser di test non segue automaticamente il forward'' 
     701{{{ 
     702#!php 
     703// Esempio di azione in modules/foobar/actions/actions.class.php 
     704public function executeUpdate() 
     705{ 
     706  ... 
     707  $this->redirect('foobar/show?id='.$this->getRequestParameter('id')); 
     708} 
     709  
     710// Esempio di test funzionale per questa azione 
     711$b = new sfTestBrowser();    
     712$b->initialize(); 
     713$b->get('/foobar/edit?id=1')-> 
     714    click('go', array('name' => 'dummy'))-> 
     715    isRedirected()->   // Controlla se esiste un forward 
     716    followRedirect();    // Segue il forward 
     717}}} 
     718 
     719C'è 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. 
     720 
     721Dopo 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: 
     722 
     723{{{ 
     724#!php 
     725$request  = $b->getRequest(); 
     726$context  = $b->getContext(); 
     727$response = $b->getResponse(); 
     728}}} 
     729 
     730{{{ 
     731#!html 
     732<blockquote class="sidebar" style="width: 800px; background-color: #cccccc;"> 
     733<p style="height: 30px; background-color: #aaaaaa; color: white; padding-top: 10px; padding-left: 10px;"><strong>L'oggetto sfBrowser</strong> 
     734<p style="margin-left: 10px;">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 <code>sfBrowser</code>. Puoi chiamarlo così:</p> 
     735<code> 
     736}}} 
     737{{{ 
     738#!php 
     739// Crea un nuovo browser 
     740$b = new sfBrowser(); 
     741$b->initialize(); 
     742$b->get('/foobar/show/id/1')-> 
     743    setField('name', 'dummy')-> 
     744    click('go'); 
     745$content = $b()->getResponse()->getContent(); 
     746}}} 
     747{{{ 
     748#!html 
     749</code> 
     750   
     751<p style="margin-left: 10px;">L'oggetto <code>sfBrowser</code> è 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). 
     752</p><br /> 
     753</blockquote> 
     754}}} 
     755 
     756 
     757=== Utilizzare asserzioni === 
     758 
     759Dato 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}}}. 
     760 
     761''Listato 15-15 - Il browser di test fornisce funzionalità di testing tramite il metodo {{{test()}}}'' 
     762{{{ 
     763#!php 
     764$b = new sfTestBrowser(); 
     765$b->initialize(); 
     766$b->get('/foobar/edit/id/1'); 
     767$request  = $b->getRequest(); 
     768$context  = $b->getContext(); 
     769$response = $b->getResponse(); 
     770  
     771// Avere accesso ai metodi lime_test tramite test() 
     772$b->test()->is($request->getParameter('id'), 1); 
     773$b->test()->is($response->getStatuscode(), 200); 
     774$b->test()->is($response->getHttpHeader('content-type'), 'text/html;charset=utf-8'); 
     775$b->test()->like($response->getContent(), '/edit/'); 
     776}}} 
     777 
     778{{{ 
     779#!html 
     780<blockquote style="padding: 5px 20px 5px 40px; margin: 10px 0; background: #ffc url(http://www.symfony-project.com/images/note.gif) no-repeat 5px 10px; border: 1px solid #ddd;"> 
     781<p> 
     782I metodi <code>getResponse(), getContext(), getRequest()</code>, e <code>test()</code> non restituiscono un oggetto <code>sfTestBrowser</code>, perciò dopo di essi non puoi concatenare altre chiamate al metodo <code>sfTestBrowser</code>. 
     783</p></blockquote> 
     784}}} 
     785 
     786Puoi facilmente controllare cookie entranti ed uscenti tramite gli oggetti richiesta e risposta, come mostrato dal Listato 15-16. 
     787 
     788''Listato 15-16 - Testare i cookie tramite {{{sfTestBrowser}}}'' 
     789{{{ 
     790#!php 
     791$b->test()->is($request->getCookie('foo'), 'bar');     // Incoming cookie 
     792$cookies = $response->getCookies(); 
     793$b->test()->is($cookies['foo'], 'foo=bar');            // Outgoing cookie 
     794}}} 
     795 
     796Utilizzare 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. 
     797 
     798''Listato 15-17 - Testare direttamente tramite {{{sfTestBrowser}}}'' 
     799{{{ 
     800#!php 
     801$b = new sfTestBrowser(); 
     802$b->initialize(); 
     803$b->get('/foobar/edit/id/1')-> 
     804    isRequestParameter('id', 1)-> 
     805    isStatutsCode()-> 
     806    isResponseHeader('content-type', 'text/html; charset=utf-8')-> 
     807    responseContains('edit'); 
     808}}} 
     809 
     810Lo 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. 
     811 
     812Un 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. 
     813 
     814{{{ 
     815#!php 
     816# get /foobar/edit/id/1 
     817ok 1 - request parameter "id" is "1" 
     818ok 2 - status code is "200" 
     819ok 3 - response header "content-type" is "text/html" 
     820ok 4 - response contains "edit" 
     8211..4 
     822}}} 
     823 
     824In 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}}}. 
     825 
     826Il 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. 
     827 
     828''Listato 15-18 - Testare le redirezioni con {{{sfTestBrowser}}}'' 
     829{{{ 
     830#!php 
     831$b = new sfTestBrowser(); 
     832$b->initialize(); 
     833$b-> 
     834    get('/foobar/edit/id/1')-> 
     835    click('go', array('name' => 'dummy'))-> 
     836    isStatusCode(200)-> 
     837    isRequestParameter('module', 'foobar')-> 
     838    isRequestParameter('action', 'update')-> 
     839  
     840    isRedirected()->      // Controlla che la risposta è un redirect 
     841    followRedirect()->    // Segue il redirect 
     842  
     843    isStatusCode(200)-> 
     844    isRequestParameter('module', 'foobar')-> 
     845    isRequestParameter('action', 'show'); 
     846}}} 
     847 
     848 
     849=== Utilizzare selettori CSS === 
     850 
     851Diversi 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. 
     852 
     853Ecco 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. 
     854 
     855''Listato 15-19 - Il browser di test fornisce accesso al contenuto della risposta come oggetto DOM'' 
     856{{{ 
     857#!php 
     858$b = new sfTestBrowser(); 
     859$b->initialize(); 
     860$b->get('/foobar/edit/id/1'); 
     861$dom = $b->getResponseDom(); 
     862$b->test()->is($dom->getElementsByTagName('input')->item(1)->getAttribute('type'),'text'); 
     863}}} 
     864 
     865Ma 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. 
     866 
     867''Listato 15-20 - Il browser di test fornisce accesso al contenuto della risposta come oggetto {{{sfDomCssSelector}}}'' 
     868{{{ 
     869#!php 
     870$b = new sfTestBrowser(); 
     871$b->initialize(); 
     872$b->get('/foobar/edit/id/1'); 
     873$c = new sfDomCssSelector($b->getResponseDom()) 
     874$b->test()->is($c->getTexts('form input[type="hidden"][value="1"]'), array(''); 
     875$b->test()->is($c->getTexts('form textarea[name="text1"]'), array('foo')); 
     876$b->test()->is($c->getTexts('form input[type="submit"]'), array('')); 
     877}}} 
     878 
     879Nella 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. 
     880 
     881''Listato 15-21 - Il browser di test fornisce accesso agli elementi della risposta tramite selettori CSS'' 
     882{{{ 
     883#!php 
     884$b = new sfTestBrowser(); 
     885$b->initialize(); 
     886$b->get('/foobar/edit/id/1')-> 
     887    checkResponseElement('form input[type="hidden"][value="1"]', true)-> 
     888    checkResponseElement('form textarea[name="text1"]', 'foo')-> 
     889    checkResponseElement('form input[type="submit"]', 1); 
     890}}} 
     891 
     892Il comportamento del metodo {{{checkResponseElement()}}} dipende dal tipo del secondo argomento che riceve: 
     893 * Se è un booleano, controlla che l'elemento che corrisponde al selettore CSS esista. 
     894 * Se è un intero, controlla che il selettore CSS restituisca tale numero di risultati. 
     895 * Se è un'espressione regolare, controlla che corrisponda al primo elemento trovato dal selettore CSS. 
     896 * Se è un'espressione regolare preceduta da !, controllare che il primo elemento non corrisponda al pattern. 
     897 * Per gli altri casi, controlla che il primo elemento trovato dal selettore CSS corrisponda al secondo argomento come stringa. 
     898 
     899Il 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. 
     900 
     901''Listato 15-22 - Utilizzare l'opzione di posizione'' 
     902{{{ 
     903#!php 
     904$b = new sfTestBrowser(); 
     905$b->initialize(); 
     906$b->get('/foobar/edit?id=1')-> 
     907    checkResponseElement('form textarea', 'foo')-> 
     908    checkResponseElement('form textarea', 'bar', array('position' => 1)); 
     909}}} 
     910 
     911L'array opzionale può anche essere usato 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. 
     912 
     913''Listato 15-23 - Utilizzare l'opzione {{{count}}} per contare le corrispondenze'' 
     914{{{ 
     915#!php 
     916$b = new sfTestBrowser(); 
     917$b->initialize(); 
     918$b->get('/foobar/edit?id=1')-> 
     919    checkResponseElement('form input', true, array('count' => 3)); 
     920}}} 
     921 
     922Questo strumento è molto potente. Accetta la maggior parte dei selettori CSS 2.1, e lo puoi usare per query complesse come quelle del Listato 15-24. 
     923 
     924''Listato 15-24 - Esempio di selettori CSS complessi accettati da {{{checkResponseElement()}}}'' 
     925{{{ 
     926#!php 
     927$b->checkResponseElement('ul#list li a[href]', 'click me'); 
     928$b->checkResponseElement('ul > li', 'click me'); 
     929$b->checkResponseElement('ul + li', 'click me'); 
     930$b->checkResponseElement('h1, h2', 'click me'); 
     931$b->checkResponseElement('a[class$="foo"][href*="bar.html"]', 'my link'); 
     932}}} 
     933 
     934 
     935=== Lavorare nell'ambiente di test === 
     936 
     937L'oggetto {{{sfTestBrowser}}} usa uno speciale front controller, impostato sull'ambiente {{{test}}}. La configurazione di default per questo ambiente è mostrata nel Listato 15-25. 
     938 
     939''Listato 15-25 - Configurazione di default dell'ambiente {{{test}}}, in {{{myapp/config/settings.yml}}}'' 
     940{{{ 
     941#!php 
     942test: 
     943  .settings: 
     944    # E_ALL | E_STRICT & ~E_NOTICE = 2047 
     945    error_reporting:        2047 
     946    cache:                  off 
     947    web_debug:              off 
     948    no_script_name:         off 
     949    etag:                   off 
     950}}} 
     951 
     952La 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. 
     953 
     954Prima 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-26 mostra come fare. 
     955 
     956''Listato 15-26 - Impostare il test browser con hostname e IP'' 
     957{{{ 
     958#!php 
     959$b = new sfTestBrowser(); 
     960$b->initialize('myapp.example.com', '123.456.789.123'); 
     961}}} 
     962 
     963 
     964=== Il task test-functional === 
     965 
     966Il 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-27. 
     967 
     968''Listato 15-27 - Sntassi del task {{{test-functional}}}'' 
     969{{{ 
     970#!php 
     971// Struttra della cartella {{{Test}}} 
     972test/ 
     973  functional/ 
     974    frontend/ 
     975      myModuleActionsTest.php 
     976      myScenarioTest.php 
     977    backend/ 
     978      myOtherScenarioTest.php 
     979 
     980## Esegue tutti i test funzionali per un'applicazione, ricorsivamente 
     981> symfony test-functional frontend 
     982 
     983## Esegue un dato test funzionale 
     984> symfony test-functional frontend myScenario 
     985 
     986## Esegue diversi test funzionali, secondo un pattern 
     987> symfony test-functional frontend my* 
     988}}} 
     989 
     990 
     991== Pratiche di naming dei test == 
     992 
     993Questa sezione elenca qualche buona abitudine per tenere i tuoi test organizzati e facili da manutenere. I suggerimenti riguardano l'organizzazione dei file, le unit test ed i test funzionali. 
     994 
     995Per la struttura dei file, dovresti chiamare le unit test con il nome delle classi che esse devono testare, ed i test funzionali secondo il modulo o scenario di cui si occupano. Un esempio nel Listato 15-28. La tua cartella {{{test/}}} conterrà presto molti file, e se non segui queste linee guida potrebbe divenire difficoltoso trovare i file. 
     996 
     997''Listato 15-28 - Esempio di nomi dei file'' 
     998{{{ 
     999#!php 
     1000test/ 
     1001  unit/ 
     1002    myFunctionTest.php 
     1003    mySecondFunctionTest.php 
     1004    foo/ 
     1005      barTest.php 
     1006  functional/ 
     1007    frontend/ 
     1008      myModuleActionsTest.php 
     1009      myScenarioTest.php 
     1010    backend/ 
     1011      myOtherScenarioTest.php 
     1012}}} 
     1013 
     1014Per le unit test, 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 unit test 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-29 mostra un esempio. 
     1015 
     1016''Listato 15-29 - Esempio di nomi dei file per le test unit'' 
     1017{{{ 
     1018#!php 
     1019// srttolower() 
     1020$t->diag('strtolower()'); 
     1021$t->isa_ok(strtolower('Foo'), 'string', 'strtolower() returns a string'); 
     1022$t->is(strtolower('FOO'), 'foo', 'strtolower() transforms the input to lowercase'); 
     1023  
     1024# strtolower() 
     1025ok 1 - strtolower() returns a string 
     1026ok 2 - strtolower() transforms the input to lowercase 
     1027}}} 
     1028 
     1029I test funzionali dovrebbero essere raggruppati per pagina e cominciare con una richiesta. Il Listato 15-30 illustra questa pratica. 
     1030 
     1031''Listato 15-30 - Esempio di nomi di test funzionali'' 
     1032{{{ 
     1033#!php 
     1034$browser-> 
     1035  get('/foobar/index')-> 
     1036  isStatusCode(200)-> 
     1037  isRequestParameter('module', 'foobar')-> 
     1038  isRequestParameter('action', 'index')-> 
     1039  checkResponseElement('body', '/foobar/') 
     1040; 
     1041  
     1042# get /comment/index 
     1043ok 1 - status code is 200 
     1044ok 2 - request parameter module is foobar 
     1045ok 3 - request parameter action is index 
     1046ok 4 - response selector body matches regex /foobar/ 
     1047}}} 
     1048 
     1049Se 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. 
     1050 
     1051 
     1052== Speciali bisogni nei test == 
     1053 
     1054Gli strumenti forniti da symfony per i test funzionali e per le unit test 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 db senza test, testare la cache ed interazioni di test lato client. 
     1055 
     1056 
     1057=== Eseguire test in un test harness === 
     1058 
     1059I 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-31. 
     1060 
     1061''Listato 15-31 - Lanciare i test in un test harness'' 
     1062{{{ 
     1063#!html 
     1064<div style="height: 200px; background-color:#333; color:#eee;"><div style="float: left; font-family: Monospace; margin-top: 6px; margin-left: 10px;"> 
     1065&gt; symfony test-unit<br /><br /> 
     1066unit/myFunctionTest.php................ok<br /> 
     1067unit/mySecondFunctionTest.php..........ok<br /> 
     1068unit/foo/barTest.php...................not ok<br /> 
     1069<br /> 
     1070Failed Test                     Stat  Total   Fail  List of Failed<br /> 
     1071------------------------------------------------------------------<br /> 
     1072unit/foo/barTest.php               0      2      2  62 63<br /> 
     1073Failed 1/3 test scripts, 66.66% okay. 2/53 subtests failed, 96.22% okay.<br /> 
     1074</div></div> 
     1075}}} 
     1076 
     1077I 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. 
     1078 
     1079Puoi 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-32. Questa è una cosa suggerita prima del trasferimento in produzione, per assicurarsi che non ci sia alcuna regressione dall'ultimo rilascio. 
     1080 
     1081''Listato 15-32 - Lanciare tutti i test di un progetto'' 
     1082{{{ 
     1083#!html 
     1084<div style="height: 30px; background-color:#333; color:#eee;"><div style="float: left; font-family: Monospace; margin-top: 6px; margin-left: 10px;"> 
     1085&gt; symfony test-all 
     1086</div></div> 
     1087}}} 
     1088 
     1089 
     1090=== Accedere ad un database === 
     1091 
     1092Le unit test 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-33. 
     1093 
     1094''Listato 15-33 - Inizializzare un db in un test'' 
     1095{{{ 
     1096#!php 
     1097$databaseManager = new sfDatabaseManager(); 
     1098$databaseManager->initialize(); 
     1099  
     1100// Opzionalmente, puoi recuperare la connessione a db corrente 
     1101$con = Propel::getConnection(); 
     1102}}} 
     1103 
     1104Prima 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-load-data}}}, o da un array, come mostrato nel Listato 15-34. 
     1105 
     1106''Listato 15-34 - Popolare un db da un file'' 
     1107{{{ 
     1108#!php 
     1109$data = new sfPropelData(); 
     1110  
     1111// Caricare i dati da un file 
     1112$data->loadData(sfConfig::get('sf_data_dir').'/fixtures/test_data.yml'); 
     1113  
     1114// Caricare i dati da un array 
     1115$fixtures = array( 
     1116  'Article' => array( 
     1117    'article_1' => array( 
     1118      'title'      => 'foo title', 
     1119      'body'       => 'bar body', 
     1120      'created_at' => time(), 
     1121    ), 
     1122    'article_2'    => array( 
     1123      'title'      => 'foo foo title', 
     1124      'body'       => 'bar bar body', 
     1125      'created_at' => time(), 
     1126    ), 
     1127  ), 
     1128); 
     1129$data->loadDataFromArray($fixtures); 
     1130}}} 
     1131 
     1132Dopodichè, usa gli oggetti Propel come faresti in un'applicazione normale, secondo i tuoi bisogni di testing. Ricorda di includere i loro file nelle unit test (puoi usare il metodo {{{sfCore::sfSimpleAutoloading()}}}, come spiegato precedentemente nella sezione "Stub, Fixture e Autoloading"). Gli oggetti Propel vengono auto-caricati nei test funzionali. 
     1133 
     1134 
     1135=== Testare la cache === 
     1136 
     1137Quando abiliti la cache per un'applicazione, i test funzionali dovrebbero verificare che le azioni in cache funzionino come ci si aspetta. 
     1138 
     1139La 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-35 mostra questo metodo. 
     1140 
     1141''Listato 15-35 - Testare la cache con il metodo {{{isCached()}}}'' 
     1142{{{ 
     1143#!php 
     1144<?php 
     1145  
     1146include(dirname(__FILE__).'/../../bootstrap/functional.php'); 
     1147  
     1148// Create a new test browser 
     1149$b = new sfTestBrowser(); 
     1150$b->initialize(); 
     1151  
     1152$b->get('/mymodule'); 
     1153$b->isCached(true);       // Controlla che la risposta venga dalla cache 
     1154$b->isCached(true, true); // Controlla che la risposta in cache arrivi con layout 
     1155$b->isCached(false);      // Controlla che la risposta non venga dalla cache 
     1156}}} 
     1157 
     1158{{{ 
     1159#!html 
     1160<blockquote style="padding: 5px 20px 5px 40px; margin: 10px 0; background: #ffc url(http://www.symfony-project.com/images/note.gif) no-repeat 5px 10px; border: 1px solid #ddd;"> 
     1161<p> 
     1162Non hai bisogno di pulire la cache all'inizio di un test funzionale; lo script di bootstrap lo fa per te. 
     1163</p></blockquote> 
     1164}}} 
     1165 
     1166 
     1167=== Testare interazioni lato client === 
     1168 
     1169Il 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. 
     1170 
     1171La 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. 
     1172 
     1173Selenium 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. 
     1174 
     1175{{{ 
     1176#!html 
     1177<blockquote style="padding: 5px 20px 5px 40px; margin: 10px 0; background: #ffc url(http://www.symfony-project.com/images/caution.gif) no-repeat 5px 10px; border: 1px solid #ddd;"> 
     1178<p> 
     1179Fai attenzione a non copiare la cartella <code>selenium/</code> sul server in produzione, in quanto risulterebbe accessibile a chiunque dalla webroot. 
     1180</p></blockquote> 
     1181}}} 
     1182 
     1183I test di Selenium sono scritti in HTML e memorizzati nella cartella {{{web/selenium/tests/}}}. Ad esempio, il Listato 15-36 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}}}. 
     1184 
     1185''Listato 15-36 - Esempio di test Selenium, in {{{web/selenium/test/testIndex.html}}}'' 
     1186{{{ 
     1187#!php 
     1188<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> 
     1189<html> 
     1190<head> 
     1191  <meta content="text/html; charset=UTF-8" http-equiv="content-type"> 
     1192  <title>Index tests</title> 
     1193</head> 
     1194<body> 
     1195<table cellspacing="0"> 
     1196<tbody> 
     1197  <tr><td colspan="3">First step</td></tr> 
     1198  <tr><td>open</td>              <td>/myapp_test.php/</td> <td>&nbsp;</td></tr> 
     1199  <tr><td>clickAndWait</td>      <td>link=click me</td>    <td>&nbsp;</td></tr> 
     1200  <tr><td>assertTextPresent</td> <td>Hello, World!</td>    <td>&nbsp;</td></tr> 
     1201</tbody> 
     1202</table> 
     1203</body> 
     1204</html> 
     1205}}} 
     1206 
     1207Un 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. 
     1208 
     1209Hai 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-37 mostra come. 
     1210 
     1211''Listato 15-37 - Aggiungere un file di test alla suite di test, in {{{web/selenium/test/TestSuite.html}}}'' 
     1212{{{ 
     1213#!php 
     1214... 
     1215<tr><td><a href='./testIndex.html'>My First Test</a></td></tr> 
     1216... 
     1217}}} 
     1218 
     1219Per eseguire il test, naviga semplicemente su 
     1220{{{ 
     1221#!php 
     1222http://myapp.example.com/selenium/index.html 
     1223}}} 
     1224 
     1225Seleziona 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. 
     1226 
     1227{{{ 
     1228#!html 
     1229<blockquote style="padding: 5px 20px 5px 40px; margin: 10px 0; background: #ffc url(http://www.symfony-project.com/images/note.gif) no-repeat 5px 10px; border: 1px solid #ddd;"> 
     1230<p> 
     1231Dato che i test Selenium girano su un browser reale, ti permettono anche di testarne le inconsistenze. Costruisci i tuoi test con un browser, e poi testali con tutti quelli con cui il tuo sito dovrebbe funzionare. 
     1232</p></blockquote> 
     1233}}} 
     1234 
     1235Il 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://seleniumrecorder.mozdev.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. 
     1236 
     1237Puoi 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. 
     1238 
     1239{{{ 
     1240#!html 
     1241<blockquote style="padding: 5px 20px 5px 40px; margin: 10px 0; background: #ffc url(http://www.symfony-project.com/images/note.gif) no-repeat 5px 10px; border: 1px solid #ddd;"> 
     1242<p> 
     1243Non dimenticare di reinizializzare i dati di test prima di lanciare i test Selenium. 
     1244</p></blockquote> 
     1245}}} 
     1246 
     1247 
     1248== Sommario == 
     1249 
     1250I test automatici includono unit test per convalidare metodi o funzioni e test funzionali per convalidare funzionalità. Symfony si basa sul framework di testing {{{lime}}} per le unit test 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 le unit test di symfony. 
     1251 
     1252Se ti assicuri di scrivere abbastanza unit test 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.