Meilleures pratiques pour tester les méthodes protégées avec PHPUnit [closed]

j'ai trouvé la discussion sur avez-vous test privé de la méthode informatif.

j'ai décidé, que dans certaines classes, je veux avoir des méthodes protégées, mais les tester. Certaines de ces méthodes sont statiques et court. Parce que la plupart des méthodes publiques en font usage, je serai probablement en mesure de retirer les tests en toute sécurité plus tard. Mais pour commencer avec une approche TDD et éviter le débogage, je veux vraiment les tester.

j'ai pensé

  • Méthode de l'Objet en tant que conseillé dans une réponse semble être exagéré pour cela.
  • commencer par des méthodes publiques et lorsque la couverture du code est donnée par des tests de niveau supérieur, les tourner protégés et supprimer les tests.
  • hériter d'une classe avec une interface testable rendant les méthodes protégées publiques

Quelle est la meilleure pratique? Est-il autre chose?

il semble, que JUnit change automatiquement les méthodes protégées pour être public, mais je n'ai pas eu un examen plus approfondi à elle. PHP ne permet pas cela via reflection .

240
demandé sur Community 2008-10-30 12:32:41

8 réponses

si vous utilisez PHP5 (>= 5.3.2) avec PHPUnit, vous pouvez tester vos méthodes privées et protégées en utilisant la réflexion pour les rendre publiques avant de lancer vos tests:

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(...));
  ...
}
368
répondu uckelman 2010-10-24 10:49:41

vous semblez déjà au courant, mais je vais quand même le reformuler; c'est un mauvais signe, si vous avez besoin de tester des méthodes protégées. Le but d'un test unitaire, est de tester l'interface d'une classe, et les méthodes protégées sont des détails de mise en œuvre. Cela dit, il y a des cas où cela a du sens. Si vous utilisez l'héritage, vous pouvez voir une superclasse comme fournissant une interface pour la sous-classe. Donc, ici, vous devez tester la méthode protégée (Mais jamais une privé ). Le la solution à cela, est de créer une sous-classe à des fins d'essai, et utiliser ceci pour exposer les méthodes. Par exemple.:

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

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

notez que vous pouvez toujours remplacer l'héritage par la composition. Lors du test de code, il est généralement beaucoup plus facile de traiter le code qui utilise ce modèle, donc vous pouvez vouloir considérer cette option.

41
répondu troelskn 2008-10-30 10:46:19

teastburn a la bonne approche. Encore plus simple est d'appeler la méthode directement et de retourner la réponse:

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);
    }
}

vous pouvez l'appeler simplement dans vos tests par:

$returnVal = PHPUnitUtil::callMethod(
                $this->object,
                '_nameOfProtectedMethod', 
                array($arg1, $arg2)
             );
27
répondu robert.egginton 2017-05-23 12:10:41

j'aimerais proposer une légère variation à getMethod() défini dans uckelman's answer .

cette version change getMethod() en supprimant les valeurs codées et en simplifiant un peu l'utilisation. Je recommande de l'ajouter à votre classe PHPUnitUtil comme dans l'exemple ci-dessous ou à votre classe PHPUnit_Framework_TestCase-extending (ou, je suppose, globalement à votre fichier PHPUnitUtil).

depuis MyClass est instancié de toute façon et ReflectionClass peut prendre une corde ou un objet...

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
}

j'ai aussi créé une fonction alias getprotectdmethod() pour être explicite sur ce qui est attendu, mais cela dépend de vous.

santé!

17
répondu teastburn 2017-05-23 12:02:48

je pense que troelskn est proche. Je le ferais plutôt:

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

puis, mettre en œuvre quelque chose comme ceci:

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

vous exécutez ensuite vos tests contre Testclassstotest.

il devrait être possible de générer automatiquement de telles classes d'extension en analysant le code. Je ne serais pas surpris si PHPUnit offre déjà un tel mécanisme (bien que je n'ai pas vérifié).

8
répondu Michael Johnson 2008-10-31 18:36:25

je vais jeter mon chapeau dans le ring ici:

j'ai utilisé le _ _ call hack avec des degrés de succès variables. L'alternative que j'ai trouvée était d'utiliser le modèle de visiteur:

1: générer une stdClass ou classe personnalisée (pour faire respecter le type)

2: prime qu'avec la méthode et les arguments requis

3: Assurez-vous que votre SUT a une méthode acceptVisitor qui exécutera la méthode avec le arguments spécifiés dans la classe de visite

4: injectez-le dans la classe que vous souhaitez tester""

5: SUT injecte le résultat de l'opération dans le visiteur

6: Appliquez vos conditions d'essai à l'attribut résultat du visiteur

3
répondu sunwukung 2010-08-26 15:59:45

vous pouvez en effet utiliser __call() de manière générique pour accéder aux méthodes protégées. Pour pouvoir tester cette classe

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

vous créez une sous-classe dans 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);
    }
}

notez que la méthode __call() ne fait pas référence à la classe de n'importe quelle manière donc vous pouvez copier ce qui précède pour chaque classe avec des méthodes protégées que vous voulez tester et juste changer la déclaration de classe. Vous pouvez être en mesure de placer cette fonction dans une base commune de la classe, mais je n'ai pas essayé.

maintenant le cas de test lui-même ne diffère que dans l'endroit où vous construisez l'objet à tester, en échangeant par exemple exposé.

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

je crois que PHP 5.3 permet d'utiliser la réflexion pour changer l'accessibilité des méthodes directement, mais je suppose que vous auriez à faire pour chaque méthode individuelle.

3
répondu David Harkness 2011-06-23 17:14:55

je suggère la solution de contournement suivante pour "Henrik Paul"'s solution/idée :)

vous connaissez les noms des méthodes privées de votre classe. Par exemple, ils sont comme _add(), _edit(), _delete (), etc.

donc quand vous voulez le tester du point de vue des tests unitaires, il suffit d'appeler méthodes privées en préfixant et / ou en suffixe quelque mot commun (par exemple _addPhpunit) de sorte que quand la méthode _ _ call () est appelée (depuis la méthode _addphpunit() doesn't exist) de la classe propriétaire, vous mettez juste le code nécessaire dans la méthode __call () pour supprimer le/Les mot (s) préfixé (s)/suffixé (s) (Phpunit) et ensuite pour appeler que la méthode privée déduite de là. C'est une autre bonne utilisation de méthodes magiques.

essayez.

2
répondu Anirudh Zala 2009-09-09 10:32:34