2011-11-07 4 views
15

Esistono buoni modi per simulare i metodi concreti in classi astratte usando PHPUnit?Metodo concreto di simulazione in classe astratta utilizzando phpunit

Quello che ho trovato finora è:

  • aspetta() -> sarà() funziona bene utilizzando metodi astratti
  • Non funziona per i metodi concreti. Il metodo originale viene invece eseguito.
  • Utilizzando mockbuilder e dando tutti i metodi astratti e il metodo concreto per setMethods() funziona. Tuttavia, richiede di specificare tutti i metodi astratti, rendendo il test fragile e troppo dettagliato.
  • MockBuilder :: getMockForAbstractClass() ignora setMethod().


Ecco alcuni test di unità examplifying i punti di cui sopra:

abstract class AbstractClass { 
    public function concreteMethod() { 
     return $this->abstractMethod(); 
    } 

    public abstract function abstractMethod(); 
} 


class AbstractClassTest extends PHPUnit_Framework_TestCase { 
    /** 
    * This works for abstract methods. 
    */ 
    public function testAbstractMethod() { 
     $stub = $this->getMockForAbstractClass('AbstractClass'); 
     $stub->expects($this->any()) 
       ->method('abstractMethod') 
       ->will($this->returnValue(2)); 

     $this->assertSame(2, $stub->concreteMethod()); // Succeeds 
    } 

    /** 
    * Ideally, I would like this to work for concrete methods too. 
    */ 
    public function testConcreteMethod() { 
     $stub = $this->getMockForAbstractClass('AbstractClass'); 
     $stub->expects($this->any()) 
       ->method('concreteMethod') 
       ->will($this->returnValue(2)); 

     $this->assertSame(2, $stub->concreteMethod()); // Fails, concreteMethod returns NULL 
    } 

    /** 
    * One way to mock the concrete method, is to use the mock builder, 
    * and set the methods to mock. 
    * 
    * The downside of doing it this way, is that all abstract methods 
    * must be specified in the setMethods() call. If you add a new abstract 
    * method, all your existing unit tests will fail. 
    */ 
    public function testConcreteMethod__mockBuilder_getMock() { 
     $stub = $this->getMockBuilder('AbstractClass') 
       ->setMethods(array('concreteMethod', 'abstractMethod')) 
       ->getMock(); 
     $stub->expects($this->any()) 
       ->method('concreteMethod') 
       ->will($this->returnValue(2)); 

     $this->assertSame(2, $stub->concreteMethod()); // Succeeds 
    } 

    /** 
    * Similar to above, but using getMockForAbstractClass(). 
    * Apparently, setMethods() is ignored by getMockForAbstractClass() 
    */ 
    public function testConcreteMethod__mockBuilder_getMockForAbstractClass() { 
     $stub = $this->getMockBuilder('AbstractClass') 
       ->setMethods(array('concreteMethod')) 
       ->getMockForAbstractClass(); 
     $stub->expects($this->any()) 
       ->method('concreteMethod') 
       ->will($this->returnValue(2)); 

     $this->assertSame(2, $stub->concreteMethod()); // Fails, concreteMethod returns NULL 
    } 
} 
+0

Non è necessario per testare le classi astratte come sono astratti. O vuoi scrivere anche un banco di prova astratto? – hakre

+0

La classe astratta è una dipendenza di un'altra classe. Quindi voglio testare SomeClass :: getMyCalculatedValue() che usa $ object-> concreteMethod(). Poiché concreteMethod() può cambiare o potrebbe essere difficile da configurare, voglio specificare il valore di ritorno di concreteMethod(). – CheeseSucker

risposta

4

sovrascrivo getMock() nel mio caso di test di base per aggiungere in tutti i metodi astratti perché è necessario tutti deridere comunque. Potresti fare qualcosa di simile con il costruttore senza dubbio.

Importante: Non è possibile prendere in giro metodi privati.

public function getMock($originalClassName, $methods = array(), array $arguments = array(), $mockClassName = '', $callOriginalConstructor = TRUE, $callOriginalClone = TRUE, $callAutoload = TRUE) { 
    if ($methods !== null) { 
     $methods = array_unique(array_merge($methods, 
       self::getAbstractMethods($originalClassName, $callAutoload))); 
    } 
    return parent::getMock($originalClassName, $methods, $arguments, $mockClassName, $callOriginalConstructor, $callOriginalClone, $callAutoload); 
} 

/** 
* Returns an array containing the names of the abstract methods in <code>$class</code>. 
* 
* @param string $class name of the class 
* @return array zero or more abstract methods names 
*/ 
public static function getAbstractMethods($class, $autoload=true) { 
    $methods = array(); 
    if (class_exists($class, $autoload) || interface_exists($class, $autoload)) { 
     $reflector = new ReflectionClass($class); 
     foreach ($reflector->getMethods() as $method) { 
      if ($method->isAbstract()) { 
       $methods[] = $method->getName(); 
      } 
     } 
    } 
    return $methods; 
} 
+0

Questo funziona. Non ho bisogno di prendere in giro metodi privati ​​=) – CheeseSucker

+0

Com'è diverso da 'getMockForAbstractClass'? – FoolishSeth

+1

@FoolishSeth Le versioni precedenti di PHPUnit non consentivano di [specificare metodi concreti aggiuntivi per la simulazione] (https://github.com/sebastianbergmann/phpunit-mock-objects/pull/49). Poiché non è possibile creare un'istanza di un oggetto fittizio senza prendere in giro ogni metodo astratto, sembra ragionevole fonderli nell'elenco quando si chiama 'getMock()' et al. –