Gli oggetti mock

SimpleTest for PHP mock objects documentation

# what Cosa sono gli oggetti mock?

Gli oggetti mock svolgono due ruoli durante un test case: il ruolo di "attori" ed il ruolo di "critici"

Nel ruolo di attore il mock ha la funzione di simulare il comportamento di un altro oggetto che durante il test sarebbe difficile o oneroso da instanziare. L'esempio classico è quello di una connessione ad un database. Allestire un database di collaudo all'inizio di ogni singolo testo rallenterebbe il collaudo a livelli inaccettabile e richiederebbe l'istallazione di un apposito motore di database e di dati di test sul server di test. Se ci fosse la possibilità di similare la connessione e restituire i dati selezionati per il collaudo non solo si otterrebbero dei test più pragmatici ma ci si potrebbe permettere di alimentare il codice con dati spuri per vederne la reazione. Avremmo la possibilità di simulare il caso di un database fuori linea o altre condizioni estreme di questo tipo, senza la necessità di generare i malfunzionamenti nella realtà. In altre parole, i mock possono fornire un grande controllo dell'ambiente di test.

Se gli oggetti mock si comportassero solo come attori sarebbero semplicemente conosciuto come "server stubs". Questi sono stati introdotti come pattern di sviluppo da Robert Binder in (Testing object-oriented systems models, patterns, and tools, Addison-Wesley) nel 1999.

Un server stub è la simulazione di un oggetto o di un componente. Dovrebbe rimpiazzare esplicitamente un componente con lo scopo di permettere il test o la prototipazione ma, fondamentalmente, resta un componente leggero. In questo modo permette ai test di eseguirsi con rapidità; nel caso la classe simulata ancora non esista affatto, permettono di eseguire comunque il codice.

Tuttavia, gli oggetti mock non solo svolgono questo ruolo (restituiendo valori di ritorno alla loro interrogazione) ma sono anche sensibili ai messaggi che vengono loro inviati (tramite le expectation). Impostando i parametri attesi dalla invocazione di un metodo i mock si comportano da garanti della correttezza nelle chiamate ai metodi. Se le expectation non sono rispettare, i mock ci risparmiano la fatica di scrivere le assert fanno il lavoro sporco a posto nostro.

Nel caso della connessione ad un database immaginrio sono in grado di verificare l'oggetto chiamante stia usando una query, supponiamo SQL, formalmente corretta. Una volta preparai con expectation sufficientemente rigorose non si avrà più bisogno di eseguire manualmente gli assert.

# creation Creare oggetti mock

Tutto quello di cui abbiamo bisogno è una classe esistente, supponiamo per la connessione al database, che abbia questo aspetto: <strong>class DatabaseConnection { function DatabaseConnection() { } function query($sql) { } function selectQuery($sql) { } }</strong> Non c'è bisogno che la classe sia stata implementata. Può bastare la sua interfaccia: <strong>interface DatabaseConnection { function DatabaseConnection(); function query($sql); function selectQuery($sql); }</strong> Per generare una versione mock della classe si deve invocare il code generator: require_once('simpletest/autorun.php'); require_once('database_connection.php'); <strong>Mock::generate('DatabaseConnection');</strong> Questo genera una classe clone chiamata `MockDatabaseConnection`. Adesso è possibile istanziare la nuova classe all'interno del test case: require_once('simpletest/autorun.php'); require_once('database_connection.php'); Mock::generate('DatabaseConnection'); class MyTestCase extends UnitTestCase { function testSomething() { <strong>$connection = new MockDatabaseConnection();</strong> } } La versione mock possiede tutti i metodi della classe originale. Anche i tipi degli argomenti vengono fedelmente preservati. Supponiamo che il metodo query si aspetti un oggetto `Query`: <strong>class DatabaseConnection { function DatabaseConnection() { } function query(Query $query) { } function selectQuery(Query $query) { } }</strong> Nel caso si invochi il metodo con un oggetto di tipo sbagliato o, peggio, con un non-object... class MyTestCase extends UnitTestCase { function testSomething() { $connection = new MockDatabaseConnection(); $connection->query('insert into accounts () values ()'); } } ...il codice lancerebbe un'eccezione di violazione di tipo esattamente come farebbe la classe originale.

Anche se la versione mock possiede tutti i metodi della classe originale, questi restituiscono sfortunatamente `null`. Dal momenti che i metodi restituiscono sistematicamente `null` non risultano molto utili ed abbiamo bisogno di un modo per impostarle per avere un comportamento diverso:

## Mocks as actors

Modificare il valore di ritorno di un metodo da `null` a qualcos'altro è abbastanza semplice: <strong>$connection->returns('query', 37)</strong> Adesso, ogni invocazione di `$connection->query()` restituisce 37. 38 non ha niente di speciale: il valore di ritorno può essere arbitrariamente complesso.

I parametri, in questo caso, sono irrilevanti e una volta che si sia impostato il mock con questo sistema si otterrà sempre il medesimo valore qualsiasi essi siano Questa potrebbe apparire una replica poco convincente di una connessione al database, ma per un metodo di test di una mezza dozzina di righe, solitamente, è tutto quello di cui si può aver bisogno.

Tuttavia, le cose non sono sempre così semplici. Uno dei problemi comuni si propone con le iterazioni, nelle quali il ritorno sistematico del solito valore può causare cicli infiniti dentro l'oggetto da collaudare. Per questo c'è bisogno di poter impostare sequenze di valori. Supponiamo di avere una semplice iterazione come questa: class Iterator { function Iterator() { } function next() { } } Stiamo parlando dell'iterazione più semplice che possa capitare. Se si assume che questa iterazione restituisca del testo fino a quando raggiunge la fine e che da quel momento debba restituire false, si potrebbe simularla così: Mock::generate('Iterator'); class IteratorTest extends UnitTestCase() { function testASequence() {<strong> $iterator = new MockIterator(); $iterator->returns('next', false); $iterator->returnsAt(0, 'next', 'First string'); $iterator->returnsAt(1, 'next', 'Second string');</strong> ... } } Quando `next()` di `MockIterator` viene invocata restituirà "First string" la prima volta e "Second string" la seconda mentre false tutte le altre volte. I valori di ritorno delle sequenze hanno precedenza sul valori di ritorno costante. Se può essere utile, si può pensare al valore di ritorno costante come il valore di ritorno di default.

Un'altra situazione delicata è il caso di un'operazione `get()` overloaded. Ad esempio è quando un'informazione conservata con una coppia nome/valore. Supponiamo di avere una classe di configurazione come questa: class Configuration { function Configuration() { ... } function get($key) { ... } } Questo è la tipica situazione in cui un oggetto mock risulta utile, dal momento che la configurazione varietà da macchina a macchina e perfino da test a test. Tuttavia, il problema è che i dati vengono recuperati mediante la chiamata al metodo `get()` e che, contemporaneamente, si desiderano valori differenti in corrispondenza di chiavi differenti. Fortunatamente, i mock dispongono di un sistema di filtraggio: <strong>$config = &new MockConfiguration(); $config->returns('get', 'primary', array('db_host')); $config->returns('get', 'admin', array('db_user')); $config->returns('get', 'secret', array('db_password'));</strong> Il parametro aggiunto è l'elenco degli argomenti con i quali provare a fare un match. In questo caso, ad esempio, si sta provando il match con il solo argomento a disposizione il quale viene usato come look up key. Adesso, quando il metodo `get()` dell'oggetto mock viene invocato, come in... $config->get('db_user') ...restituirà "admin". Il risultato è stato ricavato tentando il match degli argomenti richiesti con ognuno dei valori dell'elenco, fino a che un match completo viene individuato.

E' anche possibile impostare un argomento di default: <strong> $config->returns('get', false, array('*'));</strong> Questo non è equivalente ad impostare il valore di ritorno che si deve avere in assenza di argomenti, come accade con: <strong> $config->returns('get', false);</strong> Nel primo caso, viene accettato qualsiasi singolo argomento ma un argomento è comunque richiesto. Il secondo caso accetta un qualsiasi numero di argomenti e viene considerato nel caso siano già stati tentati tutti gli altri match.

Si noti che se nel primo caso si aggiungesse ulteriori parametri dopo il carattere jolly questi verrebbero ignorati perché la condizione sul carattere jolly verrebbe soddisfatta prima della loro valutazione.

Con elenchi di parametri particolarmente complessi l'ordine potrebbe essere importante e si corre il rischio di avere delle corrispondenze che vengono mascherate da caretteri jolly dichiarati prima. Nel caso di dubbi è meglio dichiarare i match più specifici all'inzio.

