Le expectation

Estendere lo unit tester di SimpleTest con nuove classi expectation.

# mock Maggiore controllo sugli oggetti mock

Il comportamento standard degli oggetti mock di SimpleTest è di prevedere un'esatta coincidenza degli argomenti o di accettarli tutti indistintamente. Per la maggior parte dei test questo è sufficiente. A volte, però, si potrebbe desiderare un case test più debole.

Un aspetto sul quale un test potrebbe rivelarsi troppo fortemente accoppiato è nel match dei testi. Si supponga, ad esempio, di disporre di un componente che restituisca un messaggio di errore quando qualcosa sia andato storto. Si desidera eseguire il collaudo sulla correttezza dell'errore riceuto ma il suo testo potrebbe essere piuttosto lungo. Se si dovesse scrivere il test in funzione del testo esatto, ogni volta che il messaggio viene modificato si deve tornare sui propri passi e modificare di conseguenza anche la test suite.

Per fare un esempio, supponiamo di avere un servizio di new che fallisca nel tentativo di collegarsi alla sorgente remota. <strong>class NewsService { ... function publish($writer) { if (! $this->isConnected()) { $writer->write('Cannot connect to news service "' . $this->_name . '" at this time. ' . 'Please try again later.'); } ... } }</strong> Il servizio invia il contenuto ad una classe `Writer`. Si potrebbe pensare di collaudare questo comportamento con un `MockWriter`, in questo modo: class TestOfNewsService extends UnitTestCase { ... function testConnectionFailure() {<strong> $writer = new MockWriter(); $writer->expectOnce('write', array( 'Cannot connect to news service ' . '"BBC News" at this time. ' . 'Please try again later.')); $service = new NewsService('BBC News'); $service->publish($writer);</strong> } } Questo è il tipico esempio di test debole. Se solo si decidesse di aggiungere nuove istruzioni, come il suggerimento di fonti di news alternative, il test smetterebbe di funzionare nonostante nessuna sottostante funzionalità risulterebbe alterata.

Per risolvere casi come questi, sarebbe comodo eseguire il match con un'espressione regolare, piuttosto che con l'esatta stringa. Si potrebbe cioè fare: class TestOfNewsService extends UnitTestCase { ... function testConnectionFailure() { $writer = new MockWriter();<strong> $writer->expectOnce( 'write', array(new PatternExpectation('/cannot connect/i')));</strong> $service = new NewsService('BBC News'); $service->publish($writer); } } Piuttosto che passare il `MockWriter` come parametro si è passata una classe chiamata `PatternExpectation`. L'oggetto mock è abbastanza intelligente per riconoscere che è un caso speciale e lo tratta differentemente. Invece di limitarsi a confrontare l'argomento in entrata con l'oggetto, utilizza lo stesso oggetto expectation per eseguire il test.

`PatternExpectation` accetta l'espressione regolare da utilizzare per il confronto come parametro del costruttore. Ogni volta che `MockWriter` esegue una comparazione con la classe expectation, utilizzerà `preg_match()` con l'espressione regolare fornita. Con il case test appena vista, ogni volta che la stringa "cannot connect" compare nel testo del messaggio, il mock segnalerà un evento di successo allo unit tester. Il resto del testo verrà ignorato.

Le possibili classi expectation sono:

`AnythingExpectation` | Corrisponde sempre
`EqualExpectation` | Confronto di mera uguaglianza piuttosto che di indentità
`NotEqualExpectation` | Comparazione di ineguaglianza
`IndenticalExpectation` | E' la comparazione standard degli oggetti mock e confronta l'esatta uguaglianza
`NotIndenticalExpectation` | Inverte la logica dell'oggetto mock
`WithinMarginExpectation` | Confronta due valore entro un margine di tolleranza
`OutsideMarginExpectation` | verifica che un valore sia al di fuori di un intervallo
`PatternExpectation` | Utilizz il Regex di Perl per il match di una stringa
`NoPatternExpectation` | Passa solo nel caso il Regex di Perl falisca
`IsAExpectation` | Verifica solo il tipo della classe o il suo nome
`NotAExpectation` | L'opposto di `IsAExpectation`
`MethodExistsExpectation` | Verifica che un certo metodo sia disponibile in un oggetto
`TrueExpectation` | Accetta qualsiasi variabile che PHP valuti true
`FalseExpectation` | Accetta qualsiasi variabile che PHP valuti false

La maggior parte delle expectation accettano il valore atteso nel costruttore. Le expectation sono comparatori di pattern che accettano espressioni regolari e `IsAExpectation` e `NotAExpectation` che accettano un tipo o un nome di classe come stringhe.

Vediamo alcuni esempi:

$mock->expectOnce('method', array(new IdenticalExpectation(14))); Questo è equivalente a `$mock->expectOnce('method', array(14))`. $mock->expectOnce('method', array(new EqualExpectation(14))); Questo è diverso dall'esempio precedente, il quale avrebbe accettat anche la stringa `"14"`. A volte, il controllo aggiuntivo del tipo è troppo restrittivo. $mock->expectOnce('method', array(new AnythingExpectation(14))); Questo è equivalente a `$mock->expectOnce('method', array('*'))`. $mock->expectOnce('method', array(new IdenticalExpectation('*'))); Questo può essere comodo ogni volta che si voglia specificare un `"*"` letteralmente. new NotIdenticalExpectation(14) Questo match accetta qualsiasi parametro eccetto l'intero 14. Anche la stringa `"14"` viene accettata. new WithinMarginExpectation(14.0, 0.001) Accetta qualsiasi valore tra 13.999 e 14.001 inclusi.

# behaviour Cambiare il comportamento dei mock mediante le expectation

Le classi expectation possono essere utilizzate non soltanto per inviare messaggi di assert ai mock ma anche per selezionare i comportamenti che i mock. devono assumere. Ovunque sia previsto un elenco di argomenti è possibile fornire, al suo posto, un elenco di oggetti expectation.

Supponiamo si voglia un mock di un server di autenticazione per simulare un login che si concluda con successo solo nel caso venga ricevuto un oggetto di sessione valido. Si potrebbe agire così: Mock::generate('Authorisation'); <strong> $authorisation = new MockAuthorisation(); $authorisation->returns( 'isAllowed', true, array(new IsAExpectation('Session', 'Must be a session'))); $authorisation->returns('isAllowed', false);</strong> Si è impostato il comportamento standard del mock per restituire false nel caso venga invocato `isAllowed`. Quando il metodo viene invocato con un oggetto `Session` come unico parametro, verrà restituito true. Si è anche previsto un secondo parametro come messaggio: questo verrà visualizzato come parte del messaggio di fallimento del mock nel caso la expectation sia la casua della failure.

Questo meccasimo sofisticato è utilizzato piuttosto raramente ma è stato incluso per completezza.

# extending Estendere le expectation

Le classi expectation hanno una struttura molto semplice. Talmente semplice che risulta molto facile crearne di proprie per le più comuni esigenze di test.

L'esempio qui riportato riguarda il collaudo di indirizzi IP validi. Allo scopo di far funzionare correttamente stub e mock, le nuove classi expectation dovrebbero estendere `SimpleExpectation` o, al più, una sua sottoclasse: <strong>class ValidIp extends SimpleExpectation { function test($ip) { return (ip2long($ip) != -1); } function testMessage($ip) { return "Address [$ip] should be a valid IP address"; } }</strong> Ci sono solo due metodi da implementare. Il metodo `test()` deve restituire true nel caso di expectation passata e false altrimenti. Il metodo `testMessage()` dovrebbe semplicemente restituire un messaggio explicativo che sia di aiuto per capire come il test è stato eseguito.

A questo punto la classe può essere usata al posto della precedente.

Di seguito un esempio più comune, che segue il match di una parte di un hash: <strong>class JustField extends EqualExpectation { private $key; function __construct($key, $expected) { parent::__construct($expected); $this->key = $key; } function test($compare) { if (! isset($compare[$this->key])) { return false; } return parent::test($compare[$this->key]); } function testMessage($compare) { if (! isset($compare[$this->key])) { return 'Key [' . $this->key . '] does not exist'; } return 'Key [' . $this->key . '] -> ' . parent::testMessage($compare[$this->key]); } }</strong> Una convenzione è quella di separare le parti del masseaggio con "&nbsp;->&nbsp;", in modo da facilitare eventuali altri tool nella formattazione dell'output.

Supposiamo che un sistema di autenticazione si attenda una riga proveniente dal database e corrispondente all'utente e che si debba verificare che la username sia corretta. Si potrà verificare la username con: $mock->expectOnce('authenticate', array(new JustKey('username', 'marcus')));

# unit Dentro il cofano dello unit tester

SimpleTest unit testing framework utilizza internamente le classi expectation nella classe UnitTestCase. Possiamo avvantaggiarci di questi meccanismi per il riutilizzo delle classi expectation fatte in casa direttamente all'interno delle suite di test.

Il modo più rozzo per farlo è usare il metodo generico `SimpleTest::assert()` per eseguire il test direttamente: <strong>class TestOfNetworking extends UnitTestCase { ... function testGetValidIp() { $server = &new Server(); $this->assert( new ValidIp(), $server->getIp(), 'Server IP address->%s'); } }</strong> `assert()` eseguirà direttamente il test con la expecration fornita.

In effetti, rispetto alla consueta sintassi di `assert...()` questo è un po' più disordinato.

In questo caso, piuttosto che usare la classe expectation direttamente, normalmente sarà preferibile creare un metodo di assert separato. Se per un attimo si immagina che la expectation sia più complicata abbastanza perché si desideri riutilizzarla, si otterrà: class TestOfNetworking extends UnitTestCase { ...<strong> function assertValidIp($ip, $message = '%s') { $this->assert(new ValidIp(), $ip, $message); }</strong> function testGetValidIp() { $server = &new Server();<strong> $this->assertValidIp( $server->getIp(), 'Server IP address->%s');</strong> } } E' raro che una expectation debba eseguire il match di più pattern ma questa facilitazione permette a chi si occupa di test di costruire una sorta di domain language per il collaudo delle proprie applicazioni. Inoltre, le classi expectation troppo complesse rendono i test difficili da leggere e debuggare. In effetti, estendere il framwork e creare il proprio set di strumenti è il modo migliore di procedere.

Estendere lo unit tester di SimpleTest con nuove classi expectation. Cambiare il comportamento dei mock mediante le expectation Estendere le expectation Dentro il cofano dello unit tester
SimpleTest project page on SourceForge. SimpleTest download page on LastCraft. The expectations mimic the constraints in JMock. Full API for SimpleTest from the PHPDoc.
mock objects, test driven development, inheritance of expectations, mock object constraints, advanced PHP unit testing, test first, test framework architecture expectations in simpletest test cases server stubs