Comment devenir meilleur pour tester votre propre code


45

Je suis un développeur de logiciel relativement nouveau, et l'une des choses que je pense devoir améliorer est ma capacité à tester mon propre code. Chaque fois que je développe une nouvelle fonctionnalité, il m'est vraiment difficile de suivre tous les chemins possibles pour trouver des bogues. J'ai tendance à suivre le chemin où tout fonctionne. Je sais que c'est un problème bien connu des programmeurs, mais nous n'avons pas de testeurs chez mon employeur actuel et mes collègues semblent être plutôt bons dans ce domaine.

Dans mon entreprise, nous ne faisons ni développement piloté par les tests, ni tests unitaires. Cela m'aiderait beaucoup mais il est peu probable que cela change.

Que pensez-vous que je pourrais faire pour surmonter cela? Quelle approche utilisez-vous pour tester votre propre code?


28
Ce n’est pas parce que votre entreprise n’a pas recours aux tests TDD ou aux tests unitaires que vous ne pouvez pas, tant que vous continuez de respecter vos délais et de produire un code de qualité.
Thomas Owens

1
Je suppose que Thomas m'a battu, mais je suis dans une situation similaire. J'écris des attentes très élevées sur ce qu'un changement de code devrait faire, et j'écris des tests unitaires si ou quand je le peux (même si notre société ne fait pas officiellement de tests unitaires). Vous n'avez pas à les commettre, et ils constituent d'excellents moyens d'apprendre comment les fonctions sont censées agir (ou devraient agir après que vous les ayez corrigées).
Brian

6
@ Brian, je pense que vous devriez les engager, que d'autres les utilisent ou non. Peut-être que montrer de bonnes pratiques incitera d’autres à suivre.
CaffGeek

Réponses:


20

Le travail d'un codeur est de construire des choses.

Le travail d'un testeur est de casser des choses.

Le plus difficile est de casser des choses que vous venez de construire. Vous ne réussirez qu'en franchissant cette barrière psychologique.


27
-1 Le travail d'un codeur est de construire des choses qui fonctionnent . Cela implique toujours un certain nombre de tests. Je conviens qu'il est nécessaire de disposer d'un rôle de testeur séparé, mais ce n'est pas la seule ligne de défense.
Matthew Rodatus

8
@ Matthew Rodatus - Le nombre de tests requis du côté du codeur vise uniquement à vérifier que ce qui devrait fonctionner fonctionne réellement. Du côté des testeurs, l’objectif est de rechercher des bogues, et non de constater que le code fonctionne.
mouviciel

2
C'est différent de votre réponse, et je suis plus d'accord avec cela. Mais je ne suis toujours pas entièrement d'accord. Écrire un code de qualité passe par la pratique, car vous apprenez à penser à l'avance aux possibilités d'échec. On n'apprend pas à envisager les possibilités d'échec sans apprendre à créer ces possibilités. Je ne pense pas que les codeurs devraient être la seule ligne de défense, mais ils devraient être la première ligne de défense. L'enjeu est de rigueur et de maîtrise du métier.
Matthew Rodatus

