Question

Je suis sûr que la plupart d’entre vous écrivez de nombreux tests automatisés et que vous avez également rencontré quelques pièges courants lors des tests unitaires.

Ma question est la suivante: suivez-vous des règles de conduite pour la rédaction des tests afin d'éviter des problèmes à l'avenir? Pour être plus précis: quelles sont les propriétés de bons tests unitaires ou comment écrivez-vous vos tests?

Les suggestions indépendantes de la langue sont encouragées.

Était-ce utile?

La solution

Permettez-moi de commencer par brancher les sources - Unité pragmatique Tester en Java avec JUnit (une version avec C # -Nunit aussi .. mais j’ai celle-là .. elle est agnostique pour la plupart. Recommandé.)

Les bons tests doivent être UN VOYAGE (l'acronyme n'est pas assez collant. J'ai un imprimé de la feuille de triche dans le livre que je devais sortir pour m'assurer de bien le faire .. )

  • Automatique : l'invocation des tests ainsi que la vérification des résultats pour PASS / FAIL doivent être automatiques
  • Approfondi : couverture; Bien que les bogues aient tendance à se regrouper autour de certaines régions du code, assurez-vous de tester tous les chemins et scénarios de clé .. Utilisez des outils si vous devez connaître les régions non testées
  • Répétable : les tests doivent produire les mêmes résultats à chaque fois. à chaque fois. Les tests ne doivent pas reposer sur des paramètres incontrôlables.
  • Indépendant : très important.
    • Les tests ne doivent tester qu'une chose à la fois. Plusieurs assertions sont acceptables tant qu'elles testent toutes une caractéristique / un comportement. Lorsqu'un test échoue, il doit localiser le problème.
    • Les tests ne doivent pas s'appuyer l'un sur l'autre - Isolés. Aucune hypothèse sur l'ordre d'exécution du test. Assurez-vous que chaque table de test est propre avant chaque test en utilisant le programme d'installation / démontage de manière appropriée
  • Professionnel : à long terme, vous aurez autant de code de test que de production (sinon plus). Par conséquent, respectez les mêmes normes de bonne conception pour votre code de test. Méthodes-classes bien factorisées avec des noms révélateurs d'intention, pas de duplication, tests avec des noms bien, etc.

  • Les bons tests fonctionnent également Rapide . tout test qui prend plus d'une demi-seconde à exécuter .. doit être travaillé. Plus la suite de tests dure longtemps, moins elle sera exécutée fréquemment. Plus le développeur essaiera de se faufiler entre les exécutions… si quelque chose se brise… il faudra plus de temps pour déterminer quel changement est le coupable.

Mise à jour 2010-08:

  • Lisible : cela peut être considéré comme faisant partie de Professional. Toutefois, il ne faut pas trop insister sur lui. Un test à l'acide consisterait à rechercher une personne ne faisant pas partie de votre équipe et à lui demander de déterminer le comportement testé en l'espace de quelques minutes. Les tests doivent être gérés de la même manière que le code de production: facilitez-vous la lecture même si cela nécessite plus d'effort. Les tests doivent être symétriques (suivre un schéma) et concis (tester un comportement à la fois). Utilisez une convention de dénomination cohérente (par exemple, le style TestDox). Évitez d’encombrer le test avec des "détails accessoires", devenez minimaliste.

En dehors de ceux-ci, la plupart des autres sont des directives qui réduisent le travail à faible bénéfice: par exemple. "Ne testez pas le code que vous ne possédez pas" (par exemple, des DLL tierces). N'essayez pas de tester les accesseurs et les setters. Surveillez le rapport coût / bénéfice ou la probabilité de défaut.

Autres conseils

  1. N'écrivez pas de tests ginormous. Comme l'indique l'unité dans le test d'unité, définissez chacun d'eux comme atomique et isolé . comme possible. Si nécessaire, créez des conditions préalables à l'aide d'objets fictifs, plutôt que de recréer manuellement une trop grande partie de l'environnement utilisateur standard.
  2. Ne testez pas les éléments qui fonctionnent. Évitez de tester les classes d'un fournisseur tiers, en particulier celui fournissant les API principales de la structure dans laquelle vous codez. Par exemple, ne testez pas. ajout d'un élément à la classe Hashtable du fournisseur.
  3. Pensez à utiliser un outil de couverture de code , tel que NCover, pour vous aider à découvrir les cas critiques que vous n'avez pas encore testés.
  4. Essayez d'écrire le test avant l'implémentation. Considérez le test comme davantage une spécification que votre implémentation respectera. Cf. également le développement axé sur le comportement, une branche plus spécifique du développement axé sur les tests.
  5. Soyez cohérent. Si vous n'écrivez que des tests pour une partie de votre code, ce n'est pas utile. Si vous travaillez en équipe et que certains ou tous les autres n'écrivent pas de tests, ce n'est pas très utile non plus. Convainquez-vous et tous les autres de l’importance des tests (et des propriétés permettant d’économiser du temps ), ou ne dérange pas.

La plupart des réponses semblent ici concerner les meilleures pratiques en matière de tests unitaires en général (quand, où, pourquoi et quoi), plutôt que d’écrire les tests eux-mêmes (comment). Comme la question semblait assez précise sur le "comment" partie, je pensais que je posterais cela, pris à partir d'un "sac brun" présentation que j'ai effectuée dans mon entreprise.

Les 5 tests de la loi d'écriture de Womp:

1. Utilisez des noms de méthodes de test descriptifs longs.

   - Map_DefaultConstructorShouldCreateEmptyGisMap()
   - ShouldAlwaysDelegateXMLCorrectlyToTheCustomHandlers()
   - Dog_Object_Should_Eat_Homework_Object_When_Hungry()

2. Ecrivez vos tests dans un Style Arranger / Act / Assert .

  • Alors que cette stratégie organisationnelle a été autour pendant un certain temps et appelé beaucoup de choses, l'introduction de la "AAA" acronyme a récemment été un excellent moyen de faire passer cela. Rendre tous vos tests compatibles avec Le style AAA les rend faciles à lire et à maintenir.

3. Fournissez toujours un message d'échec avec vos affirmations.

Assert.That(x == 2 && y == 2, "An incorrect number of begin/end element 
processing events was raised by the XElementSerializer");
  • Une pratique simple, mais enrichissante, qui met en évidence dans votre application Runner ce qui a échoué Si vous ne fournissez pas de message, vous obtiendrez généralement un résultat du type "Expected true, was false". dans votre sortie d'échec, ce qui vous oblige à lire le test pour savoir ce qui ne va pas.

4. Commentez la raison du test - Quelle est l'hypothèse commerciale?

  /// A layer cannot be constructed with a null gisLayer, as every function 
  /// in the Layer class assumes that a valid gisLayer is present.
  [Test]
  public void ShouldNotAllowConstructionWithANullGisLayer()
  {
  }
  • Cela peut sembler évident, mais cela la pratique protégera l'intégrité de vos tests de personnes qui ne le font pas comprendre la raison derrière le test en premier lieu. J'ai vu beaucoup les tests sont supprimés ou modifiés étaient parfaitement bien, tout simplement parce que la personne n'a pas compris le hypothèses que le test était vérification.
  • Si le test est trivial ou la méthode nom est suffisamment descriptif, il peut être autorisé à quitter le commenter.

5. Chaque test doit toujours inverser l’état des ressources qu’il touche

.
  • Utilisez des faux si possible pour éviter traiter avec des ressources réelles.
  • Le nettoyage doit être effectué lors du test. niveau. Les tests ne doivent avoir aucun confiance dans l'ordre d'exécution.

Gardez ces objectifs à l'esprit (adapté du livre xUnit Test Patterns de Meszaros)

  • Les tests devraient réduire les risques, pas présentez-le.
  • Les tests doivent être faciles à exécuter.
  • Les tests doivent être faciles à gérer car le système évolue autour d'eux

Quelques choses pour rendre cela plus facile:

  • Les tests doivent uniquement échouer à cause de une raison.
  • Les tests ne doivent tester qu’une chose
  • Minimiser les dépendances de test (non dépendances sur des bases de données, fichiers, interface utilisateur etc.)

N'oubliez pas que vous pouvez également effectuer des tests d'intégration avec votre infrastructure xUnit , mais séparez les tests d'intégration et les tests unitaires

Les tests doivent être isolés. Un test ne devrait pas dépendre d'un autre. Même plus loin, un test ne devrait pas s'appuyer sur des systèmes externes. En d'autres termes, testez votre code et non le code dont votre code dépend. Vous pouvez tester ces interactions dans le cadre de vos tests d'intégration ou fonctionnels.

Certaines propriétés des grands tests unitaires:

  • Lorsqu'un test échoue, le problème doit être immédiatement identifié. Si vous devez utiliser le débogueur pour localiser le problème, vos tests ne sont pas assez détaillés. Avoir exactement une assertion par test aide ici.

  • Lors de la refactorisation, aucun test ne doit échouer.

  • Les tests doivent être exécutés si rapidement que vous n’hésitez pas à les exécuter.

  • Tous les tests doivent toujours réussir. pas de résultats non déterministes.

  • Les tests unitaires doivent être bien factorisés, tout comme votre code de production.

@Alotor: Si vous suggérez qu'une bibliothèque ne devrait avoir que des tests unitaires sur son API externe, je ne suis pas d'accord. Je veux des tests unitaires pour chaque classe, y compris des classes que je n'expose pas aux appelants externes. (Cependant, si je ressens le besoin d'écrire des tests pour méthodes privées, alors je dois refactoriser. )

EDIT: Il y avait un commentaire sur la duplication causée par "une assertion par test". Plus précisément, si vous avez du code pour configurer un scénario et que vous souhaitez ensuite faire plusieurs assertions à ce sujet, mais n’ayant qu’une assertion par test, vous pouvez dupliquer la configuration sur plusieurs tests.

Je ne prends pas cette approche. Au lieu de cela, j'utilise des appareils de test par scénario . Voici un exemple approximatif:

[TestFixture]
public class StackTests
{
    [TestFixture]
    public class EmptyTests
    {
        Stack<int> _stack;

        [TestSetup]
        public void TestSetup()
        {
            _stack = new Stack<int>();
        }

        [TestMethod]
        [ExpectedException (typeof(Exception))]
        public void PopFails()
        {
            _stack.Pop();
        }

        [TestMethod]
        public void IsEmpty()
        {
            Assert(_stack.IsEmpty());
        }
    }

    [TestFixture]
    public class PushedOneTests
    {
        Stack<int> _stack;

        [TestSetup]
        public void TestSetup()
        {
            _stack = new Stack<int>();
            _stack.Push(7);
        }

        // Tests for one item on the stack...
    }
}

Ce que vous recherchez, c’est la délimitation des comportements de la classe sous test.

  1. Vérification des comportements attendus.
  2. Vérification des cas d'erreur.
  3. Couverture de tous les chemins de code de la classe.
  4. Exercice de toutes les fonctions membres de la classe.

L'intention de base est d'accroître votre confiance dans le comportement de la classe.

Ceci est particulièrement utile lorsque vous envisagez de refactoriser votre code. Martin Fowler a publié un article intéressant sur les tests effectués sur son site Web.

HTH.

acclamations,

Rob

Le test devrait échouer à l'origine. Ensuite, vous devez écrire le code qui les fait passer, sinon vous courez le risque de rédiger un test qui a des problèmes et qui réussit toujours.

J'aime l’acronyme Right BICEP du susdit Manuel de tests unitaires pragmatiques :

  • Droite : les résultats sont-ils corrects ?
  • B : toutes les conditions b sont-elles correctes?
  • I : pouvons-nous vérifier les i relations inverses?
  • C : pouvons-nous c vérifier les résultats par d'autres moyens?
  • E : Pouvons-nous forcer e des conditions sombres?
  • P : les p caractéristiques de performance sont-elles dans les limites?

Personnellement, j’ai le sentiment que vous pouvez aller assez loin en vérifiant que vous obtenez les bons résultats (1 + 1 doit en renvoyer 2 dans une fonction addition), en essayant toutes les conditions aux limites auxquelles vous pouvez penser (par exemple, en utilisant deux nombres). la somme est supérieure à la valeur entière max dans la fonction add) et forçant des conditions d'erreur telles que des défaillances du réseau.

