web-dev-qa-db-fra.com

Comment faire en sorte que les tests unitaires s'exécutent rapidement?

Nous avons atteint le point de notre projet où nous avons près d'un millier de tests et les gens ont cessé de se soucier de les exécuter avant de procéder à un enregistrement car cela prend tellement de temps. Au mieux, ils exécutent les tests qui sont pertinents pour le morceau de code qu'ils ont changé et au pire, ils l'archivent simplement sans tester.

Je crois que ce problème est dû au fait que la solution est passée à 120 projets (nous faisons généralement des projets beaucoup plus petits et ce n'est que la deuxième fois que nous faisons TDD correctement) et le temps de construction + test est passé à environ deux à trois minutes sur les petites machines.

Comment pouvons-nous réduire la durée d'exécution des tests? Existe-t-il des techniques? Faire semblant de plus? Faux moins? Peut-être que les tests d'intégration plus importants ne devraient pas s'exécuter automatiquement lors de l'exécution de tous les tests?

Edit: en réponse à plusieurs des réponses, nous utilisons déjà CI et un serveur de build, c'est ainsi que je sais que les tests échouent. Le problème (en fait un symptôme) est que nous continuons à recevoir des messages sur les builds ayant échoué. La plupart des gens font des tests partiels, mais pas tous. et en ce qui concerne les tests, ils sont en fait assez bien faits, ils utilisent des faux pour tout et il n'y a pas de IO du tout.

40
Ziv

Une solution possible serait de déplacer la partie test des machines de développement vers une configuration d'intégration continue ( Jenkins par exemple) en utilisant un logiciel de contrôle de version d'une certaine saveur ( git , - svn , etc ...).

Lorsqu'un nouveau code doit être écrit, le développeur donné créera une branche pour tout ce qu'il fait dans le référentiel. Tous les travaux seront effectués dans cette branche et ils peuvent valider leurs modifications dans la branche à tout moment sans gâcher la ligne de code principale.

Lorsque la fonctionnalité, la correction de bogue ou tout autre élément sur lequel ils travaillent est terminée, cette branche peut être fusionnée dans le tronc (ou comme vous préférez le faire) où tous les tests unitaires sont exécutés. Si un test échoue, la fusion est rejetée et le développeur est averti afin qu'il puisse corriger les erreurs.

Vous pouvez également demander à votre serveur CI d'exécuter les tests unitaires sur chaque branche de fonctionnalité lors de la validation. De cette façon, le développeur peut apporter des modifications, valider le code et laisser le serveur exécuter les tests en arrière-plan pendant qu'il continue à travailler sur des modifications supplémentaires ou d'autres projets.

Un excellent guide sur une façon de faire une telle configuration peut être trouvé ici (spécifique à git mais devrait fonctionner pour d'autres systèmes de contrôle de version): http://nvie.com/posts/a-successful-git-branching- modèle /

50
Mike

La majorité des tests unitaires devraient prendre moins de 10 millisecondes chacun. Avoir "presque mille tests" n'est rien et devrait prendre peut-être quelques secondes pour s'exécuter.

Si ce n'est pas le cas, vous devez arrêter d'écrire des tests d'intégration fortement couplés (sauf si c'est ce dont le code a besoin) et commencer à écrire de bons tests unitaires (en commençant par code bien découplé et bonne utilisation des contrefaçons/simulacres/talons/etc). Ce couplage aura un impact sur la qualité des tests et le temps nécessaire pour les écrire également - il ne s'agit donc pas seulement de réduire la durée d'exécution des tests.

33
Telastyn

Il existe plusieurs approches que j'ai utilisées pour résoudre un problème similaire:

  1. Vérifiez le temps d'exécution, trouvez tous les tests les plus lents, puis analysez pourquoi ils prennent autant de temps à exécuter.
  2. Vous avez 100 projets, peut-être n'avez-vous pas besoin de les construire et de les tester à chaque fois? Pourriez-vous exécuter tous les plus instables uniquement lors d'une nuit? Créez plusieurs configurations de construction "rapides" pour une utilisation quotidienne. Le serveur CI exécutera seulement un ensemble limité de projets non testés liés aux parties "chaudes" de votre processus de développement actuel.
  3. Se moquer et isoler tout ce que vous pourriez, éviter les E/S disque/réseau chaque fois que cela est possible
  4. Lorsqu'il n'est pas possible d'isoler de telles opérations, peut-être avez-vous des tests d'intégration? Peut-être pourriez-vous planifier les tests d'intégration aux versions nocturnes uniquement?
  5. Vérifiez tous les singletons occasionnels, qui gardent des références aux instances/ressources et qui consomment de la mémoire, cela pourrait entraîner une dégradation des performances lors de l'exécution de tous les tests.

De plus, vous pouvez utiliser les outils suivants pour vous faciliter la vie et exécuter les tests plus rapidement

  1. Gated commit certains serveurs CI peuvent être configurés pour effectuer la génération et le test avant de valider le code dans le référentiel source. Si quelqu'un valide le code sans exécuter tous les tests au préalable, qui contient également des tests ayant échoué, il sera rejeté et renvoyé à l'auteur.
  2. Configurer le serveur CI pour exécuter des tests en parallèle: en utilisant plusieurs machines ou processus. Les exemples sont pnunit et configuration CI avec plusieurs nœuds.
  3. Plug-in de test contin pour les développeurs, qui exécutera automatiquement tous les tests lors de l'écriture de code.
16
Akim

0. Écoutez vos programmeurs.

S'ils n'exécutent pas les tests, cela signifie qu'ils perçoivent le coût (attendre que les tests s'exécutent, faire face à de faux échecs) supérieur à la valeur (attraper tout de suite des bogues). Diminuez les coûts, augmentez la valeur et les gens exécuteront les tests tout le temps.

1. Rendez vos tests 100% fiables.

Si vous avez déjà des tests qui échouent avec de faux négatifs, traitez-le immédiatement. Réparez-les, changez-les, éliminez-les, quoi qu'il en soit pour garantir une fiabilité à 100%. (C'est OK d'avoir un ensemble de tests peu fiables, mais toujours utiles que vous pouvez exécuter séparément, mais le corps principal des tests doit être fiable.)

2. Changez vos systèmes pour garantir que tous les tests passent tout le temps.

Utilisez des systèmes d'intégration continue pour vous assurer que seules les validations de passage sont fusionnées dans la branche principale/officielle/version/quelle que soit la branche.

3. Changez votre culture pour valoriser 100% de réussite aux tests.

Enseignez la leçon qu'une tâche n'est pas "terminée" jusqu'à ce que 100% des tests réussissent et qu'elle ait été fusionnée dans la branche principale/officielle/version/quelque soit la branche.

4. Faites les tests rapidement.

J'ai travaillé sur des projets où les tests prennent une seconde, et sur des projets où ils prennent toute la journée. Il existe une forte corrélation entre le temps nécessaire pour exécuter les tests et ma productivité.

Plus les tests sont longs à exécuter, moins vous les exécuterez. Cela signifie que vous irez plus longtemps sans recevoir de commentaires sur les modifications que vous apportez. Cela signifie également que vous irez plus longtemps entre les validations. S'engager plus souvent signifie des étapes plus petites et plus faciles à fusionner; l'historique des validations est plus facile à suivre; trouver un bug dans l'histoire est plus facile; le retour en arrière est également plus facile.

Imaginez des tests qui s'exécutent si rapidement que cela ne vous dérange pas de les exécuter automatiquement à chaque compilation.

Faire des tests rapidement peut être difficile (c'est ce que le PO a demandé, non!). Le découplage est la clé. Les simulacres/contrefaçons sont OK, mais je pense que vous pouvez faire mieux en refactorisant pour rendre les moqueries/contrefaçons inutiles. Voir le blog d'Arlo Belshee, en commençant par http://arlobelshee.com/post/the-no-mocks-book .

5. Rendre les tests utiles.

Si les tests échouent lorsque vous vous trompez, à quoi ça sert? Apprenez à écrire des tests qui détecteront les bogues que vous êtes susceptible de créer. Il s'agit d'une compétence en soi et qui demandera beaucoup d'attention.

12
Jay Bazuzi

Quelques minutes suffisent pour les tests unitaires. Cependant, gardez à l'esprit qu'il existe 3 principaux types de tests:

  1. Tests unitaires - testez chaque "unité" (classe ou méthode) indépendamment du reste du projet
  2. Tests d'intégration - testez le projet dans son ensemble, généralement en appelant le programme. Certains projets que j'ai vus combinent cela avec des tests de régression. Il y a ici beaucoup moins de moqueries que les tests unitaires
  3. Tests de régression - testez le projet terminé dans son ensemble, car la suite de tests est un utilisateur final. Si vous avez une application console, vous utiliserez la console pour exécuter et tester le programme. Vous n'exposez jamais les internes à ces tests et tout utilisateur final de votre programme devrait (en théorie) être capable d'exécuter votre suite de tests de régression (même s'ils ne le feront jamais)

Ceux-ci sont classés par ordre de vitesse. Les tests unitaires doivent être rapides. Ils n'attraperont pas tous les bogues, mais ils établissent que le programme est décemment sain d'esprit. Les tests unitaires doivent s'exécuter en 3 minutes ou moins ou du matériel décent. Vous dites que vous n'avez que 1000 tests unitaires et qu'ils prennent 2-3 minutes? Eh bien, c'est probablement OK.

A vérifier:

  • Assurez-vous cependant que vos tests unitaires et tests d'intégration sont séparés. Les tests d'intégration seront toujours plus lents.

  • Assurez-vous que vos tests unitaires s'exécutent en parallèle. Il n'y a aucune raison pour qu'ils ne le fassent pas s'ils sont de vrais tests unitaires

  • Assurez-vous que vos tests unitaires sont "sans dépendance". Ils ne doivent jamais accéder à une base de données ou au système de fichiers

À part cela, vos tests ne semblent pas trop mauvais en ce moment. Cependant, pour référence, l'un de mes amis d'une équipe Microsoft a 4000 tests unitaires qui s'exécutent en moins de 2 minutes sur du matériel décent (et c'est un projet compliqué). Il est possible d'avoir des tests unitaires rapides. L'élimination des dépendances (et se moquer seulement autant que nécessaire) est la chose principale pour obtenir de la vitesse.

4
Earlz

Formez vos développeurs sur Personal Software Process (PSP) en les aidant à comprendre et à améliorer leurs performances en utilisant plus de discipline. L'écriture de code n'a rien à voir avec le claquement des doigts sur un clavier et ensuite appuyez sur un bouton de compilation et d'archivage.

PSP était très populaire dans le passé lorsque la compilation de code était un processus qui prenait beaucoup de temps (heures/jours sur un ordinateur central, donc tout le monde devait partager le compilateur). Mais lorsque les postes de travail personnels sont devenus plus puissants, nous en sommes tous venus à accepter le processus:

  1. taper du code sans réfléchir
  2. hit build/compile
  3. corrigez votre syntaxe pour la compiler
  4. exécuter des tests pour voir si ce que vous avez écrit a du sens

Si vous réfléchissez avant de taper, puis après avoir tapé, révisez ce que vous avez écrit, vous pouvez réduire le nombre d'erreurs avant d'exécuter une suite de génération et de test. Apprenez à ne pas appuyer sur build 50 fois par jour, mais peut-être une ou deux fois, alors il importe moins que votre temps de build et de test prenne quelques minutes de plus.

3
Bart Koopman

Une façon possible: divisez votre solution. Si une solution comprend 100 projets, elle est tout à fait ingérable. Ce n'est pas parce que deux projets (disons A et B) utilisent du code commun d'un autre projet (disons Lib) qu'ils doivent être dans la même solution.

Au lieu de cela, vous pouvez créer la solution A avec les projets A et Lib et également la solution B avec les projets B et Lib.

3
svick

Je suis dans une situation similaire. J'ai des tests unitaires qui testent la communication avec le serveur. Ils testent le comportement avec des délais d'attente, annulent les connexions, etc. L'ensemble des tests dure 7 minutes.

7 minutes est un temps relativement court mais ce n'est pas quelque chose que vous ferez avant chaque commit.

Nous avons également un ensemble de tests d'interface utilisateur automatisés, leur durée d'exécution est de 2 heures. Ce n'est pas quelque chose que vous souhaitez exécuter tous les jours sur votre ordinateur.

Alors que faire?

  1. Changer les tests n'est généralement pas très efficace.
  2. Exécutez uniquement les tests pertinents avant votre validation.
  3. Exécutez tous vos tests tous les jours (ou plusieurs fois par jour) sur un serveur de build. Cela vous donnera également la possibilité de générer des rapports de couverture et d'analyse de code Nice.

L'important est: tous vos tests doivent être exécutés souvent car il est important de trouver les bugs. Cependant, il n'est pas absolument nécessaire de les trouver avant les commits.

2
Sulthan

Bien que votre description du problème ne donne pas un aperçu complet de la base de code, je pense que je peux dire en toute sécurité que votre problème est double.

Apprenez à écrire les bons tests.

Vous dites que vous avez près d'un millier de tests et que vous avez 120 projets. En supposant qu'au plus la moitié de ces projets sont des projets de test, vous disposez de 1000 tests pour 60 projets de code de production. Cela vous donne environ 16-17 tests par projet !!!

C'est probablement la quantité de tests que je devrais couvrir environ 1-2 classes dans un système de production. Donc, à moins que vous n'ayez que 1-2 classes dans chaque projet (auquel cas la structure de votre projet est trop fine) vos tests sont trop gros, ils couvrent trop de terrain. Vous dites que c'est le premier projet que vous faites correctement TDD. Disons que les chiffres que vous présentez indiquent que ce n'est pas le cas, vous ne faites pas de propriété TDD.

Vous devez apprendre à écrire les bons tests, ce qui signifie probablement que vous devez apprendre à rendre le code testable en premier lieu. Si vous ne pouvez pas trouver l'expérience au sein de l'équipe pour le faire, je vous suggère d'embaucher de l'aide de l'extérieur, par ex. sous la forme d'un ou deux consultants aidant votre équipe sur une durée de 2-3 mois à apprendre à écrire du code testable, et de petits tests unitaires minimaux.

À titre de comparaison, sur le projet .NET sur lequel je travaille actuellement, nous pouvons exécuter environ 500 tests unitaires en moins de 10 secondes (et cela n'a même pas été mesuré sur une machine de haute spécification). Si c'étaient vos chiffres, vous n'auriez pas peur de les exécuter localement de temps en temps.

Apprenez à gérer la structure du projet.

Vous avez divisé la solution en 120 projets. C'est selon mes normes une quantité stupéfiante de projets.

Donc, s'il est logique d'avoir réellement cette quantité de projets (ce que j'ai l'impression que ce n'est pas le cas - mais votre question ne fournit pas suffisamment d'informations pour porter un jugement qualifié à ce sujet), vous devez diviser les projets en composants plus petits qui peut être construit, versionné et déployé séparément. Ainsi, lorsqu'un développeur exécute la suite de tests unitaire, il n'a qu'à exécuter les tests relatifs au composant sur lequel il travaille actuellement. Le serveur de build doit veiller à vérifier que tout s'intègre correctement.

Mais scinder un projet en plusieurs composants construits, versionnés et déployés séparément nécessite, selon mon expérience, une équipe de développement très mature, une équipe plus mature que j'ai l'impression que votre équipe l'est.

Mais en tout cas, vous devez faire quelque chose au sujet de la structure du projet. Soit divisez les projets en composants séparés, soit commencez à fusionner les projets.

Demandez-vous si vous avez vraiment besoin de 120 projets?

p.s. Vous voudrez peut-être consulter NCrunch. Il s'agit d'un plug-in Visual Studio qui exécute automatiquement votre test en arrière-plan.

1
Pete

Problèmes que j'ai vus:

a) Utiliser IOC pour construire des éléments de test. 70 secondes -> 7 secondes en supprimant Container.

b) Ne pas se moquer de toutes les classes. Gardez vos tests unitaires à un seul élément. J'ai vu des tests qui parcourent plusieurs classes. Ce ne sont pas des tests unitaires et beaucoup plus susceptibles de casser.

c) Profilez-les pour savoir ce qui se passait. J'ai trouvé que le constructeur construisait des trucs dont je n'avais pas besoin, alors je l'ai localisé et réduit les temps d'exécution.

d) Profil. peut-être que le code n'est pas si bon et que vous pouvez gagner en efficacité grâce à un examen.

e) Supprimez les dépendances. Garder votre test exécutable petit réduira le temps de chargement. Utilisez une bibliothèque d'interface et des conteneurs IOC pour exécuter votre solution finale, mais vos principaux projets de test ne doivent avoir que la bibliothèque d'interface définie. Cela garantit la séparation, facilite le test et rend également votre test empreinte plus petite.

0
Waratah

Les tests JUnit doivent normalement être rapides, mais certains d'entre eux doivent simplement prendre un certain temps à s'exécuter.

Par exemple, les tests de base de données prennent généralement un certain temps pour s'initialiser et se terminer.

Si vous avez des centaines de tests, même s'ils sont rapides, ils nécessitent beaucoup de temps pour s'exécuter en raison de leur nombre.

Ce qui peut être fait, c'est:

1) Identifiez les tests cruciaux. Celles des parties les plus importantes des bibliothèques et celles qui sont les plus susceptibles d'échouer après les modifications. Seuls ces tests doivent toujours être exécutés lors de la compilation. Si du code est souvent cassé, ses tests devraient être obligatoires même s'ils sont longs à exécuter, de l'autre côté, si une partie du logiciel n'a jamais causé de problème, vous pouvez ignorer les tests en toute sécurité sur chaque build.