2
@mouviciel - fausse dichotomie. Le travail du codeur est de construire des choses qui fonctionnent, et il le fait en pensant a priori dans quelles conditions son code est censé fonctionner. Et ceci est vérifié, à tout le moins, en créant un test destructif + quelques analyses de limites ad-hoc (encore une fois, au moins ). De plus, un bon codeur fonctionne par rapport aux spécifications et les spécifications (lorsqu'elles sont valides) sont toujours testables. Donc, un bon codeur développe des tests qui vérifient que ces exigences sont bien respectées dans le code (et vous le faites généralement en écrivant des tests requis qui échouent initialement jusqu'à ce que le code passe les tests avec
succès

2
@ Dave Lasley - C'est précisément ce que je veux dire: l'architecte n'est pas la meilleure personne pour assommer sa maison: il est trop fier de la force avec laquelle il est capable de voir ses défauts. Seul un autre architecte (pas le gars de la rue) peut porter un regard objectif sur la maison et constater que celle-ci peut casser dans des conditions spécifiques que l'ancien architecte était trop aveugle pour imaginer.
mouviciel

14

Franciso, je vais faire quelques hypothèses ici, sur la base de ce que vous avez dit:

"Nous ne faisons pas de tests TDD ni de tests unitaires. Cela m'aiderait beaucoup, mais il est peu probable que cela change."

À partir de là, j’imagine que votre équipe n’accorde pas beaucoup d’importance aux tests et que la direction ne lui accordera pas de temps pour essayer de ranger le code existant et de réduire au minimum sa dette technique .

Premièrement, vous devez convaincre votre équipe / direction de la valeur des tests. Soyez diplomate. Si la direction encourage votre équipe à aller de l'avant, vous devez leur montrer des faits, tels que le taux de défauts pour chaque version. Le temps passé à réparer les défauts pourrait être mieux utilisé, par exemple pour améliorer l'application et la rendre plus adaptable aux exigences futures.

Si l'équipe et la direction en général sont apathiques à propos de la correction du code et que vous vous sentez mal à l'aise, vous devrez peut-être chercher un autre lieu de travail, à moins de pouvoir les convaincre, comme je l'ai dit. J'ai rencontré ce problème à différents degrés dans tous les endroits où j'ai travaillé. Cela peut aller de l'absence d'un modèle de domaine approprié à une mauvaise communication au sein de l'équipe.

Prendre soin de votre code et de la qualité du produit que vous développez est un bon attribut que vous voulez toujours encourager chez les autres.


11

Si vous codez en C, Objective-C ou C ++, vous pouvez utiliser CLang Static Analyzer pour critiquer votre source sans l'exécuter.

Certains outils de débogage de mémoire sont disponibles: ValGrind, Guard Malloc sur Mac OS X, Electric Fence sur * NIX.

Certains environnements de développement offrent la possibilité d’utiliser un allocateur de mémoire de débogage qui remplit des tâches telles que le remplissage des pages nouvellement allouées et libérées, la détection de la libération des pointeurs non alloués et l’écriture de certaines données avant et après chaque bloc appelé si le modèle connu de ces données change jamais.

Un gars de Slashdot a déclaré qu'il tirait beaucoup de profit d'une nouvelle source de données à débogage unique dans un débogueur. "C'est ça" dit-il. Je ne suis pas toujours ses conseils, mais ceux-ci m'ont été très utiles. Même si vous n'avez pas de scénario de test stimulant un chemin de code peu commun, vous pouvez modifier une variable de votre débogueur pour prendre de tels chemins, en allouant de la mémoire, puis en utilisant le débogueur pour définir votre nouveau pointeur sur NULL au lieu du paramètre. adresse de mémoire, puis en passant par le gestionnaire d’échec d’allocation.

Utilisez des assertions - la macro assert () en C, C ++ et Objective-C. Si votre langue ne fournit pas de fonction d'assertion, écrivez-en une vous-même.

Utilisez des assertions généreusement, puis laissez-les dans votre code. J'appelle assert () "Le test qui continue à tester". Je les utilise le plus souvent pour vérifier les conditions préalables au point d'entrée de la plupart de mes fonctions. C'est une partie de "Programmation par contrat", qui est intégrée au langage de programmation Eiffel. L’autre partie concerne postconditions, c’est-à-dire assert () à des points de retour de fonction, mais j’aperçois que je n’en retire pas autant de kilométrage que de conditions préalables.

Vous pouvez également utiliser assert pour vérifier les invariants de classe. Bien qu'aucune classe ne soit strictement requise pour avoir un invariant, les classes les plus judicieusement conçues les ont. Un invariant de classe est une condition toujours vraie autre que celle contenue dans les fonctions membres pouvant placer temporairement votre objet dans un état incohérent. De telles fonctions doivent toujours restaurer la cohérence avant leur retour.

Ainsi, chaque fonction membre peut vérifier l'invariant à l'entrée et à la sortie et la classe peut définir une fonction appelée CheckInvariant que tout autre code pourrait appeler à tout moment.

Utilisez un outil de couverture de code pour vérifier quelles lignes de votre source sont effectivement testées, puis concevez des tests qui stimulent les lignes non testées. Par exemple, vous pouvez vérifier les gestionnaires de mémoire insuffisante en exécutant votre application dans une machine virtuelle configurée avec peu de mémoire physique et sans fichier d'échange, ni très petit.

(Pour une raison quelconque, je n'étais jamais au courant, alors que BeOS pouvait fonctionner sans fichier d'échange, il était extrêmement instable de cette façon. Dominic Giampaolo, qui a écrit le système de fichiers BFS, m'a exhorté à ne jamais exécuter le BeOS sans échange. Je ne voyez pourquoi cela devrait être important, mais il doit s'agir d'une sorte d'artefact d'implémentation.)

Vous devez également tester la réponse de votre code aux erreurs d'E / S. Essayez de stocker tous vos fichiers sur un partage réseau, puis débranchez votre câble réseau pendant que votre application a une charge de travail élevée. De même, débranchez le câble - ou éteignez votre réseau sans fil - si vous communiquez sur un réseau.

Les sites Web qui ne disposent pas de code Javascript robuste sont particulièrement irritants. Les pages de Facebook chargent des dizaines de petits fichiers Javascript, mais si l'un d'entre eux ne parvient pas à télécharger, la page entière se brise. Il doit simplement exister un moyen de fournir une certaine tolérance aux pannes, par exemple en réessayant un téléchargement, ou de fournir une sorte de solution de secours raisonnable lorsque certains de vos scripts ne se sont pas téléchargés.

Essayez de tuer votre application avec le débogueur ou avec "kill -9" sur * NIX alors qu'il est en train d'écrire un gros fichier important. Si votre application est bien architecturée, l'intégralité du fichier sera écrit ou ne sera pas écrit du tout, ou peut-être s'il n'est que partiellement écrit, ce qui sera écrit ne sera pas corrompu, avec quelles données sauvegardées seront complètement utilisables par l'application lors de la relecture du fichier.

les bases de données ont toujours des E / S de disque tolérantes aux pannes, mais pratiquement aucun autre type d'application ne le fait. Bien que les systèmes de fichiers journalisés empêchent la corruption du système de fichiers en cas de panne de courant ou de panne, ils ne font rien du tout pour empêcher la corruption ou la perte des données de l'utilisateur final. C’est la responsabilité des applications utilisateur, mais pratiquement aucune autre base de données que la base de données n’implémente la tolérance aux pannes.


1
+1 beaucoup de conseils pratiques qui n'ont pas besoin de l'aide de quelqu'un d'autre. La seule chose que j’ajouterais, c’est que assert sert à documenter et à vérifier les conditions qui ne peuvent échouer que s’il ya un bogue dans le code . N'affirmez jamais des choses qui pourraient échouer pour cause de "malchance", telles qu'un fichier essentiel introuvable, des entrées non valides, etc.
Ian Goldby

10

Lorsque je teste mon code, je passe généralement par une série de processus de pensée:

  1. Comment puis-je décomposer ce "truc" en une taille testable? Comment puis-je isoler ce que je veux tester? Quels stubs / mocs dois-je créer?
  2. Pour chaque morceau: Comment puis-je tester ce morceau pour m'assurer qu'il répond correctement à un ensemble raisonnable d'entrées correctes?
  3. Pour chaque morceau: Comment vérifier que le morceau répond correctement aux entrées incorrectes (pointeurs NULL, valeurs non valides)?
  4. Comment tester les limites (par exemple, où les valeurs passent de signé à non signé, de 8 bits à 16 bits, etc.)?
  5. Dans quelle mesure mes tests couvrent-ils le code? Y a-t-il des conditions que j'ai manquées? [C'est un excellent endroit pour les outils de couverture de code.] S'il y a du code qui a été manqué et qui ne peut jamais être exécuté, faut-il vraiment qu'il soit là? [C'est une toute autre question!]

Le moyen le plus simple que j’ai trouvé est de développer mes tests avec mon code. Dès que j'ai même écrit un fragment de code, j'aime bien écrire un test. Essayer de faire tous les tests après avoir codé plusieurs milliers de lignes de code avec une complexité de code cyclomatique non triviale est un cauchemar. Ajouter un ou deux tests supplémentaires après quelques lignes de code est très simple.

BTW, ce n'est pas parce que votre entreprise et / ou vos collègues ne font pas de tests unitaires ou de tests TDD que vous ne pouvez pas les essayer, à moins que cela ne soit expressément interdit. Peut-être que les utiliser pour créer du code robuste sera un bon exemple pour les autres.


5

En plus des conseils donnés dans les autres réponses, je suggérerais d'utiliser des outils d' analyse statiques (Wikipedia possède une liste de plusieurs outils d'analyse statique pour différentes langues ) afin de rechercher les défauts potentiels avant le début des tests et de surveiller certains paramètres liés à la testabilité de code, telle que la complexité cyclomatique , les mesures de complexité de Halstead , ainsi que la cohésion et le couplage (vous pouvez les mesurer avec fan-in et fan-out).

Trouver le code difficile à tester et le rendre plus facile à tester vous facilitera la rédaction de scénarios de test. En outre, la détection précoce des défauts ajoutera de la valeur à l'ensemble de vos pratiques d'assurance de la qualité (y compris les tests). À partir de là, vous familiariser avec les outils de tests unitaires et les outils de moquage vous facilitera la mise en œuvre de vos tests.


1
+1 pour la complexité cyclomatique. "Je trouve vraiment difficile de suivre tous les chemins possibles pour trouver des bogues", ce qui implique que le code de l'OP doit éventuellement être divisé en blocs plus petits et moins complexes.
Toby

@ Toby Oui, c'est pourquoi j'ai décidé de lancer l'analyse statique. Si vous ne pouvez pas tester votre code, vous avez des problèmes. Et si vous avez un problème avec votre code, il y en a peut-être d'autres. Utilisez un outil pour rechercher des indicateurs d'avertissement potentiels, les évaluer et les corriger si nécessaire. Vous aurez non seulement plus de code testable, mais également un code plus lisible.
Thomas Owens

3

Vous pouvez examiner l'utilisation possible des tables de vérité pour vous aider à définir tous les chemins potentiels de votre code. Il est impossible de prendre en compte toutes les possibilités dans les fonctions complexes, mais une fois que vous avez établi votre traitement pour tous les chemins connus, vous pouvez établir un traitement pour le cas contraire.

La plupart de ces capacités particulières sont toutefois apprises par expérience. Une fois que vous avez utilisé un cadre donné pendant un temps considérable, vous commencez à voir les modèles et les marques de comportement qui vous permettront d’examiner un morceau de code et de déterminer dans quels cas un petit changement pourrait provoquer une erreur majeure. Le seul moyen auquel je peux penser pour augmenter vos aptitudes dans ce domaine est la pratique.


3

Si, comme vous l'avez dit, vous n'avez pas besoin de tests unitaires, je ne vois pas de meilleure approche que d'essayer de casser votre propre code manuellement.

Essayez de pousser votre code à la limite . Par exemple, essayez de transmettre des variables à une fonction dépassant les limites. Avez-vous une fonction censée filtrer les entrées de l'utilisateur? Essayez de saisir différentes combinaisons de caractères.

Considérons le point de vue de l' utilisateur . Essayez d’être l’un des utilisateurs qui utiliseront votre application ou votre bibliothèque de fonctions.


1
+1 pour avoir mentionné avoir vu des choses du point de vue de l'utilisateur.
Matthew Rodatus

3

mais nous n'avons pas de testeurs chez mon employeur actuel et mes collègues semblent être assez bons à cet égard

Vos collègues doivent être vraiment exceptionnels pour ne pas suivre TDD ou les tests unitaires et ne jamais générer de bugs, alors à un certain niveau, je doute qu'ils ne réalisent aucun test unitaire eux-mêmes.

Je suppose que vos collègues font plus de tests que prévu, mais comme ce fait n’est pas connu de la direction, l’organisation en pâtit, car la direction a l’impression que les vrais tests ne sont pas effectués et que le nombre de bogues est faible. les tests n'ont pas d'importance et il ne sera pas programmé pour le faire.

Parlez à vos collègues et essayez de déterminer quel type de test unitaire ils font et d'imiter cela. Plus tard, vous pourrez créer un prototype de meilleures méthodes de test unitaire et d'attributs TDD, puis introduire progressivement ces concepts à l'équipe pour une adoption plus facile.


2
  • Ecrivez vos tests avant d'écrire votre code.
  • Chaque fois que vous corrigez un bogue qui n'a pas été détecté par un test, écrivez un test pour détecter ce bogue.

Vous devriez pouvoir obtenir une couverture de ce que vous écrivez même si votre organisation ne dispose pas d'une couverture complète. Comme beaucoup de choses dans la programmation, l'expérience de le répéter encore et encore est l'un des meilleurs moyens d'être efficace.


2

En plus de tous les autres commentaires, puisque vous dites que vos collègues savent bien écrire des tests qui ne sont pas une voie heureuse, pourquoi ne pas leur demander de vous jumeler avec vous pour la rédaction de certains tests.

La meilleure façon d'apprendre est de voir comment on procède et de tirer ce que l'on apprend de cela.


2

Test de la boîte noire! Vous devriez créer vos classes / méthodes avec des tests en tête. Vos tests doivent être basés sur les spécifications du logiciel et clairement définis sur votre diagramme de séquence (via des cas d'utilisation).

Maintenant, puisque vous ne voulez peut-être pas faire de développement piloté par les tests ...

Mettez la validation d'entrée sur toutes les données entrantes; ne fais confiance à personne. La structure .net a généré de nombreuses exceptions basées sur des arguments non valides, des références nulles et des états non valides. Vous devriez déjà penser à utiliser la validation des entrées sur la couche d'interface utilisateur, donc c'est la même astuce dans le middle-ware.

Mais vous devriez vraiment faire une sorte de test automatisé; ça sauve des vies.


2

Dans mon expérience

Unité de test, si ce n'est pas complètement automatique, c'est inutile. C'est plus comme un patron pointu cheveux pourrait acheter. Pourquoi ?, parce que Test Unit vous avait promis de gagner du temps (et de l'argent) en automatisant certains processus de test. Mais, certains outils d'unité de test font le contraire, cela oblige les programmeurs à travailler de façon étrange et oblige les autres à créer des tests trop étendus. La plupart du temps, cela ne fera pas gagner du temps, mais augmentera le temps de déplacement du contrôle qualité au développeur.

UML est une autre perte de temps. un seul tableau blanc + stylo pourrait faire la même chose, moins cher et plus rapidement.

BTW, Comment bien coder (et éviter les bugs)?

  • a) l'atomicité. Une fonction qui effectue une tâche simple (ou quelques tâches simples). Parce qu'il est facile à comprendre, il est facile à suivre et à résoudre.

  • b) homologie. Si, par exemple, vous appelez une base de données à l'aide d'une procédure de stockage, faites-le pour le reste du code.

  • c) Identifier, réduire et isoler le "code de création". La plupart du code est quasiment copié-collé. Le code créatif est l’inverse, un code nouveau et qui fait office de prototype, il peut échouer. Ce code est sujet aux bogues logiques, il est donc important de le réduire, de l'isoler et de l'identifier.

  • d) Le code "mince" est le code que vous savez qu'il est "incorrect" (ou potentiellement dangereux) mais dont vous avez toujours besoin, par exemple un code non sécurisé pour un processus multitâche. Évitez si vous le pouvez.

  • e) Évitez le code boîte noire, cela inclut le code qui n'est pas fait par vous (par exemple, le framework) et l'expression régulière. Il est facile de rater un bug avec ce type de code. Par exemple, j’ai travaillé dans un projet utilisant Jboss et j’ai trouvé non pas une mais 10 erreurs dans Jboss (en utilisant la dernière version stable), c’était un PITA pour les trouver. Evitez spécialement Hibernate, il cache la mise en oeuvre, d’où les bugs.

  • f) ajouter des commentaires dans votre code.

  • g) entrée de l'utilisateur comme source de bogues. identifiez-le. Par exemple, l'injection SQL est provoquée par une entrée utilisateur.

  • h) Identifier le mauvais élément de l'équipe et séparer la tâche assignée. Certains programmeurs sont enclins à visser le code.

  • i) Évitez le code inutile. Si, par exemple, la classe a besoin de Interface, utilisez-la, sinon évitez d'ajouter du code non pertinent.

