web-dev-qa-db-fra.com

Est-ce une bonne pratique d'exécuter des tests unitaires dans les hooks de contrôle de version?

Du point de vue technique, il est possible d'ajouter des crochets Push pré/post qui exécuteront des tests unitaires avant de permettre la fusion de certains commit spécifiques dans la branche par défaut distante.

Ma question est - est-il préférable de garder les tests unitaires dans le pipeline de construction (donc, d'introduire des commits cassés dans le repo) ou il est préférable de ne pas autoriser de "mauvais" commits.

Je me rends compte que je ne suis pas limité avec ces deux options. Par exemple, je peux autoriser toutes les validations à créer des branches et des tests avant de pousser la validation de fusion vers le référentiel. Mais si vous devez choisir entre exactement ces deux solutions, laquelle choisirez-vous et pour quelles raisons?

44
shabunc

Non, ce n'est pas le cas, pour deux raisons:

La vitesse

Les commits devraient être rapides. Un commit qui prend 500 ms, par exemple, est trop lent et encouragera les développeurs à s'engager avec plus de parcimonie. Étant donné que sur tout projet plus grand qu'un Hello World, vous aurez des dizaines ou des centaines de tests, il faudra trop de temps pour les exécuter lors de la pré-validation.

Bien sûr, les choses empirent pour les grands projets avec des milliers de tests qui s'exécutent pendant des minutes sur une architecture distribuée, ou des semaines ou des mois sur une seule machine.

Le pire, c'est qu'il n'y a pas grand-chose que vous puissiez faire pour l'accélérer. Petits Python qui ont, disons, cent tests unitaires, prennent au moins une seconde pour s'exécuter sur un serveur moyen, mais souvent beaucoup plus longtemps. Pour une application C #, cela prendra en moyenne quatre à cinq secondes , en raison du temps de compilation.

À partir de là, vous pouvez soit payer 10 000 $ supplémentaires pour un meilleur serveur, ce qui réduira le temps, mais pas beaucoup, ou exécuter des tests sur plusieurs serveurs, ce qui ne fera que ralentir les choses.

Les deux paient bien lorsque vous avez des milliers de tests (ainsi que des tests fonctionnels, système et d'intégration), ce qui permet de les exécuter en quelques minutes au lieu de semaines, mais cela ne vous aidera pas pour les projets à petite échelle.

Ce que vous pouvez faire à la place, c'est de:

  • Encouragez les développeurs à exécuter des tests fortement liés au code qu'ils ont modifié localement avant de faire une validation. Ils ne peuvent peut-être pas exécuter des milliers de tests unitaires, mais ils peuvent en exécuter cinq à dix.

    Assurez-vous que trouver des tests pertinents et les exécuter est en fait facile (et rapide). Visual Studio, par exemple, est capable de détecter quels tests peuvent être affectés par les modifications effectuées depuis la dernière exécution. D'autres IDE/plateformes/langages/frameworks peuvent avoir des fonctionnalités similaires.

  • Gardez le commit le plus rapidement possible. L'application des règles de style est OK, car souvent, c'est le seul endroit pour le faire, et parce que ces vérifications sont souvent incroyablement rapides. Faire une analyse statique est OK dès qu'elle reste rapide, ce qui est rarement le cas. L'exécution de tests unitaires n'est pas OK.

  • Exécutez des tests unitaires sur votre serveur d'intégration continue.

  • Assurez-vous que les développeurs sont informés automatiquement lorsqu'ils ont interrompu la construction (ou lorsque les tests unitaires ont échoué, ce qui est pratiquement la même chose si vous considérez un compilateur comme un outil qui vérifie certaines des erreurs possibles que vous pouvez introduire dans votre code).

    Par exemple, aller sur une page Web pour vérifier les dernières versions n'est pas une solution. Ils doivent être informés automatiquement . Afficher une fenêtre contextuelle ou envoyer un SMS sont deux exemples de la manière dont ils peuvent être informés.

  • Assurez-vous que les développeurs comprennent que la rupture de la build (ou l'échec des tests de régression) n'est pas OK, et que dès que cela se produit, leur priorité est de le réparer. Peu importe qu'ils travaillent sur une fonctionnalité hautement prioritaire que leur patron a demandé de livrer pour demain: ils ont échoué la construction, ils devraient le réparer.

Sécurité

Le serveur qui héberge le référentiel ne doit pas exécuter de code personnalisé, tel que des tests unitaires, en particulier pour des raisons de sécurité. Ces raisons ont déjà été expliquées dans CI runner sur le même serveur de GitLab?

Si, d'autre part, votre idée est de lancer un processus sur le serveur de build à partir du hook de pré-validation, cela ralentira encore plus les validations.

35
Arseni Mourzenko

Permettez-moi d’être en désaccord avec mes collègues répondeurs.

Ceci est connu sous le nom de Gated Check-ins dans le monde TFS, et j'attends ailleurs. Lorsque vous essayez de vous enregistrer dans une branche avec l'archivage fermé, l'étagère est envoyée au serveur, ce qui garantit que vos modifications sont générées et que les tests unitaires spécifiés (lire: tous) réussissent. S'ils ne le font pas, cela vous informe que vous êtes un mauvais singe qui a cassé la construction. S'ils le font, les changements vont dans le contrôle de code source (ouais!).

D'après mon expérience, les enregistrements bloqués sont l'un des processus les plus importants pour réussir les tests unitaires - et par extension, la qualité du logiciel.

Pourquoi?

  • Parce que les enregistrements bloqués obligent les gens à réparer les tests cassés. Dès que les tests cassés deviennent quelque chose que les gens peuvent faire plutôt que doivent faire , ils deviennent sans priorité par des ingénieurs paresseux et/ou des gens d'affaires arrogants.
    • Plus un test est long, plus il est difficile (et coûteux) à corriger.
  • Parce que dès que les gens doivent exécuter les tests plutôt que doivent exécuter le tests, l'exécution des tests sont contournés par des ingénieurs paresseux/oublieux et/ou des gens d'affaires arrogants.
  • Parce que dès que les tests unitaires ont un impact sur votre temps de validation, les gens vraiment commencent à se soucier de faire leurs tests unit tests. La vitesse compte. La reproductibilité est importante. La fiabilité est importante. L'isolement est important.

Et bien sûr, il y a l'avantage que vous avez mentionné à l'origine - lorsque vous avez activé les enregistrements et une suite solide de tests, chaque ensemble de modifications est "stable". Vous enregistrez tous ces frais généraux (et le potentiel d'erreur) de "quand a été la dernière bonne construction?" - toutes les versions sont assez bonnes pour se développer.

Oui, il faut du temps pour créer et exécuter les tests. D'après mon expérience, 5 à 10 minutes pour une application C # de bonne taille et environ 5 000 tests unitaires. Et je m'en fiche. Oui, les gens devraient s'enregistrer fréquemment. Mais ils doivent également mettre à jour leurs tâches fréquemment, ou vérifier leur courrier électronique, ou prendre un café ou des dizaines d'autres choses "ne travaillant pas sur le code" qui composent le travail d'un ingénieur logiciel pour occuper ce temps. Vérifier un mauvais code coûte beaucoup plus cher que 5 à 10 minutes.

41
Telastyn

Les validations doivent s'exécuter rapidement. Lorsque je valide du code, je veux qu'il soit poussé vers le serveur. Je ne veux pas attendre quelques minutes pendant qu'il exécute une batterie de tests. Je suis responsable de ce que je pousse vers le serveur et je n'ai besoin de personne qui me garde avec des hooks de validation.

Cela dit, une fois arrivé sur le serveur, il doit être analysé, testé à l'unité et construit immédiatement (ou dans un court laps de temps). Cela m'alerterait sur le fait que les tests unitaires sont cassés, ou qu'ils n'ont pas été compilés, ou que j'ai fait un gâchis montré par les outils d'analyse statique disponibles. Plus vite cela se fait (la construction et l'analyse), plus vite mes commentaires et plus vite je suis capable de le réparer (les pensées ne sont pas complètement sorties de mon cerveau).

Donc non, ne mettez pas de tests et autres dans les hooks de validation sur le client. Si vous devez le faire, placez-les sur le serveur dans un post-commit (car vous n'avez pas de serveur CI) ou sur le serveur de build CI et alertez-moi de manière appropriée des problèmes avec le code. Mais n'empêchez pas la validation de se produire en premier lieu.

Je dois également souligner qu'avec certaines interprétations du développement piloté par les tests, un devrait enregistrer un test unitaire qui casse d'abord . Cela démontre et documente le bogue est présent. Ensuite, un archivage ultérieur serait le code qui corrige le test unitaire. Empêcher tout archivage jusqu'à la réussite des tests unitaires réduirait la valeur effective de l'archivage d'un test unitaire qui ne parvient pas à documenter le problème.

Connexes: Dois-je avoir des tests unitaires pour les défauts connus? et Quelle est la valeur de la vérification des tests unitaires ayant échoué?

40
user40980

En principe, je pense qu'il est logique d'empêcher les gens d'apporter des modifications à la ligne principale qui cassent la construction. En d'autres termes, le processus de modification de la branche principale de votre référentiel doit exiger que tous les tests réussissent. Briser la construction est tout simplement trop coûteux en termes de temps perdu pour que tous les ingénieurs du projet fassent autre chose.

Cependant, la solution particulière des hooks de validation n'est pas un bon plan.

  1. Le développeur doit attendre que les tests s'exécutent lors de la validation. Si le développeur doit attendre sur son poste de travail que tous les tests soient réussis, vous avez perdu un temps d'ingénieur précieux. L'ingénieur doit pouvoir passer à la tâche suivante, même s'il devra revenir en arrière car les tests ont échoué.
  2. Les développeurs peuvent vouloir valider du code cassé dans une branche. Dans une tâche plus importante, la version développeur du code peut passer beaucoup de temps pas dans un état de passage. De toute évidence, la fusion de ce code dans la ligne principale serait très mauvaise. Mais il est plutôt important que le développeur puisse toujours utiliser le contrôle de version pour suivre ses progrès.
  3. Il existe parfois de bonnes raisons de sauter le processus et de contourner les tests.
10
Winston Ewert

Non, vous ne devriez pas, comme d'autres réponses l'ont souligné.

Si vous voulez avoir une base de code qui est garantie de n'avoir aucun test en échec, vous pouvez à la place développer sur des branches de fonctionnalités et faire des requêtes dans le maître. Vous pouvez ensuite définir les conditions préalables à l'acceptation de ces demandes d'extraction. L'avantage est que vous pouvez pousser très rapidement et que les tests s'exécutent en arrière-plan.

3
Yogu

Devoir attendre la construction et les tests réussis sur chaque commit sur la branche principale est vraiment horrible, je pense que tout le monde est d'accord là-dessus.

Mais il existe d'autres façons de réaliser une branche principale cohérente. Voici une suggestion, un peu similaire dans la veine des enregistrements bloqués dans TFS, mais qui est généralisable à tout système de contrôle de version avec des branches, bien que j'utilise principalement des termes git:

  • Avoir une branche intermédiaire dans laquelle vous ne pouvez valider que les fusions entre vos branches de développement et la branche principale

  • Configurer un hook qui démarre ou met en file d'attente une génération et teste les validations effectuées sur la branche intermédiaire, mais cela ne fait pas attendre le validateur

  • En cas de build et de tests réussis, faites automatiquement passer la branche principale forward si elle est à jour

    Remarque: ne pas automatiquement fusionner dans la branche principale, car la fusion testée, si ce n'est pas une fusion directe du point de vue de la branche principale, peut échouer lorsqu'elle est fusionnée dans la branche principale avec des validations intermédiaires

En conséquence:

  • Interdire à l'homme de s'engager dans la branche principale, automatiquement si vous le pouvez, mais également dans le cadre du processus officiel s'il y a une faille ou s'il n'est pas techniquement possible de faire respecter cela

    Au moins, vous pouvez vous assurer que personne ne le fera involontairement ou sans méchanceté, une fois que c'est une règle fondamentale. Vous ne devriez jamais essayer de le faire.

  • Vous devrez choisir entre:

    • Une seule branche intermédiaire, qui fera en sorte que les fusions réussies échouent en fait si une fusion précédente mais non construite et non testée échoue

      Au moins, vous saurez quelle fusion a échoué et demandez à quelqu'un de la corriger, mais les fusions entre les deux ne sont pas trivialement traçables (par le système de contrôle de version) pour les résultats de la construction et des tests ultérieurs.

      Vous pouvez regarder l'annotation de fichier (ou blâmer), mais parfois un changement dans un fichier (par exemple la configuration) générera des erreurs dans des endroits inattendus. Cependant, c'est un événement plutôt rare.

    • Plusieurs branches intermédiaires, qui permettront aux fusions réussies non conflictuelles d'accéder à la branche principale

      Même dans le cas où une autre branche intermédiaire aurait non conflictuelle échecs de fusion. La traçabilité est un peu meilleure, d'autant moins qu'une fusion ne s'attendait pas à un changement affectant d'une autre fusion. Mais encore une fois, c'est assez rare pour ne pas vous inquiéter tous les jours ou toutes les semaines.

      Pour avoir des fusions non conflictuelles la plupart du temps, il est important de diviser sensiblement les branches intermédiaires, par ex. par équipe, par couche ou par composant/projet/système/solution (même si vous le nommez).

      Si la branche principale a été transmise entre-temps à une autre fusion, vous devez à nouveau fusionner. J'espère que ce n'est pas un problème avec les fusions non conflictuelles ou avec très peu de conflits.

Par rapport aux enregistrements fermés, l'avantage ici est que vous avez la garantie d'une branche principale fonctionnelle, car la branche principale n'est autorisée qu'à avancer, et non à fusionner automatiquement vos modifications avec tout ce qui a été validé entre les deux. Le troisième point est donc la différence essentielle.

2
acelent

Je préfère que les "tests unitaires réussis" soient une porte d'entrée pour soumettre du code. Cependant, pour que cela fonctionne, vous aurez besoin de quelques éléments.

Vous aurez besoin d'un cadre de construction qui met en cache les artefacts.

Vous aurez besoin d'un cadre de test qui met en cache le statut du test à partir des exécutions de test (réussies), avec n'importe quel artefact donné.

De cette façon, les enregistrements avec des tests unitaires réussis seront rapides (vérification croisée de la source à l'artefact qui a été construit lorsque le développeur a vérifié les tests avant l'enregistrement), ceux dont les tests unitaires ont échoué seront bloqués et les développeurs qui n'oubliez pas de vérifier les versions avant de les valider. Vous serez encouragé à vous en souvenir la prochaine fois, car le cycle de compilation et de test est long.

2
Vatine

Les validations brisées ne devraient pas être autorisées sur trunk, car trunk est ce qui peut entrer en production. Vous devez donc vous assurer qu'il existe une passerelle à passer avant d'être activée trunk. Cependant, les commits cassés peuvent être totalement corrects dans un dépôt tant qu'ils ne sont pas sur le coffre.

D'un autre côté, obliger les développeurs à attendre/résoudre les problèmes avant de pousser les modifications dans le référentiel présente un certain nombre d'inconvénients.

Quelques exemples:

  • Dans TDD, il est courant de valider et d'envoyer des tests d'échec pour de nouvelles fonctionnalités avant de commencer à implémenter la fonctionnalité
  • Il en va de même pour signaler des bogues en validant et en poussant un test qui échoue
  • Pousser du code incomplet permet à 2 personnes ou plus de travailler sur une fonctionnalité en parallèle facilement
  • Votre infrastructure CI peut prendre son temps à vérifier, mais le développeur n'a pas à attendre
1
tkruse

Je dirais que cela dépend du projet et de la portée des tests automatisés qui sont exécutés sur le "commit".

Si les tests que vous souhaitez exécuter dans le déclencheur d'enregistrement sont vraiment rapides, ou si le flux de travail du développeur forcera de toute façon un travail administratif après un tel enregistrement, le je pense que cela ne devrait pas avoir beaucoup d'importance et forcer les développeurs à ne vérifier que dans des trucs qui exécutent absolument les tests les plus élémentaires. (Je suppose que vous ne feriez que les tests les plus élémentaires sur un tel déclencheur.)

Et je pense, si la vitesse/le flux de travail le permet, c'est une bonne chose de ne pas pousser les modifications vers d'autres développeurs qui échouent aux tests - et vous ne savez que s'ils échouent si vous les exécutez.

Vous écrivez "commit ... to remote branch" dans la question, ce qui impliquerait pour moi que ce n'est (a) pas quelque chose qu'un développeur fait toutes les quelques minutes, donc une petite attente peut être très bien acceptable, et (b) qu'après un tel commit les changements de code peuvent avoir un impact sur d'autres développeurs, donc des vérifications supplémentaires peuvent être nécessaires.

Je peux être d'accord avec les autres réponses sur "ne faites pas tourner vos pouces de devs en attendant" pour une telle opération.

1
Martin Ba