2) Préparez le serveur d'intégration continue, qui exécutera tous les tests en arrière-plan. C'est à vous de décider si vous décidez de construire toutes les heures ou de construire après chaque commit (le second n'a de sens que si vous voulez détecter automatiquement le commit qui a causé le problème).

0
Danubian Sailor

Je ressens votre douleur et je suis tombé sur plusieurs endroits où la vitesse de construction peut être beaucoup améliorée. Cependant, le nombre de choses que je recommande est de mesurer à un détail granulaire pour déterminer où votre construction prend le plus de temps. Par exemple, j'ai une build avec environ 30 projets qui prend un peu plus d'une minute à exécuter. Cependant, ce n'est qu'une partie de l'image. Je sais également quels projets prennent le plus de temps à construire, ce qui aide à concentrer mes efforts.

Les choses qui mangent du temps de construction:

  • Téléchargement de packages (Nuget pour C #, Maven pour Java, Gem pour Ruby, etc.)
  • Copie de grandes quantités de fichiers sur le système de fichiers (exemple: fichiers de support GDAL)
  • Ouverture de connexions à la base de données (certaines prennent plus d'une seconde par connexion pour négocier)
  • Code basé sur la réflexion
  • Code généré automatiquement
  • Utilisation d'exceptions pour contrôler le flux du programme

Les bibliothèques factices utilisent la réflexion ou injectent du code à l'aide de bibliothèques de bytecode pour générer la simulation pour vous. Bien que cela soit très pratique, il consomme du temps de test. Si vous générez des simulations à l'intérieur d'une boucle dans votre test, cela peut ajouter un temps mesurable aux tests unitaires.

Il existe des moyens de résoudre les problèmes:

  • Déplacer les tests impliquant une base de données vers l'intégration (c'est-à-dire uniquement sur le serveur de génération CI)
  • Évitez de créer des mocks en boucles dans vos tests. En fait, évitez simplement les boucles dans vos tests. Vous pouvez probablement obtenir les mêmes résultats en utilisant un test paramétré dans ce cas.
  • Envisagez de diviser votre solution massive en solutions distinctes

Lorsque votre solution contient plus de 100 projets, vous disposez d'une combinaison de code de bibliothèque, de tests et de code d'application. Chacune des bibliothèques peut être sa propre solution avec ses tests associés. Jet Brains Team City est un serveur de build CI qui se double d'un serveur Nuget - et je suis sûr que ce n'est pas le seul. Cela vous donne la flexibilité de déplacer les bibliothèques qui ne sont probablement pas souvent modifiées vers leurs propres solutions/projets et d'utiliser Nuget pour résoudre les dépendances de votre code d'application. Des solutions plus petites signifient que vous pouvez apporter vos modifications à une bibliothèque rapidement et sans tracas, et profiter des avantages de la solution principale.

0
Berin Loritsch