Je viens de lire un extrait du livre "Growing Object-Oriented Software" qui explique certaines raisons pour lesquelles se moquer de la classe concrète n'est pas recommandé.
Voici un exemple de code d'un test unitaire pour la classe MusicCentre:
public class MusicCentreTest {
@Test public void startsCdPlayerAtTimeRequested() {
final MutableTime scheduledTime = new MutableTime();
CdPlayer player = new CdPlayer() {
@Override
public void scheduleToStartAt(Time startTime) {
scheduledTime.set(startTime);
}
}
MusicCentre centre = new MusicCentre(player);
centre.startMediaAt(LATER);
assertEquals(LATER, scheduledTime.get());
}
}
Et sa première explication:
Le problème avec cette approche est qu'elle laisse implicitement la relation entre les objets. J'espère que nous avons maintenant clairement indiqué que l'intention du développement piloté par les tests avec des objets fantômes est de découvrir les relations entre les objets. Si je sous-classe, il n'y a rien dans le code de domaine pour rendre une telle relation visible, juste des méthodes sur un objet. Cela rend plus difficile de voir si le service qui prend en charge cette relation pourrait être pertinent ailleurs et je devrai refaire l'analyse la prochaine fois que je travaillerai avec la classe.
Je ne peux pas comprendre exactement ce qu'il veut dire quand il dit:
Cela rend plus difficile de voir si le service qui prend en charge cette relation pourrait être pertinent ailleurs et je devrai refaire l'analyse la prochaine fois que je travaillerai avec la classe.
Je comprends que le service correspond à MusicCentre
la méthode appelée startMediaAt
.
Que veut-il dire par «ailleurs»?
L'extrait complet est ici: http://www.mockobjects.com/2007/04/test-smell-mocking-concrete-classes.html
Réponses:
L'auteur de ce message encourage l'utilisation des interfaces par rapport à l'utilisation des classes membres.
It turns out that my MusicCentre object only uses the starting and stopping methods on the CdPlayer, the rest are used by some other part of the system. I'm over-specifying my MediaCentre by requiring it to talk to a CdPlayer, what it actually needs is a ScheduledDevice.
La relation qu'il redoute de découvrir plus tard est le fait que la classe MediaCentre n'a pas besoin de tout l'objet CdPlayer. Son affirmation est qu'en utilisant une interface (vraisemblablement limitée à simplement démarrer | arrêter), il est plus facile de comprendre ce qu'est réellement l'interaction.
"ailleurs" signifie simplement que d'autres objets peuvent avoir des relations également limitées, et que l'objet membre complet n'est pas requis - un sous-ensemble de la fonctionnalité enveloppée via une interface devrait être suffisant.
La revendication commence à avoir plus de sens lorsque vous explosez toutes les fonctionnalités potentielles:
Maintenant, son affirmation "J'ai juste besoin de démarrer et d'arrêter" a plus de sens. L'utilisation de l'objet membre concret au lieu d'une interface rend moins clair pour les futurs développeurs ce qui est vraiment nécessaire. L'exécution de tests unitaires à partir de MediaCentre sur toutes les autres fonctions de CdPlayer est un gaspillage d'efforts de test car ils appartiennent à l'état "indifférent". Si la
Record
fonction ne fonctionnait pas dans ce cas, nous ne nous en soucions vraiment pas car elle n'est pas requise. Mais un futur responsable ne saurait pas nécessairement cela sur la base du code, tel qu'il est écrit.En fin de compte, la prémisse de l'auteur est de n'utiliser que ce qui est nécessaire et de faire clairement comprendre aux futurs responsables ce qui était nécessaire auparavant. L'objectif est de minimiser les retouches / réanalyses du module de code lors de la maintenance ultérieure.
la source
Après y avoir beaucoup réfléchi, j'obtiens une interprétation possible de cette citation:
Le "service" cité correspond au "fait de l'ordonnancement". Cela pourrait être exprimé par une interface bien nommée et "concentrée sur un seul rôle" nommée "ScheduledDevice" ou exprimé implicitement par une implémentation de méthode concrète ne dépendant d'aucune interface.
Dans l'exemple ci-dessus, la planification est exprimée par l'ensemble de l'objet complet nommé
CDPlayer
. Ainsi, cela conduit toujours à une relation implicite entreMusicCentre
et "fait de programmation".Donc, si nous commençons à injecter des classes concrètes et à les moquer dans des objets de haut niveau; lorsque nous voulons tester ceux-ci, nous devons analyser chaque objet "concret" injecté pour voir s'il présente une relation spécifique que nous DEVONS MOCKER car ils sont CACHÉS (implicites). Au contraire, le codage TOUJOURS sur l'interface permet au développeur de déterminer directement quel type de relation est sur le point d'être servi par l'objet de haut niveau et donc de détecter les fonctionnalités qui doivent être moquées afin d'isoler le test unitaire.
la source
Le service que je voulais dire ici était CDPlayer.scheduleToStartAt (). C'est ce que le MediaCentre appelle - le collaborateur dont il a besoin pour fonctionner. Le MediaCentre est l'objet à tester.
L'idée est que si je précise exactement ce dont dépend le MediaCentre, et non une classe d'implémentation, je peux donner un nom à ce rôle de dépendance et en parler. Tout ce que le MediaCentre doit savoir, c'est qu'il parle à ScheduledDevices. Comme le reste du système change, je n'aurai pas besoin de modifier le MediaCentre à moins que ses fonctionnalités ne changent.
Est ce que ça aide?
la source