Peut-on introduire des méthodes qui ne sont utilisées que lors des tests unitaires?


12

Récemment, j'étais TDDing une méthode d'usine. La méthode consistait à créer soit un objet simple, soit un objet enveloppé dans un décorateur. L'objet décoré peut être de plusieurs types, tous étendant StrategyClass.

Dans mon test, je voulais vérifier si la classe d'objet retourné était comme prévu. C'est facile quand un objet simple revient, mais que faire quand il est emballé dans un décorateur?

Je code en PHP afin que je puisse utiliser ext/Reflectionpour trouver une classe d'objet enveloppé, mais il me semblait être trop compliqué et quelque peu contre les règles de TDD.

Au lieu de cela, j'ai décidé d'introduire getClassName()qui retournerait le nom de classe de l'objet lorsqu'il était appelé à partir de StrategyClass. Cependant, lorsqu'il est appelé depuis le décorateur, il renvoie la valeur retournée par la même méthode dans l'objet décoré.

Un code pour le rendre plus clair:

interface StrategyInterface {
  public function getClassName();
}

abstract class StrategyClass implements StrategyInterface {
  public function getClassName() {
    return \get_class($this);
  }
}

abstract class StrategyDecorator implements StrategyInterface {

  private $decorated;

  public function __construct(StrategyClass $decorated) {
    $this->decorated = $decorated;
  }

  public function getClassName() {
    return $this->decorated->getClassName();
  }

}

Et un test PHPUnit

 /**
   * @dataProvider providerForTestGetStrategy
   * @param array $arguments
   * @param string $expected
   */
  public function testGetStrategy($arguments, $expected) {

    $this->assertEquals(
      __NAMESPACE__.'\\'.$expected,  
      $this->object->getStrategy($arguments)->getClassName()
    )
  }

//below there's another test to check if proper decorator is being used

Mon point est le suivant: est-il acceptable d'introduire de telles méthodes, qui n'ont d'autre utilité que de faciliter les tests unitaires? D'une certaine manière, cela ne me semble pas juste.


Peut-être que cette question jettera plus d'informations sur votre question, programmers.stackexchange.com/questions/231237/… , je crois que cela dépend de l'utilisation et si les méthodes aideront grandement au test unitaire et au débogage de n'importe quelle application que vous développez. .
Clément Mark-Aaba

Réponses:


13

Ma pensée est non, vous ne devriez rien faire uniquement parce que c'est nécessaire pour la testabilité. Un grand nombre de décisions que les gens prennent profitent à la testabilité et la testabilité peut même être le principal avantage, mais cela devrait être une bonne décision de conception sur d'autres mérites. Cela signifie que certaines propriétés souhaitées ne sont pas testables. Un autre exemple est lorsque vous devez savoir à quel point une routine est efficace, par exemple votre Hashmap utilise-t-il une plage de valeurs de hachage uniformément répartie - rien dans l'interface externe ne pourrait vous le dire.

Au lieu de penser, "est-ce que je reçois la bonne classe de stratégie" pense que "la classe que je reçois exécute ce que cette spécification essaie de tester?" C'est bien quand vous pouvez tester la plomberie interne mais ce n'est pas nécessaire, testez simplement le bouton et voyez si vous obtenez de l'eau chaude ou froide.


+1 OP décrit les tests unitaires en boîte claire, pas les tests de fonctionnalité TDD
Steven A. Lowe

1
Je vois le point avant, même si je suis un peu réticent à ajouter des tests d'algorithmes StrategyClass quand je veux tester si la méthode d'usine fait son travail. Ce type de rupture de l'isolement à mon humble avis. Une autre raison pour laquelle je veux éviter cela est que ces classes particulières fonctionnent sur la base de données, donc les tester nécessite un mocking / stubbing supplémentaire.
Mchl

D'un autre côté et à la lumière de cette question: programmers.stackexchange.com/questions/86656/… lorsque nous distinguons les "tests TDD" des "tests unitaires", cela devient parfaitement bien (toujours la plomberie de la base de données cependant: P)
Mchl

