2009-02-13 7 views
9

Per gli oggetti che compongono un altro oggetto come parte della loro implementazione, qual è il modo migliore per scrivere il test unitario in modo da testare solo l'oggetto principale? Esempio banale:Test degli oggetti con dipendenze in PHPUnità

class myObj { 
    public function doSomethingWhichIsLogged() 
    { 
     // ... 
     $logger = new logger('/tmp/log.txt'); 
     $logger->info('some message'); 
     // ... 
    } 
} 

So che l'oggetto potrebbe essere progettato in modo tale che la dipendenza oggetto logger potrebbe essere iniettato e, quindi, preso in giro in un test di unità, ma non è sempre il caso - in scenari più complessi, si ha bisogno comporre altri oggetti o effettuare chiamate a metodi statici.

Dato che non vogliamo testare l'oggetto logger, solo il myObj, come procedere? Creiamo un "doppio" stoppato con lo script di test? Qualcosa di simile:

class logger 
{ 
    public function __construct($filepath) {} 
    public function info($message) {} 
} 

class TestMyObj extends PHPUnit_Framework_TestCase 
{ 
    // ... 
} 

Questo sembra fattibile per piccoli oggetti, ma sarebbe un dolore per le API più complesse dove il SUT dipendeva dai valori di ritorno. Inoltre, cosa succede se si desidera testare le chiamate all'oggetto dipendenza nello stesso modo in cui è possibile con oggetti mock? C'è un modo di deridere oggetti che sono istanziati dal SUT piuttosto che essere passati dentro?

Ho letto la pagina man sui mock ma non sembra coprire questa situazione in cui la dipendenza è composta piuttosto che aggregata. Come si fa?

+0

Hai guardato PHPUnit che ha introdotto le dipendenze di prova e quindi riutilizzare apparecchio dal 3.4? http://sebastian-bergmann.de/archives/848-Fixture-Reuse-in-PHPUnit-3.4.html Cheers Markus –

risposta

4

Come sembra che tu sappia già, le dipendenze delle classi concrete rendono i test difficili (o addirittura impossibili). Devi disaccoppiare questa dipendenza. Una modifica semplice, che non interrompe l'API esistente, è quella predefinita per il comportamento corrente, ma fornisce un hook per sovrascriverlo. Ci sono diversi modi in cui questo potrebbe essere implementato.

Alcuni linguaggi dispongono di strumenti che possono iniettare classi di simulazione in codice, ma non conosco nulla di simile per PHP. Nella maggior parte dei casi, sarebbe probabilmente meglio refactoring del codice comunque.

+0

Sembra che tu stia dicendo che qualsiasi forma di aggregazione è cattiva in quanto è una dipendenza concreta che è un duro accoppiamento e quindi difficile da testare. È giusto?Penso di essere d'accordo ma non porterebbe a uno script di bootstrap pesante in cui tutti gli oggetti di dipendenza sono iniettati dove sono necessari? – DavidWinterbottom

+1

@troelskn, solo per curiosità, potresti dare un esempio di strumenti per quelle lingue di cui stai parlando. –

+0

@david Sì alla prima parte e sort-of-yes alla seconda parte, tranne che "pesante" è soggettivo. I contenitori DI sono un modo per alleviare il dolore un po '. – troelskn

0

Sembra che ho capito male la domanda, vorrei provare di nuovo:

si dovrebbe utilizzare il pattern Singleton o una fabbrica per il logger, se non è già troppo tardi:

class LoggerStub extends Logger { 
    public function info() {} 
} 
Logger::setInstance(new LoggerStub()); 
... 
$logger = Logger::getInstance(); 

Se è possibile 't modificare il codice, è possibile utilizzare un catch-all classe che sta sovraccaricando __call()

class GenericStub { 
    public function __call($functionName, $arguments) {} 
} 
6

seguito troelskn consigliare ecco un esempio di base di cosa dovresti fare.

<?php 

class MyObj 
{ 
    /** 
    * @var LoggerInterface 
    */ 
    protected $_logger; 

    public function doSomethingWhichIsLogged() 
    { 
     // ... 
     $this->getLogger()->info('some message'); 
     // ... 
    } 

    public function setLogger(LoggerInterface $logger) 
    { 
     $this->_logger = $logger; 
    } 

    public function getLogger() 
    { 
     return $this->_logger; 
    } 
} 


class MyObjText extends PHPUnit_Framework_TestCase 
{ 
    /** 
    * @var MyObj 
    */ 
    protected $_myObj; 

    public function setUp() 
    { 
     $this->_myObj = new MyObj; 
    } 

    public function testDoSomethingWhichIsLogged() 
    { 
     $mockedMethods = array('info'); 
     $mock = $this->getMock('LoggerInterface', $mockedMethods); 
     $mock->expects($this->any()) 
      ->method('info') 
      ->will($this->returnValue(null)); 

     $this->_myObj->setLogger($mock); 

     // do your testing 
    } 
} 

Maggiori informazioni su oggetti mock può essere trovato in the manual.

0

Esiste in realtà una nuova estensione ragionevolmente per l'overloading di classe PHP rilasciato dagli stessi ragazzi che creano PHPUnit. Ti consente di ignorare il nuovo operatore nei casi in cui non è possibile ridefinire il codice, purtroppo non è così semplice da installare su Windows.

L'URL è http://github.com/johannes/php-test-helpers/blob/master/