Les bons tests doivent être maintenables.

Je n'ai pas encore compris comment faire cela dans les environnements complexes.

Tous les manuels commencent à se décoller lorsque votre base de code commence à atteindre dans des centaines de milliers ou des millions de lignes de code.

  • Les interactions d'équipe explosent
  • nombre de tests élémentaires explosant
  • les interactions entre les composants explosent.
  • le temps de construire tous les unittests devient une partie importante du temps de construction
  • un changement d'API peut avoir des répercussions sur des centaines de cas de test. Même si le changement de code de production était facile.
  • le nombre d'événements requis pour ordonner les processus dans le bon état augmente, ce qui augmente à son tour le temps d'exécution du test.

Une bonne architecture peut contrôler l’explosion des interactions, mais inévitablement les systèmes deviennent plus complexes, le système de test automatisé évolue avec.

C’est là que vous commencez à faire face à des compromis:

  • teste uniquement l’API externe, sinon la refactorisation des résultats internes entraîne un remaniement important du scénario de test.
  • La configuration et la suppression de chaque test deviennent plus compliquées à mesure qu'un sous-système encapsulé conserve plus d'état.
  • la compilation nocturne et l'exécution automatisée des tests atteignent des heures.
  • l’augmentation des temps de compilation et d’exécution signifie que les concepteurs n’exécutent pas ou ne veulent pas exécuter tous les tests
  • pour réduire les temps d’exécution des tests, considérez que l’ordonnancement des tests nécessite une réduction de la configuration et du démontage