a) et b) sont la clé. Par exemple, j'ai eu un problème avec un système, lorsque j'ai cliqué sur un bouton (enregistrer), il n'a pas enregistré le formulaire. Ensuite, j'ai fait une liste de contrôle:

  • le bouton fonctionne? ... oui.
  • la base de données stocke quelque chose? non, donc l'erreur était dans une étape intermédiaire.
  • alors, la classe qui stocke dans la base de données fonctionne?. non <- ok, j'ai trouvé l'erreur. (il était lié à la permission de la base de données). Ensuite, j'ai vérifié non seulement cette procédure, mais toutes les procédures qui font de même (en raison de l'homologie du code). Il m'a fallu 5 minutes pour suivre le bogue et 1 minute pour le résoudre (et beaucoup d'autres bugs).

Et un sidenote

La plupart du temps, QA est nul (en tant que section distincte de Dev), il est inutile si elles ne sont pas travaillées dans le projet. Ils font des tests génériques et rien d’autre. Ils sont incapables d'identifier la plupart des bogues logiques. Dans mon cas, je travaillais dans une banque prestigieuse, un programmeur finissait un code puis l'envoyait à QA. QA a approuvé le code et a été mis en production ... alors le code a échoué (un échec épique), savez-vous qui a été blâmé ?. oui, le programmeur.


