Domanda

Ho trovato la discussione su Testare il metodo privato informativo.

Ho deciso che in alcune classi voglio avere metodi protetti, ma testali. Alcuni di questi metodi sono statici e brevi. Poiché la maggior parte dei metodi pubblici li utilizza, probabilmente sarò in grado di rimuovere in sicurezza i test in seguito. Ma per iniziare con un approccio TDD ed evitare il debug, voglio davvero testarli.

Ho pensato a quanto segue:

  • Object Method come indicato in una risposta sembra essere eccessivo per questo.
  • Inizia con metodi pubblici e quando la copertura del codice viene fornita da test di livello superiore, rendili protetti e rimuovi i test.
  • Eredita una classe con un'interfaccia testabile che rende pubblici i metodi protetti

Qual è la migliore pratica? C'è qualcos'altro?

Sembra che JUnit cambi automaticamente i metodi protetti per renderli pubblici, ma non ho avuto uno sguardo più approfondito. PHP non lo consente tramite reflection .

È stato utile?

Soluzione

Se stai usando PHP5 (> = 5.3.2) con PHPUnit, puoi testare i tuoi metodi privati ??e protetti usando reflection per impostarli come pubblici prima di eseguire i test:

protected static function getMethod($name) {
  $class = new ReflectionClass('MyClass');
  $method = $class->getMethod($name);
  $method->setAccessible(true);
  return $method;
}

public function testFoo() {
  $foo = self::getMethod('foo');
  $obj = new MyClass();
  $foo->invokeArgs($obj, array(...));
  ...
}

Altri suggerimenti

Sembri già consapevole, ma lo riaffermerò comunque; È un brutto segno, se è necessario testare metodi protetti. Lo scopo di un unit test è testare l'interfaccia di una classe e i metodi protetti sono dettagli di implementazione. Detto questo, ci sono casi in cui ha senso. Se usi l'ereditarietà, puoi vedere una superclasse che fornisce un'interfaccia per la sottoclasse. Quindi qui, dovresti testare il metodo protetto (ma mai privato ). La soluzione a questo è creare una sottoclasse a scopo di test e utilizzarla per esporre i metodi. Ad esempio:.

class Foo {
  protected function stuff() {
    // secret stuff, you want to test
  }
}

class SubFoo extends Foo {
  public function exposedStuff() {
    return $this->stuff();
  }
}

Nota che puoi sempre sostituire l'eredità con la composizione. Durante il test del codice, di solito è molto più semplice gestire il codice che utilizza questo modello, quindi potresti prendere in considerazione tale opzione.

teastburn ha il giusto approccio. Ancora più semplice è chiamare direttamente il metodo e restituire la risposta:

class PHPUnitUtil
{
  public static function callMethod($obj, $name, array $args) {
        $class = new \ReflectionClass($obj);
        $method = $class->getMethod($name);
        $method->setAccessible(true);
        return $method->invokeArgs($obj, $args);
    }
}

Puoi chiamarlo semplicemente nei tuoi test:

$returnVal = PHPUnitUtil::callMethod(
                $this->object,
                '_nameOfProtectedMethod', 
                array($arg1, $arg2)
             );

Vorrei proporre una leggera variazione a getMethod () definito in La risposta di Uckelman .

Questa versione cambia getMethod () rimuovendo i valori codificati e semplificando un po 'l'utilizzo. Ti consiglio di aggiungerlo alla tua classe PHPUnitUtil come nell'esempio seguente o alla tua classe di estensione PHPUnit_Framework_TestCase (o, suppongo, a livello globale al tuo file PHPUnitUtil).

Poiché MyClass viene comunque istanziato e ReflectionClass può prendere una stringa o un oggetto ...

