2009-02-13 31 views
9

Para objetos que componen otro objeto como parte de su implementación, ¿cuál es la mejor manera de escribir la prueba unitaria para que solo se pruebe el objeto principal? ejemplo trivial:Prueba de objetos con dependencias en PHPUnit

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

Sé que el objeto podría ser diseñado de manera que se podría inyectar la dependencia de los objetos registrador y por lo tanto se burló en una prueba de unidad, pero eso no es siempre el caso - en los escenarios más complicados, sí es necesario para componer otros objetos o hacer llamadas a métodos estáticos.

Como no queremos probar el objeto logger, solo el myObj, ¿cómo procedemos? ¿Creamos un "doble" trozado con el guión de prueba? Algo así como:

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

class TestMyObj extends PHPUnit_Framework_TestCase 
{ 
    // ... 
} 

Esto parece factible para objetos pequeños, pero sería un dolor para las API más complicados donde el SUT dependía de los valores de retorno. Además, ¿qué sucede si desea probar las llamadas al objeto de dependencia de la misma manera que con los objetos simulados? ¿Hay alguna forma de burlarse de los objetos que son instanciados por el SUT en lugar de ser pasados?

He leído la página del manual en los simulacros, pero no parece cubrir esta situación en la que la dependencia se compone en lugar de agregarse. ¿Cómo lo haces?

+0

¿Has mirado en PHPUnit que introdujo las dependencias de la prueba y por lo tanto accesorio reutilizar desde 3.4? http://sebastian-bergmann.de/archives/848-Fixture-Reuse-in-PHPUnit-3.4.html Cheers Markus –

Respuesta

4

Como usted ya sabe, Concrete Class Dependencies hace que las pruebas sean difíciles (o imposibles). Necesitas desacoplar esa dependencia. Un cambio simple, que no rompe la API existente, es establecer de forma predeterminada el comportamiento actual, pero proporciona un gancho para anularlo. Hay varias formas en que esto podría implementarse.

Algunos idiomas tienen herramientas que pueden inyectar clases simuladas en el código, pero no sé nada de esto para PHP. En la mayoría de los casos, probablemente sea mejor que refactorice su código de todos modos.

+0

Parece que estás diciendo que cualquier forma de agregación es mala ya que es una dependencia concreta que es difícil acoplamiento y por lo tanto, difícil de probar. ¿Es esto correcto?Creo que estoy de acuerdo, pero ¿no conduciría esto a un pesado script de arranque donde todos los objetos de dependencia se inyectan donde se necesitan? – DavidWinterbottom

+1

@troelskn, solo por curiosidad, podría dar un ejemplo de herramientas para los idiomas de los que está hablando. –

+0

@david Sí a la primera parte, y sort-of-yes a la segunda parte, excepto que "heavy" es subjetivo. Los contenedores DI son una forma de aliviar el dolor un poco. – troelskn

0

Parece que no he entendido bien la pregunta, voy a tratar de nuevo:

se debe utilizar el patrón singleton o una fábrica para el registrador, si no es ya demasiado tarde:

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

Si es posible 't cambiar el código, se puede utilizar un cajón de sastre clase que está sobrecargando __call()

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

Siguiendo troelskn asesorar aquí hay un ejemplo básico de lo que debe hacer.

<?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 
    } 
} 

Más información acerca de los objetos de imitación se puede encontrar in the manual.

0

En realidad, existe una extensión razonablemente nueva para la sobrecarga de clase PHP lanzada por los mismos tipos que compilan PHPUnit. Le permite anular el nuevo operador en los casos en que no puede refactorizar el código. Desafortunadamente, no es tan fácil de instalar en Windows.

El URL es http://github.com/johannes/php-test-helpers/blob/master/

Cuestiones relacionadas