78 votes

Comment introduire les tests unitaires dans un grand héritage (C/C++) de la base de code?

Nous avons une grande, application multi-plateforme écrit en C. (avec un petit, mais de plus en plus de C++), Il a évolué au fil des années avec de nombreuses fonctionnalités que vous attendez d'une grande application C/C++:

  • #ifdef l'enfer
  • De gros fichiers qui font qu'il est difficile d'isoler de code de tests
  • Les fonctions qui sont trop complexes pour être facilement testables

Étant donné que ce code est destiné pour les systèmes embarqués, c'est beaucoup de frais généraux pour l'exécuter sur la cible réelle. Nous voudrions donc faire plus de notre développement et de tests dans le rapide de cycles, sur un système local. Mais nous voudrions éviter la stratégie classique de "copier/coller dans un .c fichier sur votre système, corriger les bugs, copier/coller le dos". Si les développeurs vont aller de la peine pour ce faire, nous aimerions être en mesure de recréer les mêmes tests plus tard, et de les exécuter dans un mode automatisé.

Voici notre problème: afin de refactoriser le code pour être plus modulaire, nous avons besoin de plus de tests. Mais dans le but d'introduire des tests unitaires automatisés, nous en avons besoin pour être plus modulaire.

Un problème est que depuis que nos fichiers sont volumineux, on pourrait avoir une fonction à l'intérieur d'un fichier qui appelle une fonction dans le même fichier que nous avons besoin de stub pour faire un bon test de l'unité. Il semble que ce serait moins un problème que notre code devient de plus en plus modulaire, mais c'est un long chemin.

Une chose que nous avons pensé à faire était de marquage "connu pour être testable" source code avec des commentaires. Ensuite, on pourrait écrire un script d'analyse des fichiers source pour les tests de code, le compiler dans un fichier séparé, et faire le lien avec les tests unitaires. Nous pourrions introduire lentement les tests unitaires que nous corriger les défauts et ajouter plus de fonctionnalités.

Toutefois, il est à craindre que le maintien de ce régime (avec toutes les fonctions stub) deviendra trop de tracas, et les développeurs d'arrêter de maintenir les tests unitaires. Une autre approche consiste à utiliser un outil qui génère automatiquement les stubs pour tout le code, et le lien du fichier. (le seul outil que nous avons trouvée pour ce faire est cher produit commercial), Mais cette approche semble exiger que tout notre code plus modulaire avant même de commencer, puisque seuls les appels externes peuvent être écrasé.

Personnellement, je préfère les développeurs de penser au sujet de leurs dépendances externes et intelligemment écrire leurs propres talons. Mais ce qui pourrait être écrasante pour écraser toutes les dépendances pour une horrible et envahis par la végétation, de 10 000 ligne de fichier. Il peut être difficile de convaincre les développeurs qu'ils ont besoin pour maintenir les stubs pour toutes leurs dépendances externes, mais est-ce la bonne façon de le faire? (Un autre argument que j'ai entendu, c'est que le responsable d'un sous-système doit maintenir les talons de ses sous-systèmes. Mais je me demande si "forcer" les développeurs d'écrire leurs propres talons conduirait à de meilleurs tests unitaires?)

Le #ifdefs, bien sûr, ajouter une toute autre dimension au problème.

Nous avons regardé plusieurs C/C++ en fonction de l'unité de frameworks de test, et il y a beaucoup d'options qui ont l'air bien. Mais nous n'avons pas trouvé de quoi faciliter la transition de "boules de code sans tests unitaires" à "l'unité de code de tests".

Voici donc mes questions à quelqu'un d'autre qui a été à travers ce:

  • Ce qui est un bon point de départ? Nous allons dans la bonne direction, ou bien sommes-nous manque quelque chose d'évident?
  • Les outils qui pourraient être utiles pour aider à la transition? (de préférence gratuit et open-source, car notre budget est à peu près "zéro")

Remarque, notre environnement est sous Linux/UNIX, donc on ne peut pas utiliser n'importe quel Windows-seulement les outils.

53voto

S.Lott Points 207588

"nous n'avons pas trouvé de quoi faciliter la transition de "boules de code sans tests unitaires" à "l'unité de code de tests'."

Quelle triste-pas de solution miracle, juste un beaucoup de travail, de corriger les années de l'accumulation de la dette technique.

Il n'est pas facile de transition. Vous avez un grand, complexe, grave problème.

Vous ne pouvez le résoudre en petites étapes. Chaque petite étape consiste à la suivante.

  1. Choisir un discret morceau de code qui est absolument essentiel. (Ne pas grignoter sur les bords à la poubelle.) Choisir un composant qui est important et, d'une certaine manière, peut être taillé de le reste. Alors qu'une seule fonction est idéale, il pourrait être un enchevêtrement de cluster de fonctions ou peut-être un ensemble de fichiers de fonctions. C'est bien de commencer avec quelque chose de moins que parfait pour vos composants testables.

  2. Comprendre ce que c'est censé faire. Comprendre ce que son interface est censé être. Pour ce faire, vous pourriez avoir à faire quelques refactoring pour faire de votre cible morceau fait discret.

  3. Écrire un "ensemble" test d'intégration que -- pour -- tests discret morceau de code plus ou moins comme il a été trouvé. Obtenir ce passer avant d'essayer et de changer quoi que ce soit significatif.

  4. Refactoriser le code dans bien rangé, testable, les unités qui font mieux le sens de votre actuelle des boules de poil. Vous allez avoir à maintenir une certaine compatibilité (pour l'instant) avec l'ensemble de votre test d'intégration.

  5. Écrire des tests unitaires pour les nouvelles unités.

  6. Une fois toutes les passes, de démantèlement de l'ancienne API et de corriger ce qui va être brisé par le changement. Si nécessaire, la reprise de l'original test d'intégration; il teste l'ancienne API, vous voulez tester la nouvelle API.

Itérer.

26voto

George V. Reilly Points 5471

Michael Plumes a écrit la bible sur ce, de Travailler de façon Efficace avec le Code existant

9voto

Mon peu d'expérience avec le code existant et l'introduction de tests serait de créer des "tests de Caractérisation". Vous commencez à créer des tests avec l'entrée connues et ensuite obtenir la sortie. Ces tests sont utiles pour les méthodes/classes que vous ne savez pas ce qu'ils font réellement, mais vous savez qu'ils sont au travail.

Cependant, il y a parfois des quand il est presque impossible de créer des tests unitaires (même les tests de caractérisation). Sur ce cas, j'attaque le problème par le biais de tests d'acceptation (Fitnesse dans ce cas).

Vous créez le tas de classes nécessaires pour tester une fonctionnalité et le vérifier sur fitnesse. Il est similaire à la "les tests de caractérisation", mais c'est d'un niveau plus élevé.

7voto

iain Points 4876

Comme dit George Travailler Efficacement avec le Code de Legs est la bible pour ce genre de chose.

Toutefois, la seule façon d'autres personnes dans votre équipe va acheter dans est si elles voient l'avantage pour eux personnellement de garder les épreuves de travail.

Pour réaliser cela, vous avez besoin d'un framework de test avec qui est aussi simple que possible à utiliser. Pour d'autres développeurs, vous prenez vos tests comme des exemples d'écrire leur propre. Si elles n'ont pas les tests unitaires de l'expérience, ne vous attendez pas à passer du temps à apprendre un cadre, ils seront probablement de voir l'écriture de l'unité des tests comme un frein à leur développement, ne sachant pas si le cadre est une excuse pour ignorer les tests.

Passer un peu de temps sur l'intégration continue à l'aide d'un régulateur de vitesse, luntbuild, cdash etc. Si votre code est automatiquement compilé tous les soirs et les tests s'exécutent alors les développeurs vont commencer à voir les avantages si les tests unitaires repérer les bugs avant d'assurance de la qualité.

Une chose à encourager, est partagé code de la propriété. Si un développeur passe leur code et les pauses de quelqu'un d'autre test, ils ne devraient pas s'attendre à ce que la personne à fixer leur test, ils doivent étudier pourquoi le test ne fonctionne pas et de corriger eux-mêmes. Dans mon expérience, c'est l'une des choses les plus difficiles à atteindre.

La plupart des développeurs d'écrire une forme de test de l'unité, quelques fois un petit morceau de jeter le code ils ne vérifient pas dans ou intégrer le construire. Faire de l'intégration de ces derniers dans la construction facile et les développeurs de commencer à acheter.

Mon approche consiste à ajouter des tests pour les nouveau et que le code est modifié, parfois vous ne pouvez pas en ajouter autant de tests détaillés comme vous le souhaitez sans découplage trop de code existant, err sur le côté de la pratique.

Le seul endroit où j'insiste sur les tests unitaires est sur la plate-forme de code spécifique. Où #ifdefs sont les remplace par de plate-forme spécifique niveau plus élevé de fonctions/classes, celles-ci doivent être testés sur toutes les plates-formes avec les mêmes tests. Cela permet d'économiser des charges de temps à ajouter de nouvelles plates-formes.

Nous utiliser boost::test à la structure de notre test, la simple auto-enregistrement par les fonctions d'écriture de test facile.

Ces sont enveloppés dans CTest (partie de CMake) cela va à un groupe de tests unitaires exécutables à la fois et génère un rapport simple.

Notre nightly build est automatisé avec ant et luntbuild (ant colles c++, .net et java s'appuie)

Bientôt, je l'espère, pour ajouter de déploiement automatisé et des tests fonctionnels pour le construire.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X