Question

J'ai trouvé la discussion sur la Testez-vous la méthode privée ? informative.

J'ai décidé que dans certaines classes, je souhaitais avoir des méthodes protégées, mais les tester. Certaines de ces méthodes sont statiques et courtes. Étant donné que la plupart des méthodes publiques les utilisent, je serai probablement en mesure de supprimer 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é à ce qui suit:

  • objet de méthode comme indiqué dans une réponse semble être excessive pour cela.
  • Commencez par les méthodes publiques et, lorsque la couverture de code est fournie par des tests de niveau supérieur, mettez-les à l'abri et supprimez les tests.
  • Hériter d'une classe avec une interface testable rendant les méthodes protégées publiques

Quelle est la meilleure pratique? Y a-t-il autre chose?

Il semble que JUnit change automatiquement les méthodes protégées en public, mais je ne l'ai pas analysée plus en profondeur. PHP ne le permet pas via réflexion .

Était-ce utile?

La solution

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 d'exécuter 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(...));
  ...
}

Autres conseils

Vous semblez déjà être au courant, mais je le répète quand même; C'est un mauvais signe si vous devez tester des méthodes protégées. Le but d'un test unitaire est de tester l'interface d'une classe. Les méthodes protégées sont des détails d'implémentation. Cela dit, il y a des cas où cela a du sens. Si vous utilisez l'héritage, vous pouvez voir une superclasse fournissant une interface à la sous-classe. Donc, ici, vous devriez tester la méthode protégée (mais jamais une méthode privée ). La solution à cela consiste à créer une sous-classe à des fins de test et à l'utiliser 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 du code, il est généralement beaucoup plus facile de traiter avec du code qui utilise ce modèle. Vous pouvez donc envisager cette option.

teastburn a le bonne approche. Encore plus simple, appelez directement la méthode et renvoyez 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 appeler cela simplement dans vos tests par:

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

Je voudrais proposer une légère variante de getMethod () définie dans La réponse d'Uckelman .

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

Etant donné que MyClass est de toute façon instanciée, ReflectionClass peut prendre une chaîne 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 également créé une fonction alias getProtectedMethod () pour indiquer explicitement ce à quoi on s'attend, mais c'est à vous de voir.

Salut!

Je pense que Troelskn est proche. Je ferais ceci à la place:

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

Ensuite, implémentez quelque chose comme ceci:

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

Vous exécutez ensuite vos tests sur TestClassToTest.

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’aie pas vérifié).

Je vais jeter mon chapeau dans le ring ici:

J'ai utilisé l'appel __call avec un succès mitigé. L’alternative que j’ai imaginée était d’utiliser le modèle de visiteur:

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

2: amorcez-le 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 les 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 au visiteur

6: appliquez vos conditions de test à l'attribut de résultat du visiteur

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 aucune référence à la classe. Vous pouvez donc copier ce qui précède pour chaque classe avec les méthodes protégées que vous souhaitez tester et modifier simplement la déclaration de la classe. Vous pourrez peut-être placer cette fonction dans une classe de base commune, mais je ne l’ai pas essayée.

Le scénario de test lui-même ne diffère que par l'endroit où vous construisez l'objet à tester, en effectuant la permutation dans ExampleExposed for Example.

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

Je pense que PHP 5.3 vous permet d'utiliser la réflexion pour modifier directement l'accessibilité des méthodes, mais je suppose que vous devriez le faire pour chaque méthode individuellement.

Je suggère de suivre la solution de contournement pour "Henrik Paul":)

Vous connaissez les noms des méthodes privées de votre classe. Par exemple, ils ressemblent à _add (), _edit (), _delete (), etc.

.

Par conséquent, lorsque vous souhaitez le tester sous l'aspect du test unitaire, appelez simplement les méthodes privées en préfixant et / ou en ajoutant un mot commun (par exemple, _addPhpunit) afin que la méthode __call () soit appelé (puisque la méthode _addPhpunit () n'existe pas) de la classe propriétaire, vous venez de mettre le code nécessaire dans la méthode __call () pour supprimer le mot / s suffixé / ph (Phpunit), puis appeler cette méthode privée déduite à partir de là. C’est un autre bon usage des méthodes magiques.

Essayez-le.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top