Si vous ajoutez les méthodes, elles font partie du contrat avec vos utilisateurs. Vous vous retrouverez avec des codeurs qui appellent vos fonctions de test uniquement et se branchent en fonction des résultats. En général, je préfère exposer le moins possible de la classe.
BillThor

5

Mon avis à ce sujet est - parfois, vous devez retravailler un peu votre code source pour le rendre plus testable. Ce n'est pas idéal et ne devrait pas être une excuse pour encombrer l'interface avec des fonctions qui ne sont censées être utilisées que pour les tests, donc la modération est généralement la clé ici. Vous ne voulez pas non plus être dans la situation où les utilisateurs de votre code utilisent soudainement les fonctions d'interface de test pour des interactions normales avec votre objet.

Ma façon préférée de gérer cela (et je dois m'excuser de ne pas pouvoir montrer comment le faire en PHP car je code principalement dans des langages de style C) est de fournir les fonctions de test d'une manière qui ne le sont pas. exposés au monde extérieur par l'objet lui-même, mais accessibles par des objets dérivés. À des fins de test, je dériverais alors une classe qui gérerait l'interaction avec l'objet que je veux réellement tester et demander au test unitaire d'utiliser cet objet particulier. Un exemple C ++ ressemblerait à ceci:

Classe de type de production:

class ProdObj
{
  ...
  protected:
     virtual bool checkCertainState();
};

Classe d'aide au test:

class TestHelperProdObj : public ProdObj
{
  ...
  public:
     virtual bool checkCertainState() { return ProdObj::checkCertainState(); }
};

De cette façon, vous êtes au moins dans une position où vous n'avez pas à exposer les fonctions de type «test» dans votre objet principal.


Une approche intéressante. J'aurais besoin de voir comment je pourrais l'adapter
Mchl

3

Il y a quelques mois, lorsque j'ai placé mon lave-vaisselle nouvellement acheté, beaucoup d'eau sortait de son tuyau, j'ai réalisé que c'était probablement dû au fait qu'il avait été correctement testé dans l'usine d'où il venait. Il n'est pas rare de voir des trous de montage et des trucs sur des machines qui ne sont là que pour des tests dans une chaîne de montage.

Les tests sont importants, si besoin est, ajoutez simplement quelque chose pour cela.

Mais essayez quelques-unes des alternatives. Votre option basée sur la réflexion n'est pas si mauvaise. Vous pouvez avoir un accesseur virtuel protégé pour ce dont vous avez besoin et créer une classe dérivée pour tester et confirmer. Vous pouvez peut-être diviser votre classe et tester directement une classe feuille résultante. Cacher la méthode de test avec une variable de compilation dans votre code source est également une option (je connais à peine PHP, je ne sais pas si c'est possible en PHP).

Dans votre contexte, vous pouvez décider de ne pas tester la bonne composition au sein du décorateur, mais de tester le comportement attendu de la décoration. Cela mettrait peut-être un peu plus l'accent sur le comportement attendu du système et non pas tant sur les spécifications techniques (qu'est-ce que le motif décorateur vous achète d'un point de vue fonctionnel?).


1

Je suis un débutant absolu en TDD, mais cela semble dépendre de la méthode ajoutée. D'après ce que je comprends de TDD, vos tests sont censés "piloter" la création de votre API dans une certaine mesure.

Quand c'est OK:

  • Tant que la méthode ne rompt pas l'encapsulation et sert un objectif conforme à la responsabilité de l'objet.

Quand ce n'est pas OK:

  • Si la méthode semble être quelque chose qui ne serait jamais utile ou n'a aucun sens par rapport à d'autres méthodes d'interface, elle peut être incohérente ou déroutante. À ce stade, cela brouillerait ma compréhension de cet objet.
  • L'exemple de Jeremy, "... quand vous avez besoin de savoir à quel point une routine est efficace, par exemple votre Hashmap utilise une plage de valeurs de hachage uniformément répartie - rien dans l'interface externe ne pourrait vous le dire."
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.