Ci sono casi in cui si desidera che il mock gestisca dei riferimenti ad oggetti piuttosto che delle copie degli stessi. Accade raramente, ma potrebbe capitare di dover simulare un oggetto che gestisce dati primitivi, come delle stringhe. Per esempio: class Pad { function Pad() { } function &note($index) { } } In questo caso, per impostare un riferimento nell'elenco dei valori di ritorno: function testTaskReads() { $note = 'Buy books'; $pad = new MockPad(); $vector-><strong>returnsByReference(</strong>'note', $note, array(3)<strong>)</strong>; $task = new Task($pad); ... } Con questa soluzione si è certi che ogni volta che viene invocato `$pad->note(3)` viene restituito sempre il medesimo oggetto `$note`, perfino quando questo viene modificato.

E' possibile combinare ortogonalmente i tre fattori tempo, parametri e uso delle copie e dei riferimenti. Per esempio:

$buy_books = 'Buy books'; $write_code = 'Write code'; $pad = new MockPad(); $vector-><strong>returnsByReferenceAt(0, 'note', $buy_books, array('*', 3)); $vector-><strong>returnsByReferenceAt(1, 'note', $write_code, array('*', 3)); Questo restituisce un riferimento a `$buy_books` e successivamente a `$write_code`, ma solo sei i due paramtri risultano impostati ed il secondo è l'intero 3. In questo modo ogni esigenza dovrebbe risultare soddisfatta.

Un ultimo caso delicato è quello di un oggetto che ne instanzia un altro, ovvero quello che è conosciuto come factory pattern. Supponiamo che una query al nostro immaginario database abbia successo e che venga restituito un result set nella forma di iteratore in modo che ad ogni chiamata del metodo `next()` dell'iteratore venga fornita una riga o il valore false. L'idea di dover simulare un comportamento simile può sembrare da incubo ma in realtà può essere realizzata con un mock con il meccanismi già presentati: Mock::generate('DatabaseConnection'); Mock::generate('ResultIterator'); class DatabaseTest extends UnitTestCase { function testUserFinderReadsResultsFromDatabase() {<strong> $result = new MockResultIterator(); $result->returns('next', false); $result->returnsAt(0, 'next', array(1, 'tom')); $result->returnsAt(1, 'next', array(3, 'dick')); $result->returnsAt(2, 'next', array(6, 'harry')); $connection = new MockDatabaseConnection(); $connection->returns('selectQuery', $result);</strong> $finder = new UserFinder(<strong>$connection</strong>); $this->assertIdentical( $finder->findNames(), array('tom', 'dick', 'harry')); } } Adesso solo se viene invocato il metodo `query()` di `$connection` viene restituito l'oggetto `$result` che si esaurisce la sua funzione dopo la terza chiamata a `next()`. Dovrebbero esserci abbastanza informazioni perché la classe `UserFinder` possa essere collaudata senza problemi. Un test molto preciso e nessun database reale tra i piedi.

E' possibile ulteriormente raffinare questo test richiedendo che venga utilizzata la query corretta: $connection->returns('selectQuery', $result, array(<strong>'select name, id from people'</strong>)); Il realtà questa è una cattiva idea.

Abbiamo `UserFinder` che appartiene al mondo degli oggetti e che comunica con le tabelle del database utilizzando un'interfaccia piuttosto estesa, l'intero SQL. Immaginamo di aver scritto molti test come questo che adesso si trovano a dipendere dall'esatta stringa SQL utilizzata. C'è la possibilità che queste query cambino in massa per ragioni non legate allo specifico test. Ad esempio, le regole nell'uso di apici e virgolette potrebbe cambiare, potrebbe essere rinominata una tabella, potrebbe venire aggiunta una link table e così via. Queste modifiche richiederebbero la riscrittura di ogni singolo test ogni volta che viene eseguito un refactoring, nonostante il comportamento da collaudare sia fondamentalmente restato inalterato. I test sono pensati per aiutare il refactoring, non per ostacolarlo. Personalmente, preferirei certamente una test suite che non faccia fallire i test solo per un cambio di nome di tabella.

Una regola è quella di non scrivere mock di interfacce troppo complesse.

Al contrario, ecco come appare un test completo: class DatabaseTest extends UnitTestCase {<strong> function setUp() { ... } function tearDown() { ... }</strong> function testUserFinderReadsResultsFromDatabase() { $finder = new UserFinder(<strong>new DatabaseConnection()</strong>); $finder->add('tom'); $finder->add('dick'); $finder->add('harry'); $this->assertIdentical( $finder->findNames(), array('tom', 'dick', 'harry')); } } Questo collaudo è immune ai cambiamenti dello schema del db. Fallisce solo se si rompono le funzionalità dell'applicazione, cioè quello che intendevamo collaudare.

Il segreto è nel codice di `setUp()` e di `tearDown()` sui quali fino ad ora abbiamo sorvolato. Questi metodi hanno il compito di ripulire le tabelle del database ed assicurare la correttezza dello schema. Realizzare questo risultato può comportare molto lavoro ma generalente si dispone comunque di codice simile per svolgere il deployment.

Un'area dove si ha veramente bisogno del mocking è nella simulazione delle anomalie. Supponiamo che il database cada durante il funzionamento di `UserFinder`

`UserFinder` reagisce come desideriamo? class DatabaseTest extends UnitTestCase { function testUserFinder() { $connection = new MockDatabaseConnection();<strong> $connection->throwOn('selectQuery', new TimedOut('Ouch!'));</strong> $alert = new MockAlerts();<strong> $alert->expectOnce('notify', 'Database is busy - please retry');</strong> $finder = new UserFinder($connection, $alert); $this->assertIdentical($finder->findNames(), array()); } } E' stato passato un oggetto `$alert` a `UserFinder`. Questo invierà alcuni messaggi all'interfaccia utente. Per poter passare il test, il finder, piuttostci che propagare un'eccezione, deve scrivere un messaggio a `$alert`.

Per adesso, tutto bene.

Come possiamo dire al mock `DatabaseConnection` di lanciare l'eccezione? La generiamo usando il metodo `throwOn`.

Infine, cosa accade se il metodo che intendiamo simulare non esiste affatto? Se si tenta di impostare un valore di ritorno su un metodo che non esiste, SimpleTest lancia un'errore. Perché non usare `__call()` per simulare dinamicamente i metodi?

L'uso di `__call` sugli oggetti con interfacce dinamiche nella implementazione corrente dei mock può essere problematico. E' possibile fare il mock del metodo `__call()` ma è una soluzione orrenda. Perché mail un test dovrebbe prendersi cura di tali dettagli di implementazione? Il test vuole solo simulare la chiamata.

La soluzione a questo è di aggiungere metodi extra al mock durante la sua generazione. <strong>Mock::generate('DatabaseConnection', 'MockDatabaseConnection', array('setOptions'));</strong> In test suite di notevoli dimensioni questo può generare problemi, dal momento che c'è il rischio di avere già una classe chiamata `MockDatabaseConnection` priva dei metodi extra. Il code generator infatti non genera la versione mock di una classe se ne trova un'altra con lo stesso nome. Vedere il proprio metodo fallire perché un'altra definizione è stata invocata precedentemente può essere disorientante.

Per gestire questa situazione, SimpleTest permette allo sviluppatore di scegliere liberamente il nome della nuova classe nel momento dell'aggiunta dei metodi extra. Mock::generate('DatabaseConnection', <strong>'MyMockDatabaseConnection'</strong>, array('setOptions')); In questo esempio il mock si comporta come se `setOptions()` esistesse nella classe originale.

Gli oggetti mock possono essere utilizzati solo all'interno dei test case perché inviano messaggi, come expectation, direttamente al test case. Lanciarli al di fuori dell'ambiente del test case provocherebbe un run time error non appena la prima expectation fosse invocata. Parleremo delle expectation più avanti.

# expectations Mock come critici

Nonostante l'approccio dei server stub riesca ad isolare i test dagli sconvolgimenti del mondo, questa è solo una metà dei benefici. E' possibile fare in modo che la classe sotto collaudo riceva i messaggi richiesti: ma si riesce a fargli inviare indietro quelli corretti? Il collaudo può diventare un vero inferno senza una libreria di mocking.

Per fare un esempio, consideriamo una semplice classe `PageController` che gestisca il form per il pagamento con carta di credito:

