66 votes

Mock privé méthode avec PHPUnit

J'ai une question sur l'utilisation de PHPUnit pour se moquer d'une méthode privée dans une classe. Permettez-moi de vous présenter un exemple:

 class A {
  public function b() { 
    // some code
    $this->c(); 
    // some more code
  }

  private function c(){ 
    // some code
  }
}
 

Comment puis-je condenser le résultat de la méthode privée pour tester la partie encore plus codée de la fonction publique?

Résolu partiellement en lisant ici

81voto

edorian Points 22780

Généralement, vous n'avez pas de test ou de se moquer de l'privée et protégée des méthodes directement.

Ce que vous voulez de test est le public de l'API de votre classe. Tout le reste est un détail d'implémentation, pour votre classe, et ne doit pas "casser" vos tests si vous le changez.

Que vous aide aussi quand vous remarquez que vous "ne pouvez pas obtenir 100% de couverture de code" car vous pourriez avoir de code dans votre classe, que vous ne pouvez pas exécuter en appelant l'API publique.


D'habitude vous ne voulez pas le faire

Mais si votre classe ressemble à ceci:

class a {

    public function b() {
        return 5 + $this->c();
    }

    private function c() {
        return mt_rand(1,3);
    }
}

je peux voir la nécessité de vouloir se moquer de c() depuis la fonction "random" est l'état global et vous pouvez pas test.

Le "nettoyer?/verbose?/trop compliqué-peut-être?/j'ai-comme-il-généralement de" Solution

class a {

    public function __construct(RandomGenerator $foo) {
        $this->foo = $foo;
    }

    public function b() {
        return 5 + $this->c();
    }

    private function c() {
        return $this->foo->rand(1,3);
    }
}

maintenant il n'est plus nécessaire de se moquer de "c()" car il ne contient pas de variables globales et vous pouvez tester bien.


Si vous ne voulez pas, ou ne pouvez pas supprimer l'état global de votre private function (mauvaise chose mauvaise de la réalité ou vous la définition de mauvais peut être différent) que vous pouvez tester à l'encontre de la maquette.

// maybe set the function protected for this to work
$testMe = $this->getMock("a", array("c"));
$testMe->expects($this->once())->method("c")->will($this->returnValue(123123));

et l'exécution de vos tests à l'encontre de cette maquette étant donné que la seule fonction de vous emporter/maquette est "c()".


Pour citer la "Pragmatique de Tests Unitaires, livre:

"En général, vous ne voulez pas briser l'encapsulation pour l'amour de test (ou en tant que Maman avait l'habitude de dire, "ne pas exposer vos soldats!"). La plupart du temps, vous devriez être en mesure de tester une classe par l'exercice de ses méthodes publiques. Si il est important de fonctionnalités qui est caché derrière privés ou protégés d'accès, qui peut être un signe d'avertissement qu'il y a une autre classe dans il du mal à sortir."


Plus: Why you don't want test private methods.

25voto

doctore Points 1140

Vous pouvez tester les méthodes privées mais vous ne pouvez pas simuler (mock) le fonctionnement de ces méthodes.

En outre, la réflexion ne vous permet pas de convertir une méthode privée protégé ou publique. setAccessible seulement vous permet d'appeler la méthode d'origine.

Sinon, vous pouvez utiliser runkit pour renommer les méthodes privées et comprennent une nouvelle "mise en œuvre". Cependant, ces caractéristiques sont expérimentales et leur utilisation n'est pas recommandée.

24voto

David Harkness Points 16674

Vous pouvez utiliser la réflexion et setAccessible() dans vos tests pour vous permettre de définir l'état interne de votre objet de telle sorte qu'il renvoie ce que vous voulez de la méthode privée. Vous devrez être sur PHP 5.3.2.

 $fixture = new MyClass(...);
$reflector = new ReflectionProperty('MyClass', 'myPrivateProperty');
$reflector->setAccessible(true);
$reflector->setValue($fixture, 'value');
// test $fixture ...
 

10voto

Mark McEver Points 31

Voici une variante des autres réponses pouvant être utilisées pour effectuer de tels appels sur une ligne:

 public function callPrivateMethod($object, $methodName)
{
    $reflectionClass = new \ReflectionClass($object);
    $reflectionMethod = $reflectionClass->getMethod($methodName);
    $reflectionMethod->setAccessible(true);

    $params = array_slice(func_get_args(), 2); //get all the parameters after $methodName
    return $reflectionMethod->invokeArgs($object, $params);
}
 

5voto

Asaph Points 56989

Une option serait de faire c() protected au lieu de private , puis de sous-classer et de remplacer c() . Puis testez avec votre sous-classe. Une autre option serait de refactoriser c() dans une classe différente que vous pouvez injecter dans A (on parle d'injection de dépendance). Ensuite, injectez une instance de test avec une implémentation factice de c() dans votre test unitaire.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by: