Code de test copié-collé: à quel point est-ce mauvais?

12

Mon travail actuel consiste principalement à écrire du code de test GUI pour diverses applications sur lesquelles nous travaillons. Cependant, je trouve que j'ai tendance à copier et coller beaucoup de code dans les tests. La raison en est que les zones que je teste ont tendance à être assez similaires pour nécessiter une répétition mais pas assez similaires pour encapsuler du code dans des méthodes ou des objets. Je trouve que lorsque j'essaie d'utiliser plus largement les classes ou les méthodes, les tests deviennent plus lourds à maintenir et parfois carrément difficiles à écrire en premier lieu.

Au lieu de cela, je copie généralement un gros morceau de code de test d'une section et le colle dans une autre, et j'apporte les modifications mineures dont j'ai besoin. Je n'utilise pas de méthodes de codage plus structurées, telles que l'utilisation de plus de principes ou de fonctions OO.

Les autres codeurs se sentent-ils ainsi lors de l'écriture du code de test? Évidemment, je veux suivre les principes DRY et YAGNI, mais je trouve que le code de test (code de test automatisé pour les tests GUI de toute façon) peut rendre ces principes difficiles à suivre. Ou ai-je juste besoin de plus de pratique de codage et d'un meilleur système global de faire les choses?

EDIT: L'outil que j'utilise est SilkTest, qui est dans un langage propriétaire appelé 4Test. De plus, ces tests concernent principalement les applications de bureau Windows, mais j'ai également testé des applications Web à l'aide de cette configuration.

joshin4colours
la source
Quel outil de test utilisez-vous? Il se peut que votre infrastructure de test ne prenne pas en charge les types de tests que vous écrivez. Couper-coller de plus de 3 lignes est généralement très mauvais, mais si vous êtes en mesure d'ajouter clairement plus de valeur à long terme en automatisant un test GUI qu'en le réalisant manuellement à chaque fois, alors tout ce que vous faites est probablement sacrément bon bien.
GlenPeterson
De quelle langue s'agit-il également? Vous pouvez avoir quelque chose de disponible qui ne vient tout simplement pas à l'esprit, qui permettrait la réutilisation (comme les fonctions de première classe). D'un autre côté, les cas de test sont censés rester simples, pour qu'il soit moins probable qu'ils aient eux-mêmes des bogues ...
Izkata
3
Dans tout ce que j'ai écrit, le test du code n'est pas exclu de la refactorisation ..
Simon Whitehead

Réponses:

23

Les scénarios de test copiés-collés puis modifiés sont souvent corrects.

Les tests doivent avoir le moins de dépendances externes possible et être aussi simples que possible. Les cas de test ont tendance à changer avec le temps et des cas de test auparavant presque identiques peuvent soudainement diverger. Mettre à jour un cas de test sans avoir à se soucier de casser d'autres cas est une bonne chose.

Bien sûr, le code passe-partout qui est identique dans de nombreux cas de test et doit changer de concert peut et doit être éliminé.

9000
la source
1
C'est principalement ce que je ressens. Un code de test presque identique est correct dans de nombreux cas, mais un code de test identique répété est une mauvaise nouvelle.
joshin4colours
12

La répétition est la racine de tout mal

C'est vrai! La répétition est la racine de tout mal . C'était probablement Knuth qui disait dans son livre «L'optimisation prématurée est la racine de tout mal», mais je pense que c'est la répétition.

Chaque fois que vous regardez un programme ou que vous en écrivez un et que vous découvrez une sorte de répétition: supprimez-le! Tuez-le immédiatement… peu importe, mais débarrassez-vous-en !

Chaque fois que j'introduisais une sorte de répétition et que je devais corriger un bogue là-dedans, j'oubliais de corriger la réplique ... (Donald Knuth) Donc, chaque fois qu'il y a une répétition, supprimez-la du mieux que vous pouvez, ne piratez pas !

Pensez à une conception allégée propre (comme encapsuler vos blocs de code répétitifs dans des classes d'assistance) et écrivez quelques tests avant de changer quelque chose (juste pour être sûr que vous n'avez pas cassé quelque chose). Cela est vrai pour tout morceau de code écrit et les codes de test ne font pas exception.

Voici une bonne lecture de Code Horror qui m'inspire - Une proposition modeste pour l'école du copier-coller de la réutilisation du code .

Yusubov
la source
"Chaque fois que j'introduisais une sorte de répétition et que je devais corriger un bogue là-dedans, j'oubliais de réparer la réplique ..." exactement. De plus, si vous c & p et que vous oubliez d'ajuster le texte copié à votre contexte actuel, cela nuira beaucoup. Le code de test buggé ne ressemble pas à la situation optimale, maintenant?
marktani
oui, j'ai pris les stations de Knuth :)
Yusubov
9
Vous vous êtes répété: Vous vous êtes répété en disant "La répétition est la racine de tout mal" dans le titre et votre phrase d'introduction.
Thomas Eding
Oui, je l'ai fait intentionnellement pour souligner l'importance et vous êtes invités à modifier cette partie :)
Yusubov
1
Thomas Eding, vous vous êtes également répété. Vous vous êtes répété aussi =)
marktani
7

Il est encore assez mal de couper et coller. Il y a quelques problèmes.

Vos tests peuvent être fragiles, car vous êtes vulnérable à quelque chose qui nécessite une modification de tout ce code copié-collé. Aurez-vous à réécrire tous les tests?

Si vous ne pouvez pas encapsuler la logique dans des méthodes d'assistance en dehors de vos tests, vous ne pouvez pas écrire les tests de ces méthodes d'assistance elles-mêmes. Il est généralement difficile de rédiger des tests de méthodes de test, car vous devez casser votre code pour tester le test. Mais vous pouvez tester les méthodes auxiliaires de test unitaire.

Cela pourrait bien rendre les tests moins lisibles. Un gros bloc de code copié peut être plus difficile à lire qu'un appel à la méthode d'assistance avec un nom descriptif.

Tout ce que j'ai énuméré peut être un problème. Si vous trouvez aucun d'entre eux en fait sont un problème, bien sûr , il va bien.

psr
la source
> un appel à la méthode d'assistance avec un nom descriptif. N'est-ce pas le problème avec cela que vos tests unitaires deviennent maintenant des programmes en soi - que l'on est censé éviter. Que faire si certains tests échouent - est-ce le code qui est cassé ou les assistants de test?
dwjohnston
4

J'étais d'accord avec toi. Mais ensuite, au fil du temps, j'ai trouvé que chaque changement que j'apportais (en particulier les changements DI dans les tests unitaires) nécessitait de nombreux tests pour changer et c'était lourd. Maintenant je m'inscris à l'école de DRY, même lors des tests.

Pour les tests GUI, vous souhaiterez peut-être examiner le modèle PageObject pour réduire le code répété.

pdr
la source
2

Je recommanderais de choisir des modèles XUnit. J'ai eu exactement le même problème jusqu'à ce que je commence à tirer parti de ce livre. Le motif Object Mother semble être le plus utile pour votre scénario.

Comme quelqu'un d'autre l'a mentionné, l'encapsulation correcte de ce code d'installation peut être onéreuse, mais devoir le changer à tous les endroits que vous copiez et collez l'est encore plus.

Michael Brown
la source
+1 pour le Object Mother patterncode d'initialisation commun.
k3b
2

Les gens devraient-ils essayer de limiter la répétition quand ils le peuvent - oui. Mais le gain dépend de la situation. Cela pourrait revenir au débat sur les «meilleures pratiques». Mais la question est de savoir ce qui vous convient le mieux dans cette situation. Il existe des exceptions à chaque règle.

Voici quelques questions que je poserais: 1) Quelle est la probabilité que cette fonctionnalité testée dans l'UAT change? S'il est peu probable que cela change, il y a moins de chances que vous deviez mettre à jour chacun de vos ensembles de code. 2) S'il y a un changement dans l'UAT, cela aura-t-il toujours un impact sur chaque ensemble du code copié ou pourrait-il seulement avoir un impact sur un ou deux ensembles? S'il peut être isolé et ne nécessiter qu'une modification d'un seul ensemble, il peut être utile de séparer les éléments. 3) Quelle sera la complexité de la méthode initiale si vous essayez de la faire gérer tous les scénarios? Ajoutez-vous beaucoup de boucles imbriquées if / else /? Si vous commencez à faire trop de branchements, vous risquez de vous retrouver avec du code difficile à comprendre. Serait-il plus facile de faire la mise à jour dans chacun des textes copiés que de revoir toute la logique de branchement?

Si vous êtes bloqué copier / coller / modifier, je pense que vous voudriez ajouter des commentaires tels que «Ceci est copié dans la méthode xyz». De cette façon, vous serez rappelé de mettre à jour toutes les versions collées du code. Ou (venant d'un autre utilisateur SilkTest) pourriez-vous ajouter un fichier inc distinct qui se concentrerait uniquement sur ce code répété. De cette façon, vous avez toutes les variations en un seul endroit et pouvez facilement voir les différentes méthodes qui nécessiteraient une mise à jour.

Dan B
la source
0

Une grande procédure

Une pensée: on dirait que vous essayez d'éviter le code couper-coller en créant des méthodes comme:

testScreen(title, fieldList, linkList, param1, param2, param3,...) {
    test that the layout at the top of the screen is correct
    test if PageTitle == title?
    for each field in fieldList:
        check that it appears in order on the screen
    for each field in linkList:
        check that it appears in order on the screen
    test if param1 is whatever...
    test if param2 is whatever...
    etc.
    test that the bottom of the screen is correct
}

Beaucoup de petites procédures (boîte à outils)

Avez-vous également envisagé l'approche inverse? Au lieu de passer un million de paramètres à une grande procédure testScreen (), créez peut-être votre propre cadre ou une trousse d'outils de petites procédures d'assistance que vous élaborez selon vos besoins. Comme:

testScreenTop()
verifyLinks(list)
testScreenBottom()

Vous coupez et collez toujours ces procédures dans chaque écran, mais vous coupez et collez de plus petits morceaux de code et vous découpez des morceaux communs qui ne sont pas coupés et collés (le contenu de chaque petite procédure).

Couper et coller

Le seul moment où le code coupé-collé ne m'a pas mordu, c'est quand le code a été jeté avant que je ne doive le changer. Ma plus grande préoccupation avec les tests d'interface utilisateur est la rapidité avec laquelle ils deviennent obsolètes. Si vous trouvez que vous jetez tout votre code avant de le changer, alors vous avez peut-être trouvé un créneau où couper et coller est OK! De plus, ce n'est pas si mal quand il n'y a pas de code en aval du code coupé-collé (par exemple dans l'interface utilisateur d'une application). Si vous collez plus de 3 lignes, j'aimerais vraiment faire quelque chose. Prenez au moins des mesures pour le minimiser!

Test d'interface utilisateur automatisé

Heck, si vous pouvez prouver une plus grande productivité avec des tests d'interface utilisateur automatisés que des tests manuels en utilisant n'importe quelle technique (le coût d'écriture / maintenance des tests automatisés est inférieur à celui des tests manuels à chaque fois, mais la qualité est la même), je pense que vous devriez écrire un document. Je le lirais! Je peux voir le titre maintenant, "Coupez et collez un code Net Win pour les tests d'interface utilisateur!"

GlenPeterson
la source
0

Ce n'est vraiment pas si mal. En fait, si vous trouvez que certains modèles de code sont utilisés très souvent et que les changements sont très routiniers (comme quelques chaînes ou valeurs de paramètres), vous pouvez même écrire un générateur de code qui génère du code de test répétitif basé sur un petit (- ish?) entrez la liste des valeurs qui changent. J'ai fait cela (code de test généré) plusieurs fois, en utilisant des fichiers batch, des scripts SQLPlus, même des macros Excel (cela semble moche, mais les variables pour les différents scripts de test étaient déjà dans une feuille de calcul) et cela peut être un excellent gain de temps . Le fait est que si quelque chose change dans la structure globale du code de cas de test répétitif, vous pouvez simplement régénérer tout ce dont vous avez besoin.

FrustratedWithFormsDesigner
la source
0

C'est la même chose que la plupart des autres réponses, mais d'une manière qu'un responsable non technique peut comprendre.

Imaginez le scénario d'erreur suivant:

  • Quelqu'un modifie le jeu de données dont dépend la plupart de vos tests.
  • Conséquence: soudainement 117 de vos 2933 tests automatisés échouent.

Que vas-tu faire?

  • (1) Corriger 117 tests?
  • (2) supprimez les 117 tests et réimplémentez-les via un nouveau copier-coller. cela pourrait être plus facile que (1)
  • (3) refactoriser les tests pour extraire le code commun afin qu'à l'avenir vous n'ayez à adapter qu'une seule méthode (ou quelques-unes) pour corriger les tests (voir réponses de @pdr ou @Michael Brown)
  • (4) supprimer les 117 tests sans réimplémenter les tests

Selon mon expérience:

Lors de l'introduction de la gestion automatisée des tests, comme les «copier-coller»: vous obtenez de nombreux tests en peu de temps.

Après certains des "scénarios d'erreur", la gestion préfère (4) parce que la fixation de "copier-coller-tests" est si chère.

Le faire correctement en premier lieu (3) ne sera pas si rapide mais augmente les chances de survie des tests

k3b
la source