2

Un testeur et un programmeur font face au problème sous des angles différents, mais les deux rôles doivent entièrement tester les fonctionnalités et rechercher les bogues. L'accent est mis sur les rôles différents. Un testeur classique ne voit l’application que de l’extérieur (c’est-à-dire une boîte noire). Ils sont des experts sur les exigences fonctionnelles de l'application. Un programmeur est censé être un expert à la fois des exigences fonctionnelles et du code (mais a tendance à se concentrer davantage sur le code).

(Cela dépend de l'organisation si les programmeurs sont explicitement censés être des experts en exigences. Quoi qu'il en soit, l'attente implicite est là - si vous concevez quelque chose de mal, c'est vous (et non la personne qui répond à vos exigences).)

Ce double rôle d’experts pèse sur l’esprit du programmeur et, à l’exception des plus expérimentés, peut réduire la maîtrise des exigences. Je trouve que je dois changer de vitesse mentalement pour prendre en compte les utilisateurs de l'application. Voici ce qui m'aide:

  1. Débogage ; définir des points d'arrêt dans le code et exécuter l'application. Une fois que vous avez atteint un point d'arrêt, parcourez les lignes lorsque vous interagissez avec l'application.
  2. Tests automatisés ; écrire du code qui teste votre code. Cela ne sert qu'aux niveaux inférieurs à l'interface utilisateur.
  3. Apprenez à connaître vos testeurs ; ils connaissent peut-être mieux l'application que vous, alors apprenez-en. Demandez-leur quelles sont les faiblesses de votre application et quelles tactiques elles utilisent pour trouver des bogues.
  4. Apprenez à connaître vos utilisateurs ; apprendre à marcher dans la peau de vos utilisateurs. Les exigences fonctionnelles sont l’empreinte de vos utilisateurs. Vos utilisateurs connaissent souvent beaucoup de choses sur l’application qui peuvent ne pas apparaître clairement dans les exigences fonctionnelles. Si vous comprenez mieux vos utilisateurs (la nature de leur travail dans le monde réel et comment votre application est censée les aider), vous comprendrez mieux ce que cette application est censée être.

2

Je pense que vous voulez travailler sur deux fronts. L’un est politique, amenant votre organisation à adopter les tests à un certain niveau (dans l’espoir qu’elle en adoptera de plus en plus avec le temps). Parlez à des ingénieurs d'assurance qualité en dehors de votre lieu de travail. Trouvez des listes de livres d'assurance qualité . Poke autour des articles de Wikipédia pertinents . Familiarisez-vous avec les principes et les pratiques d’AQ. L’apprentissage de ces éléments vous préparera à présenter les arguments les plus convaincants possibles au sein de votre organisation. De bons départements d'assurance qualité existent et ils apportent une valeur ajoutée considérable à leurs organisations.

En tant que développeur individuel, adoptez des stratégies à utiliser dans votre propre travail. Utilisez vous-même TDD en codéveloppant du code et des tests. Gardez les tests clairs et bien entretenus. Si on vous demande pourquoi vous faites cela, vous pouvez dire que vous empêchez les régressions et que votre processus de pensée est mieux organisé (les deux seront vrais). Écrire un code testable est un art , apprenez-le. Soyez un bon exemple pour vos collègues développeurs.

En partie, je prêche moi-même ici, parce que je fais beaucoup moins de ce genre de choses que je ne le devrais.

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.