class PaymentForm extends PageController { function __construct($alert, $payment_gateway) { ... } function makePayment($request) { ... } } Questa classe utilizza un oggetto `PaymentGateway` per comunicare fisicamente con la banca ed un oggetto `Alert` per gestire gli errori. La classe comunica anche alla pagine o al template. E' responsabile della visualizzazione degli errori e dell'evidenziazione di ogni campo del form che non sia stato compilato correttamente.

Il metodo che ci interessa è `makePayment()`. Se viene inserito un numero "CVV2" errato (le ultime 3 cifre sul retro della carta di credito) si vuole che il processo di pagamento non venga nemmeno iniziato e che venga visualizzato invece un errore. Nel test form avremo: <?php require_once('simpletest/autorun.php'); require_once('payment_form.php'); Mock::generate('Alert'); Mock::generate('PaymentGateway'); class PaymentFormFailuresShouldBeGraceful extends UnitTestCase { function testMissingCvv2CausesAlert() { $alert = new MockAlert(); <strong>$alert->expectOnce( 'warn', array('Missing three digit security code', 'cvv2'));</strong> $controller = new PaymentForm(<strong>$alert</strong>, new MockPaymentGateway()); $controller->makePayment($this->requestWithMissingCvv2()); } function requestWithMissingCvv2() { ... } } ?> Una domanda sorge spontanea: dove sono gli assert?

La chiamata a `expectOnce('warn', array(...))` indica al mock di attendersi una chiamata a `warn()` prima che il test termini. Nel momento in cui la chiamata a `warn()` viene intercettata, il mock verifica gli argomenti utilizzati. Se gli argomenti non corrispondono a quanto stabilito viene generata una failure. Viene generata una failure anche nel caso la chiamata non venga eseguita affatto.

Il test appena visti non si milita a verificare che `warn` venga chiamato ma che vengano ricevuta la stringa "Missing three digit security code" ed il tag "cvv2". Nel momento della comparazione di entrambi i parametri viene applicato l'equivalente di `assertIdentical()`.

Nel caso non si sia interessati al messaggio, come accade nel caso di messaggi provenienti dall'interfaccia utente soggetti a molti cambiamenti, è possibile saltare il parametro con l'operatore "*": class PaymentFormFailuresShouldBeGraceful extends UnitTestCase { function testMissingCvv2CausesAlert() { $alert = new MockAlert(); $alert->expectOnce('warn', array(<strong>'*'</strong>, 'cvv2')); $controller = new PaymentForm($alert, new MockPaymentGateway()); $controller->makePayment($this->requestWithMissingCvv2()); } function requestWithMissingCvv2() { ... } } Possiamo rendere ancora più debole il test tralasciando l'array dei parametri: function testMissingCvv2CausesAlert() { $alert = new MockAlert(); <strong>$alert->expectOnce('warn');</strong> $controller = new PaymentForm($alert, new MockPaymentGateway()); $controller->makePayment($this->requestWithMissingCvv2()); } In questo modo l'unico test che verrà effettuato è sull'invocazione del metodo, il che può essere un po' eccessivo in questo caso. Più avanti vedremo come indebolire le expectation in modo più selettivo.

I test senza assert sono sia compatti che molto espressivi. Siccome la chiamata viene intercettata mentre arriva dall'interno dell'oggetto, in questo caso della classe `Alert`, non c'è il bisogno di dichiarare alcun assert sul suo stato. Non solo questo evita gli assert nei test ma anche la necessità di aggiungere altri accessor al codice originale. Nel caso ci si dovesse scoprire ad aggiungere accessor al codice, cioè quelli che comunemente vengono chiamati "state based testing", potrebbe essere il momento di considerare l'uso dei mock nei propri test. Quest'ultimo approccio è chiamato "behaviour based testing" ed è normalmente considerato migliore rispetto all'uso degli accessor.

Aggiungiamo un altro test. Assicuriamoci che non si riesca nemmeno a tentare un pagamento in assenza del codice CVV2: class PaymentFormFailuresShouldBeGraceful extends UnitTestCase { function testMissingCvv2CausesAlert() { ... } function testNoPaymentAttemptedWithMissingCvv2() { $payment_gateway = new MockPaymentGateway(); <strong>$payment_gateway->expectNever('pay');</strong> $controller = new PaymentForm(new MockAlert(), $payment_gateway); $controller->makePayment($this->requestWithMissingCvv2()); } ... } Nei test può risultare molto difficile realizzare l'assert di una condizione negativa ma il metodo `expectNever()` rende l'operazione semplice:

