Fractionnement des tests d'unités par exigence ou méthode

16

Tout d'abord, excuses pour le titre, je ne pouvais pas penser à la façon la plus simple de l'expliquer!

J'ai une méthode pour laquelle je veux écrire des tests unitaires. Je vais le garder assez générique car je ne veux pas discuter de l'implémentation de la méthode, juste son test. La méthode est la suivante:

public void HandleItem(item a)
{         
     CreateNewItem();
     UpdateStatusOnPreviousItem();
     SetNextRunDate();
}

Cette classe a donc une méthode publique qui appelle ensuite des méthodes privées pour exécuter la logique.

Donc, lors de l'écriture du test unitaire, je veux vérifier que les trois choses ont été faites. Comme ils sont tous appelés dans la même manche, j'ai pensé que je pouvais le faire en un seul test:

public void GivenItem_WhenRun_Thenxxxxx
{
     HandleItem(item);
     // Assert item has been created
     // Assert status has been set on the previous item
     // Assert run date has been set
}

Mais je pensais que je pouvais aussi l'écrire en trois tests distincts:

public void GivenItem_WhenRun_ThenItemIsCreated()
{
    HandleItem(item);
}

public void GivenItem_WhenRun_ThenStatusIsUpdatedOnPreviousItem()
{
   HandleItem(item);
}

public void GivenItem_WhenRun_ThenRunDateIsSet()
{
     HandleItem(item);
}

Donc, cela me semble plus agréable car il répertorie essentiellement les exigences, mais les trois sont liés et nécessitent exactement le même travail effectué sur la méthode testée, donc j'exécute le même code 3 fois.

Y a-t-il une approche recommandée à adopter avec cela?

Merci

ADringer
la source

Réponses:

29

Il existe une différence subtile entre les deux approches. Dans le premier cas, lorsque le premier Assertéchoue, les deux autres ne sont plus exécutés. Dans le second cas, les trois tests sont toujours exécutés, même en cas d'échec. Selon la nature de la fonctionnalité testée, celle-ci peut ou ne convient pas à votre cas:

  • s'il est logique d'exécuter les trois assertions indépendamment les unes des autres, car lorsque l'une échoue, les deux autres peuvent toujours ne pas échouer, alors la deuxième approche a l'avantage d'obtenir les résultats de test complets pour les 3 tests en une seule fois. Cela peut être bénéfique si vous avez des temps de construction notables, car cela vous permet de corriger jusqu'à 3 erreurs à la fois avant de faire la prochaine génération.

  • si, cependant, un échec du premier test implique toujours que les deux autres tests échouent également, alors il est probablement préférable d'utiliser la première approche (car cela n'a pas beaucoup de sens d'exécuter un test si vous le savez déjà au préalable, il le fera). échouer).

Doc Brown
la source
2
+1, bon point. Il ne m'est pas venu à l'esprit que les temps de construction peuvent également être un goulot d'étranglement.
Kilian Foth du
1
@KilianFoth: Vous ne travaillez pas assez souvent en C ++ :(
Matthieu M.
1
@MatthieuM .: pour être honnête, la question est étiquetée "C #"
Doc Brown
10

Réponse courte: Il est beaucoup plus important que vos tests couvrent toutes les fonctionnalités que la façon dont ils le font.

Réponse plus longue: si vous souhaitez toujours choisir parmi ces solutions largement équivalentes, vous pouvez utiliser des critères auxiliaires pour ce qui est le mieux. Par exemple,

  • lisibilité: si la méthode fait beaucoup de choses qui ne sont pas étroitement liées, un test combiné peut être difficile à comprendre. Cependant, la méthode elle-même peut également être difficile à comprendre, alors vous devriez peut-être refactoriser la méthode plutôt que le test!)
  • efficacité: si l'exécution de la méthode prend beaucoup de temps, cela pourrait être une faible raison de combiner les trois vérifications pour gagner du temps
  • efficacité2: si l'exécution du code de configuration de votre framework prend beaucoup de temps, cela pourrait également être une faible raison pour éviter plusieurs méthodes de test. (Cependant, si cela pose vraiment un problème, yuo devrait probablement corriger ou modifier votre configuration de test - les tests de régression perdent une grande partie de leur valeur si vous ne pouvez pas les exécuter à la vitesse de l'éclair.)
Kilian Foth
la source
2

Utilisez un appel de méthode avec plusieurs assertions. Voici pourquoi:

Lorsque vous testez HandleItem (a), vous testez que la méthode a mis l'élément dans l'état correct. Au lieu de "une affirmation par test", pensez à "un concept logique par test".

Question: Si un CreateNewItem échoue, mais que les deux autres méthodes réussissent, cela signifie-t-il que HandleItem s'est terminé avec succès? Je suppose que non.

Avec plusieurs assertions (avec les messages appropriés), vous saurez exactement ce qui a échoué. Vous testez généralement une méthode plusieurs fois pour plusieurs entrées ou états d'entrée, pour ne pas éviter plusieurs assertions.

OMI, ces questions sont généralement le signe d'autre chose. C'est un signe que HandleItem n'est pas vraiment quelque chose que vous pouvez "tester unitaire" car il semble simplement déléguer à d'autres méthodes. Lorsque vous validez simplement que HandleItem appelle correctement d'autres méthodes, il devient plus un candidat au test d'intégration (auquel cas vous auriez encore 3 assertions).

Vous voudrez peut-être envisager de rendre les 3 autres méthodes publiques et de les tester indépendamment. Ou même les extraire dans une autre classe.

Kevin
la source
0

Utilisez la 2e approche. Avec votre 1ère approche, si le test échoue, vous ne saurez pas pourquoi tout de suite, car cela pourrait être l'une des 3 fonctions qui ont échoué. Avec la deuxième approche, vous saurez immédiatement où le problème s'est produit. Vous pouvez mettre du code en double dans votre fonction de configuration de test.

Eternal21
la source
-1

À mon humble avis, vous devriez tester les trois parties de cette méthode séparément afin de savoir plus précisément où les choses vont mal quand elles le font tout en évitant de parcourir deux fois la même partie de votre code.

SuperUser
la source
-2

Je ne pense pas qu'il y ait de bonnes raisons d'écrire des méthodes de test distinctes pour votre cas d'utilisation. Si vous souhaitez obtenir les résultats des trois conditions variables, vous pouvez tester les trois et imprimer leurs échecs en les concaténant dans a stringet en vérifiant si la chaîne est toujours vide une fois le test terminé. En les conservant tous dans la même méthode, vos conditions et messages d'échec documentent la post-condition attendue de la méthode en un seul endroit, plutôt que de la diviser en trois méthodes qui pourraient être séparées ultérieurement.

Cela signifie que votre test unique aura plus de code, mais si vous avez autant de post-conditions que c'est un problème, vous voudrez probablement refactoriser vos méthodes de test pour tester des méthodes individuelles à l'intérieur de HandleItemtoute façon.

IllusiveBrian
la source