J'ai une compréhension de base des objets fictifs et factices, mais je ne suis pas sûr de savoir quand et où se moquer, surtout dans le cas où cela s'appliquerait ici .
Réponses
Trop de publicités?Les objets fantaisie sont utiles lorsque vous souhaitez tester les interactions entre une classe sous test et une interface particulière.
Par exemple, nous voulons tester cette méthode sendInvitations(MailServer mailServer)
des appels MailServer.createMessage()
exactement une fois, et aussi des appels MailServer.sendMessage(m)
exactement une fois, et pas d'autres méthodes sont appelées sur l' MailServer
interface. C'est quand nous pouvons utiliser les objets fantaisie.
Avec des objets fantaisie, au lieu de passer un réel MailServerImpl
, ou un test TestMailServer
, nous pouvons passer d'un simulacre de mise en œuvre de l' MailServer
interface. Avant de passer à un simulacre d' MailServer
, nous "former", de sorte qu'il sait ce que les appels de méthode à attendre et que les valeurs de retour de retour. À la fin, l'objet fantaisie affirme, que de toutes les méthodes ont été appelés comme prévu.
Cela sonne bien en théorie, mais il ya aussi quelques inconvénients.
Se moquer des lacunes
Si vous avez un simulacre de cadre en place, vous êtes tentés d'utiliser l'objet fantaisie à chaque fois que vous avez besoin pour passer d'une interface à la classe sous test. De cette façon, en fin de test des interactions, même quand il n'est pas nécessaire. Malheureusement, indésirables (accidentelle) tests des interactions est mauvais, parce que vous êtes des tests qu'une exigence particulière est mise en œuvre, en particulier, plutôt que la mise en œuvre a produit le résultat voulu.
Voici un exemple de pseudo-code. Supposons que nous avons créé un MySorter
de la classe et nous voulons tester:
// the correct way of testing
testSort() {
testList = [1, 7, 3, 8, 2]
MySorter.sort(testList)
assert testList equals [1, 2, 3, 7, 8]
}
// incorrect, testing implementation
testSort() {
testList = [1, 7, 3, 8, 2]
MySorter.sort(testList)
assert that compare(1, 2) was called once
assert that compare(1, 3) was not called
assert that compare(2, 3) was called once
....
}
(Dans cet exemple, nous supposons que ce n'est pas un particulier algorithme de tri, comme le tri rapide, que nous voulons tester; dans ce cas, le dernier test serait valide.)
Dans un tel exemple extrême, il est évident pourquoi le second exemple est mauvais. Lors du changement de la mise en œuvre de l' MySorter
, le premier test fait un excellent travail de faire en sorte que nous encore trier correctement, ce qui est le point de l'ensemble de tests - ils nous permettre de changer le code de sécurité. D'autre part, le dernier test toujours les pauses et il est activement nuisibles; elle entrave le refactoring.
Se moque comme les talons de
Cadres fictifs permettent souvent aussi moins strictes d'utilisation, où nous n'avons pas de préciser exactement combien de fois les méthodes et quels paramètres sont prévus; ils permettent de créer des simulacres d'objets qui sont utilisés comme des talons.
Supposons que nous avons une méthode sendInvitations(PdfFormatter pdfFormatter, MailServer mailServer)
que nous voulons tester. L' PdfFormatter
objet peut être utilisé pour créer une invitation. Voici le test:
testInvitations() {
// train as stub
pdfFormatter = create mock of PdfFormatter
let pdfFormatter.getCanvasWidth() returns 100
let pdfFormatter.getCanvasHeight() returns 300
let pdfFormatter.addText(x, y, text) returns true
let pdfFormatter.drawLine(line) does nothing
// train as mock
mailServer = create mock of MailServer
expect mailServer.sendMail() called exactly once
// do the test
sendInvitations(pdfFormatter, mailServer)
assert that all pdfFormatter expectations are met
assert that all mailServer expectations are met
}
Dans cet exemple, nous n'avons pas vraiment se soucier de l' PdfFormatter
objet afin que nous venons de former tranquillement accepter l'appel et revenir quelques raisonnable en conserve les valeurs de retour pour toutes les méthodes qu' sendInvitation()
arrive à appeler à ce point. Comment avons-nous arriver à exactement cette liste de méthodes pour former? Nous avons simplement lancé le test et continué à ajouter des méthodes jusqu'à ce que le test réussi. Un avis, que nous avons formé le talon pour répondre à une méthode sans avoir la moindre idée de pourquoi il a besoin de l'appeler, nous avons simplement ajouté tout ce que le test de la plainte. Nous sommes heureux, le test passe.
Mais ce qui se passe plus tard, quand nous changeons sendInvitations()
, ou une autre classe qui sendInvitations()
utilise, pour créer plus de fantaisie fichiers pdf? Notre test soudain échoue parce que maintenant, en plus de méthodes d' PdfFormatter
sont appelés et nous n'avons pas la formation de notre talon d'attendre d'eux. Et, habituellement, il n'est pas seulement un test qui échoue dans des situations de ce genre, c'est un test qui arrive à utiliser, directement ou indirectement, l' sendInvitations()
méthode. Nous avons à fixer tous ces tests en ajoutant plus de formations. Notez également, que nous ne pouvons pas supprimer les méthodes ne sont plus nécessaires, car nous ne savons pas lequel d'entre eux ne sont pas nécessaires. Encore une fois, c'est une entrave à la refactorisation.
Aussi, la lisibilité de test ont terriblement souffert, il y a beaucoup de code que nous n'avons pas écrire parce qu'il le voulait, mais parce que nous avons; c'est pas nous qui veulent que le code. Les Tests qui utilisent les objets fantaisie look très complexes et sont souvent difficiles à lire. Les essais doivent aider le lecteur à comprendre, comment la classe sous test doit être utilisé, donc ils doivent être simples et clairs. Si elles ne sont pas lisibles, personne ne va les maintenir; en fait, il est plus facile de les supprimer que de les maintenir.
Comment résoudre ce problème? Facilement:
- Essayez de l'utilisation réelle des classes au lieu de se moque autant que possible. Utilisez le réel
PdfFormatterImpl
. Si il n'est pas possible de modifier le réel classes pour rendre cela possible. Ne pas être en mesure d'utiliser une classe dans les tests de la montre généralement à quelques problèmes avec la classe. La résolution des problèmes est une situation gagnant-gagnant - vous fixe de la classe et que vous avez un test plus simple. D'autre part, de ne pas le fixer et se moque de l'aide est un no-win situation - vous n'avez pas de fixer le réel de la classe et que vous avez plus complexe, moins lisible tests d'entraver la poursuite des refactorings. - Essayez de créer un test simple de mise en œuvre de l'interface au lieu de se moquer de lui dans chaque test, et l'utilisation de cette classe de test dans tous vos tests. Créer
TestPdfFormatter
qui ne fait rien. De cette façon, vous pouvez modifier en une seule fois pour tous les tests et vos tests ne sont pas encombré avec de longues configurations où vous vous entraînez vos talons.
Dans l'ensemble, les objets fantaisie ont leur utilité, mais lorsqu'il n'est pas utilisé avec soin, ils encouragent souvent les mauvaises pratiques, la mise en œuvre des tests de détails, entravent le refactoring et de produire difficile à lire et difficile à maintenir tests.
Pour plus de détails sur les défaillances de se moque de voir aussi les Objets Fantaisie: des Lacunes et des Cas d'Utilisation.
Un test unitaire doit tester une seule codepath grâce à une méthode unique. Lors de l'exécution d'une méthode passe en dehors de cette méthode, dans un autre objet, et de retour à nouveau, vous avez une dépendance.
Lorsque vous testez le code de la trajectoire avec le réel de la dépendance, vous n'êtes pas les tests unitaires; vous êtes des tests d'intégration. Voilà qui est bien et nécessaire, il n'est pas de tests unitaires.
Si votre dépendance est buggy, votre test peut être affectée de manière à revenir à un faux positif. Par exemple, vous pouvez passer de la dépendance à l'inattendu de la valeur null, et la dépendance ne peut pas jeter sur la valeur null comme il est documenté à faire. Votre test ne permet pas de enounter un argument null exception comme il se doit, et le test passe.
On peut également trouver sa dur, voire impossible, d'obtenir de manière fiable l'objet dépendant de revenir exactement ce que vous voulez pendant un test. Qui comprend également jeter prévu des exceptions dans les tests.
Une maquette remplace celui de la dépendance. Vous définissez les attentes sur les appels à la charge de l'objet, de définir exactement les valeurs de retour, il devrait vous donner pour effectuer le test que vous voulez, et/ou quelles sont les exceptions à jeter de sorte que vous pouvez tester votre code de gestion des exceptions. De cette façon, vous pouvez tester l'appareil en question facilement.
TL;DR: se Moquer de toutes les dépendances de votre unité de test de touche.
Vous devriez maquette d'un objet lorsque vous avez une dépendance à une unité de code que vous essayez de test qui doit être "juste".
Par exemple, lorsque vous essayez de tester un peu de logique dans votre unité de code, mais vous avez besoin pour obtenir quelque chose à partir d'un autre objet, et quel est retourné à partir de cette dépendance peut affecter ce que vous essayez de tester le simulacre de l'objet.
Un excellent podcast sur le sujet peuvent être trouvées ici
Recherchez l' article (pdf) des programmeurs pragmatiques pour une bonne discussion sur le moment de se moquer.