Je suis relativement nouveau dans le monde des tests unitaires, et j'ai juste décidé d'ajouter une couverture de test pour mon application existante cette semaine.
C'est une tâche énorme, principalement en raison du nombre de classes à tester mais aussi parce que l'écriture de tests est nouvelle pour moi.
J'ai déjà écrit des tests pour un tas de classes, mais maintenant je me demande si je le fais bien.
Lorsque j'écris des tests pour une méthode, j'ai l'impression de réécrire une deuxième fois ce que j'ai déjà écrit dans la méthode elle-même.
Mes tests semblent tellement liés à la méthode (tester tous les chemins de code, en attendant que certaines méthodes internes soient appelées un certain nombre de fois, avec certains arguments), il semble que si je refactorise la méthode, les tests échoueront même si le le comportement final de la méthode n'a pas changé.
C'est juste un sentiment, et comme dit plus haut, je n'ai aucune expérience des tests. Si des testeurs plus expérimentés pouvaient me donner des conseils sur la façon d'écrire de bons tests pour une application existante, ce serait grandement apprécié.
Edit: Je serais ravi de remercier Stack Overflow, j'ai eu d'excellentes contributions en moins de 15 minutes qui ont répondu à plus d'heures de lecture en ligne que je viens de faire.
la source
Réponses:
Je pense que vous vous trompez.
Un test unitaire doit:
Il ne doit pas regarder à l'intérieur de la méthode pour voir ce qu'elle fait, donc la modification des internes ne doit pas entraîner l'échec du test. Vous ne devez pas tester directement que des méthodes privées sont appelées. Si vous souhaitez savoir si votre code privé est testé, utilisez un outil de couverture de code. Mais ne soyez pas obsédé par cela: une couverture à 100% n'est pas une exigence.
Si votre méthode appelle des méthodes publiques dans d'autres classes et que ces appels sont garantis par votre interface, vous pouvez tester que ces appels sont effectués à l'aide d'un framework de simulation.
Vous ne devez pas utiliser la méthode elle-même (ou l'un des codes internes qu'elle utilise) pour générer dynamiquement le résultat attendu. Le résultat attendu doit être codé en dur dans votre scénario de test afin qu'il ne change pas lorsque l'implémentation change. Voici un exemple simplifié de ce que devrait faire un test unitaire:
Notez que la façon dont le résultat est calculé n'est pas vérifiée - seulement que le résultat est correct. Continuez à ajouter des cas de test de plus en plus simples comme ci-dessus jusqu'à ce que vous ayez couvert autant de scénarios que possible. Utilisez votre outil de couverture de code pour voir si vous avez manqué des chemins intéressants.
la source
Pour les tests unitaires, j'ai trouvé que Test Driven (tests en premier, code en second) et code en premier, test en second lieu étaient extrêmement utiles.
Au lieu d'écrire du code, puis d'écrire test. Écrivez le code, puis regardez ce que vous pensez que le code devrait faire. Pensez à toutes les utilisations prévues et écrivez un test pour chacune. Je trouve que les tests d'écriture sont plus rapides mais plus complexes que le codage lui-même. Les tests devraient tester l'intention. Pensez également aux intentions que vous finissez par trouver des cas d'angle dans la phase d'écriture du test. Et bien sûr, lors de l'écriture de tests, l'une des rares utilisations peut provoquer un bogue (quelque chose que je trouve souvent, et je suis très heureux que ce bogue n'ait pas corrompu les données et ne soit pas contrôlé).
Pourtant, tester est presque comme coder deux fois. En fait, j'avais des applications où il y avait plus de code de test (quantité) que de code d'application. Un exemple était une machine d'état très complexe. Je devais m'assurer qu'après y avoir ajouté plus de logique, le tout fonctionnait toujours sur tous les cas d'utilisation précédents. Et comme ces cas étaient assez difficiles à suivre en regardant le code, j'ai fini par avoir une si bonne suite de tests pour cette machine que j'étais confiant qu'elle ne casserait pas même après avoir fait des changements, et les tests m'ont sauvé le cul plusieurs fois . Et comme les utilisateurs ou les testeurs trouvaient des bogues avec les cas de flux ou de coin manquants, devinez quoi, ajoutés aux tests et ne se reproduisaient plus jamais. Cela a vraiment donné aux utilisateurs confiance en mon travail en plus de rendre le tout super stable. Et quand il a dû être réécrit pour des raisons de performances, devinez quoi,
Tous les exemples simples
function square(number)
sont excellents et sont probablement de mauvais candidats pour passer beaucoup de temps à tester. Ceux qui font une logique métier importante, c'est là que les tests sont importants. Testez les exigences. Ne vous contentez pas de tester la plomberie. Si les exigences changent, devinez quoi, les tests doivent l'être aussi.Le test ne doit pas littéralement tester cette fonction foo invoquée la barre de fonction 3 fois. C'est faux. Vérifiez si le résultat et les effets secondaires sont corrects, pas la mécanique interne.
la source
Il convient de noter que le réajustement des tests unitaires dans le code existant est beaucoup plus difficile que de conduire la création de ce code avec des tests en premier lieu. C'est l'une des grandes questions dans le traitement des applications héritées ... comment effectuer des tests unitaires? Cela a été posé plusieurs fois auparavant (vous pouvez donc être fermé en tant que question dupe), et les gens se retrouvent généralement ici:
Déplacement du code existant vers Test Driven Development
J'appuie la recommandation du livre de la réponse acceptée, mais au-delà, il y a plus d'informations liées dans les réponses.
la source
N'écrivez pas de tests pour obtenir une couverture complète de votre code. Écrivez des tests qui garantissent vos exigences. Vous pouvez découvrir des chemins de code inutiles. Inversement, s'ils sont nécessaires, ils sont là pour remplir une sorte d'exigence; trouver ce que c'est et tester l'exigence (pas le chemin).
Gardez vos tests petits: un test par exigence.
Plus tard, lorsque vous devez apporter une modification (ou écrire un nouveau code), essayez d'abord d'écrire un test. Juste un. Vous aurez ensuite franchi la première étape du développement piloté par les tests.
la source
Le test unitaire concerne la sortie que vous obtenez d'une fonction / méthode / application. Peu importe comment le résultat est produit, il importe juste qu'il soit correct. Par conséquent, votre approche du comptage des appels aux méthodes internes et autres est fausse. Ce que j'ai tendance à faire, c'est de m'asseoir et d'écrire ce qu'une méthode devrait retourner étant donné certaines valeurs d'entrée ou un certain environnement, puis d'écrire un test qui compare la valeur réelle retournée avec ce que j'ai trouvé.
la source
Essayez d'écrire un test unitaire avant d'écrire la méthode qu'il va tester.
Cela vous forcera certainement à penser un peu différemment à la façon dont les choses se font. Vous n'aurez aucune idée du fonctionnement de la méthode, de ce qu'elle est censée faire.
Vous devez toujours tester les résultats de la méthode, pas comment la méthode obtient ces résultats.
la source
les tests sont censés améliorer la maintenabilité. Si vous changez une méthode et un test, cela peut être une bonne chose. D'un autre côté, si vous regardez votre méthode comme une boîte noire, peu importe ce qui se trouve à l'intérieur de la méthode. Le fait est que vous devez vous moquer des choses pour certains tests, et dans ces cas, vous ne pouvez vraiment pas traiter la méthode comme une boîte noire. La seule chose que vous pouvez faire est d'écrire un test d'intégration - vous chargez une instance entièrement instanciée du service testé et faites-le faire comme il le ferait dans votre application. Ensuite, vous pouvez le traiter comme une boîte noire.
C'est parce que vous écrivez vos tests après avoir écrit votre code. Si vous le faisiez dans l'autre sens (vous avez d'abord écrit les tests), ce ne serait pas le cas.
la source