Vous devez également décider:

Où stockez-vous les cas de test dans votre base de code?

  • comment documentez-vous vos cas de test?
  • les appareils de test peuvent-ils être réutilisés pour économiser la maintenance des cas de test?
  • que se passe-t-il lorsqu'une exécution de scénario de test nocturne échoue? Qui fait le triage?
  • Comment maintenez-vous les objets fictifs? Si vous avez 20 modules utilisant tous leur propre modèle d’API de journalisation factice, la modification de l’API se répercute rapidement. Non seulement les scénarios de test changent, mais les 20 objets fantaisie changent également. Ces 20 modules ont été écrits sur plusieurs années par différentes équipes. C’est un problème classique de réutilisation.
  • Les individus et leurs équipes comprennent la valeur des tests automatisés. Ils n’apprécient tout simplement pas la façon dont l’autre équipe le fait. : -)

Je pourrais continuer éternellement, mais ce que je veux dire, c'est que:

Les tests doivent être maintenables.

J'ai couvert ces principes il y a quelque temps dans Cet article de MSDN Magazine . que je pense qu'il est important pour tout développeur de lire.

La définition que je donne "bien" tests unitaires, s’ils possèdent les trois propriétés suivantes:

  • Ils sont lisibles (nommage, assertions, variables, longueur, complexité ..)
  • Ils sont maintenables (pas de logique, pas trop spécifiés, basés sur des états, refactorisés ..)
  • Ils sont dignes de confiance (testez la bonne chose, isolés, pas les tests d'intégration ..)
  • Le test d'unité ne fait que tester l'API externe de votre unité. Vous ne devez pas tester le comportement interne.
  • Chaque test d'un TestCase doit tester une (et une seule) méthode dans cette API.
    • Des cas de test supplémentaires doivent être inclus pour les cas d'échec.
  • Testez la couverture de vos tests: une fois l'unité testée, toutes les lignes de cette unité auraient dû être exécutées.

Jay Fields a un nombre de bons conseils sur la rédaction de tests unitaires et un message dans lequel il résume les conseils les plus importants . Vous y verrez que vous devez réfléchir de manière critique à votre contexte et juger si le conseil vaut la peine. Vous obtenez une tonne de réponses étonnantes ici, mais vous devez choisir celle qui convient le mieux à votre contexte. Essayez-les et refacturez-les simplement si ça sent mauvais pour vous.

Cordialement

Ne supposez jamais qu'une méthode triviale à 2 lignes fonctionnera. Écrire un test unitaire rapide est le seul moyen d'éviter que le test nul manquant, le signe moins égaré et / ou la subtile erreur de cadrage ne vous mordent, inévitablement lorsque vous avez encore moins de temps pour le faire que maintenant.

Je seconde le "A TRIP" répondre, sauf que les tests DEVRAIENT s’appuyer les uns sur les autres !!!

Pourquoi?

DRY - Ne vous répétez pas - s'applique également aux tests! Les dépendances de test peuvent aider à 1) économiser le temps de configuration, 2) économiser les ressources de luminaire et 3) identifier les défaillances. Bien sûr, cela est uniquement dû au fait que votre infrastructure de test prend en charge des dépendances de première classe. Autrement, je l’admets, ils sont mauvais.

Suivi http://www.iam.unibe.ch/~ scg / Research / JExample /

Les tests unitaires sont souvent basés sur des données ou des objets fictifs. J'aime écrire trois types de tests unitaires:

  • " transitoire " tests unitaires: ils créent leurs propres objets / données fictifs et testent leur fonction, mais détruisent tout et ne laissent aucune trace (comme aucune donnée dans une base de données de test)
  • " persistant " test unitaire: ils testent les fonctions de votre code en créant des objets / données qui seront nécessaires ultérieurement à des fonctions plus avancées pour leur propre test unitaire (évitant à ces fonctions avancées de recréer à chaque fois leur propre ensemble d'objets / données fictifs)
  • " persistant " tests unitaires: tests unitaires utilisant des objets / données fictifs déjà présents (car créés dans une autre session de test unitaire) par les tests unitaires persistants.

Il faut éviter de rejouer tout afin de pouvoir tester toutes les fonctions.

  • Je lance très souvent le troisième type car tous les objets / données fictifs sont déjà présents.
  • Je lance le deuxième type à chaque changement de modèle.
  • Je lance le premier pour vérifier de temps en temps les fonctions de base, pour vérifier les régressions de base.

Pensez aux deux types de tests et traitez-les différemment - tests fonctionnels et tests de performance.

Utilisez différentes entrées et mesures pour chacune. Vous devrez peut-être utiliser un logiciel différent pour chaque type de test.

J'utilise une convention de dénomination de test cohérente décrite par de Roy Osherove Normes de dénomination des tests unitaires Chaque méthode d'une classe de cas de test donnée a le style de dénomination suivant: MethodUnderTest_Scenario_ExpectedResult.

    La première section du nom du test est le nom de la méthode du système testé.
    Suivant est le scénario spécifique en cours de test.
    Enfin, voici les résultats de ce scénario.

Chaque section utilise Upper Camel Case et est délimitée par un score inférieur.

J'ai trouvé cela utile lors de l'exécution du test. Les tests sont regroupés par nom de la méthode testée. Et avoir une convention permet aux autres développeurs de comprendre l'intention du test.

J'ajoute également des paramètres au nom de la méthode si la méthode à tester a été surchargée.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top