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/Reflection
pour 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.
Réponses:
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.
la source
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:
Classe d'aide au test:
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.
la source
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?).
la source
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:
Quand ce n'est pas OK:
la source