`expectOnce()` e `expectNever()` sono più che sufficienti per la maggior parte dei test ma, occasionalmente, si potrebbe aver bisogno di collaudare più eventi. Normalmente, a vantaggio dell'usabilità, si vuole che vengano evidenziati tutti i campi non compilati del form e non solo il primo. Allo scopo dovremmo essere in grado di eseguire chiamate multiple a `Alert::warn()` e non solo una: function testAllRequiredFieldsHighlightedOnEmptyRequest() { $alert = new MockAlert();<strong> $alert->expectAt(0, 'warn', array('*', 'cc_number')); $alert->expectAt(1, 'warn', array('*', 'expiry')); $alert->expectAt(2, 'warn', array('*', 'cvv2')); $alert->expectAt(3, 'warn', array('*', 'card_holder')); $alert->expectAt(4, 'warn', array('*', 'address')); $alert->expectAt(5, 'warn', array('*', 'postcode')); $alert->expectAt(6, 'warn', array('*', 'country')); $alert->expectCallCount('warn', 7);</strong> $controller = new PaymentForm($alert, new MockPaymentGateway()); $controller->makePayment($this->requestWithMissingCvv2()); } Il contatore presente in `expectAt()` rappresenta il numero di volte per il quale il metodo è già stato invocato. Qui si sta eseguendo un assert per ogni campo che verrà evidenziato.

Si noti che siamo costretti a valutare anche l'ordine delle chiamate. SimpleTest non prevede ancora un sistema per evitare questo inconveniente ma lo prevederà nelle prossime versioni.

Di seguito è riportato un elenco delle expectation utilizzabili su un oggetto mock in SimpleTest. Così come per gli assert, questi metodi accettano un parametro opzionale con il messaggio di failure.

Expectation | Descrizione
--- | ---
`expect($method, $args)` | Nel caso il metodo venga invocato, gli argomenti devono corrispondere a quanto dichiarato
`expectAt($timing, $method, $args)` | Gli argomenti devono corrispondere alla `$timing`-esima chiamata
`expectCallCount($method, $count)` | Il metodo deve essere invocato esattamente `$count` volte
`expectMaximumCallCount($method, $count)` | Il metodo deve essere invocato non più di `$count` volte
`expectMinimumCallCount($method, $count)` | Il metodo deve essere invocato almeno `$count` volte
`expectNever($method)` | Il metodo non deve essere mai invocato
`expectOnce($method, $args)` | Il metodo deve essere invocato una sola volta e con i parametri indicati
`expectAtLeastOnce($method, $args)` | Il metodo deve essere invocato almeno una volta e con i parametri indicati

I parametri sono:

$method
  La stringa con il nome del metodo al quale applicare la condizione.
$args
  L'elenco degli argomenti. E' possibile utilizzare i caratteri jolly allo stesso modo in cui si utilizzano con `setReturn()`. Questo argomento è opzionale in `expectOnce()` e in `expectAtLeastOnce()`.
$timing
  L'istante in cui valutare la consizione. La prima chiamata inizia con 0 ed i conteggi delle chiamate ai vari metodi sono indipendenti tra loro.
$count
  Il numero di invocazioni attese

Nel caso si abbia una sola chiamata nel test, è bene assicurarsi di usare `expectOnce`.
L'uso di `$mocked->expectAt(0, 'method', 'args);` consertirebbe al metodo di non essere mai invocato. Il controllo degli argomenti ed il controllo del numero totale di chiamate sono operazioni indipendenti. Quando si usa `expectAt()`, deve essere aggiunta la chiamata a `expectCallCount()` altrimenti risulterà consentito non invocare mai il metodo.

Come per gli assert nei test case, tutte le expectation accettano come terzo parametro un messaggio con il quale sovrascrivere il messaggio di default. Come nell'altro caso, inoltre, il messaggio di default può essere recuperato mediante "%s".

What are mock objects? Creating mock objects. Mocks as critics with expectations.
The original Mock objects paper. SimpleTest project page on SourceForge. SimpleTest home page on LastCraft.
software development, php programming, programming php, software development tools, php tutorial, free php scripts, architecture, php resources, mock objects, junit, php testing, unit test, php testing, jmock, nmock