class PHPUnitUtil {
    /**
     * Get a private or protected method for testing/documentation purposes.
     * How to use for MyClass->foo():
     *      $cls = new MyClass();
     *      $foo = PHPUnitUtil::getPrivateMethod($cls, 'foo');
     *      $foo->invoke($cls, $...);
     * @param object $obj The instantiated instance of your class
     * @param string $name The name of your private/protected method
     * @return ReflectionMethod The method you asked for
     */
    public static function getPrivateMethod($obj, $name) {
      $class = new ReflectionClass($obj);
      $method = $class->getMethod($name);
      $method->setAccessible(true);
      return $method;
    }
    // ... some other functions
}

Ho anche creato una funzione alias getProtectedMethod () per essere esplicito su ciò che ci si aspetta, ma dipende da te.

Cheers!

Penso che troelskn sia vicino. Vorrei fare questo invece:

class ClassToTest
{
   protected testThisMethod()
   {
     // Implement stuff here
   }
}

Quindi, implementa qualcosa del genere:

class TestClassToTest extends ClassToTest
{
  public testThisMethod()
  {
    return parent::testThisMethod();
  }
}

Quindi esegui i test con TestClassToTest.

Dovrebbe essere possibile generare automaticamente tali classi di estensione analizzando il codice. Non sarei sorpreso se PHPUnit offre già un tale meccanismo (anche se non ho controllato).

Lancio il cappello sul ring qui:

Ho usato l'hack __call con diversi gradi di successo. L'alternativa che mi è venuta in mente è stata quella di utilizzare il modello Visitor:

1: genera una classe stdClass o personalizzata (per imporre il tipo)

2: prime quello con il metodo e gli argomenti richiesti

3: assicurarsi che il SUT disponga di un metodo acceptVisitor che eseguirà il metodo con gli argomenti specificati nella classe ospite

4: iniettalo nella classe che desideri testare

5: SUT inserisce il risultato dell'operazione nel visitatore

6: applica le condizioni del test all'attributo del risultato del visitatore

Puoi effettivamente usare __call () in modo generico per accedere a metodi protetti. Per poter provare questa classe

class Example {
    protected function getMessage() {
        return 'hello';
    }
}

crei una sottoclasse in ExampleTest.php:

class ExampleExposed extends Example {
    public function __call($method, array $args = array()) {
        if (!method_exists($this, $method))
            throw new BadMethodCallException("method '$method' does not exist");
        return call_user_func_array(array($this, $method), $args);
    }
}

Si noti che il metodo __call () non fa riferimento alla classe in alcun modo, quindi è possibile copiare quanto sopra per ogni classe con metodi protetti che si desidera testare e modificare semplicemente la dichiarazione di classe. Potresti riuscire a posizionare questa funzione in una classe base comune, ma non l'ho provato.

Ora lo stesso test case differisce solo per il modo in cui si costruisce l'oggetto da testare, scambiando in ExampleExposed per Example.

class ExampleTest extends PHPUnit_Framework_TestCase {
    function testGetMessage() {
        $fixture = new ExampleExposed();
        self::assertEquals('hello', $fixture->getMessage());
    }
}

Credo che PHP 5.3 ti consenta di usare la riflessione per cambiare direttamente l'accessibilità dei metodi, ma suppongo che dovresti farlo per ogni metodo singolarmente.

Suggerisco di seguire la soluzione alternativa per la soluzione / idea di "Henrik Paul" :)

Conosci i nomi dei metodi privati ??della tua classe. Ad esempio sono come _add (), _edit (), _delete () ecc.

Quindi quando vuoi testarlo dall'aspetto del unit test, chiama semplicemente i metodi privati ??prefissando e / o suffissando qualche parola comune (ad esempio _addPhpunit) in modo che quando il metodo __call () sia chiamato (poiché non esiste il metodo _addPhpunit ()) della classe del proprietario, basta inserire il codice necessario nel metodo __call () per rimuovere le parole / i prefissi / suffissi (Phpunit) e quindi chiamare quel metodo privato dedotto da lì. Questo è un altro buon uso dei metodi magici.

Provalo.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top