Création de tests unitaires sur une couche CRUD d'une application, comment puis-je rendre les tests indépendants?

14

J'essaie donc de rendre mes tests unitaires aussi simples que possible, mais cela devient gênant lorsque je teste quelques méthodes simples d'ajout / suppression.

Pour la méthode add, je dois essentiellement créer un objet factice et l'ajouter, puis une fois le test réussi, je dois supprimer l'objet factice.

Et pour le test de suppression, je dois évidemment créer un objet factice pour pouvoir le supprimer.

Comme vous pouvez le voir si un test échoue, l'autre échouera également, car ils sont tous les deux nécessaires.

Même chose avec un système où vous auriez besoin d'écrire un test qui "annule une commande" ... eh bien, une commande fictive serait nécessaire pour annuler d'abord, cela ne va-t-il pas à l'encontre des directives des tests unitaires?

Comment les cas comme celui-ci doivent-ils être traités?

Kyle
la source
Vous pouvez également jeter un œil à cette question: programmers.stackexchange.com/questions/115455/…
Guven

Réponses:

13

Eh bien, il n'y a rien de mal à ce que vous faites. Plusieurs tests peuvent couvrir le même code; cela signifie simplement qu'un problème entraînera l'échec de plusieurs tests. Ce que vous voulez éviter, ce sont des tests qui dépendent des résultats d'autres tests. C'est-à-dire que votre test de suppression dépend de l'exécution du test d'ajout, et donc si vous exécutez votre test de suppression AVANT votre test d'ajout, il échouera. Pour éviter ce problème, assurez-vous que vous avez une "liste vierge" au début de chaque test, afin que ce qui se passe dans un test donné ne puisse pas affecter les tests suivants.

Une excellente façon de procéder consiste à exécuter les tests sur une base de données en mémoire.

Dans votre test d'ajout, créez une base de données vide, ajoutez l'objet et vérifiez qu'il a bien été ajouté.

Dans votre test de suppression, créez la base de données avec l'objet que vous supprimerez déjà. Supprimez l'objet et confirmez qu'il a été supprimé.

Soufflez la base de données dans votre code de démontage.

Adam Jaskiewicz
la source
La base de données en mémoire est juste rapide (en mémoire) et simple (en cours). Vous pouvez le faire avec n'importe quel magasin de données.
Paul Draper
3

Utilisez les transactions.

Si vous utilisez une base de données qui prend en charge les transactions, exécutez chaque test dans une transaction. Annulez la transaction à la fin du test. Ensuite, chaque test laissera la base de données inchangée.

Oui, pour annuler une commande, vous devrez d'abord en créer une. C'est très bien. Le test va d'abord créer une commande, puis l'annuler, puis vérifier que la commande est annulée.

Kevin Cline
la source
J'adore cette idée. Mis en œuvre avec grand effet aujourd'hui.
pimbrouwers
3

Vous le faites bien. Le seul et unique principe fondamental des tests unitaires est de couvrir chaque chemin de code que vous avez, afin que vous puissiez être sûr que votre code fait ce qu'il est censé faire, et continue de le faire après les modifications et les refactorisations. Garder les tests unitaires petits, simples et à usage unique est un objectif valable, mais ce n'est pas fondamental. Avoir un test qui appelle deux méthodes de relation de votre API n'est pas en soi discutable, en fait, comme vous le faites remarquer, c'est souvent nécessaire. L'inconvénient d'avoir des tests redondants est simplement qu'ils prennent plus de temps à écrire, mais comme presque tout dans le développement, c'est un compromis que vous devez faire tout le temps, et la meilleure solution n'est presque jamais l'un des points extrêmes.

Kilian Foth
la source
2

Les techniques de test de logiciels sont extrêmement variées, et plus vous vous renseignerez à leur sujet, vous commencerez à voir beaucoup de conseils différents (et parfois contradictoires). Il n'y a pas un seul «livre» pour passer.

Je pense que vous êtes dans une situation où vous avez vu des conseils pour les tests unitaires qui disent des choses comme

  • Chaque test doit être autonome et ne pas être affecté par d'autres tests
  • Chaque test unitaire doit tester une chose, et une seule chose
  • Les tests unitaires ne doivent pas toucher la base de données

etc. Et tout cela est vrai, selon la façon dont vous définissez le «test unitaire» .

Je définirais un «test unitaire» comme quelque chose comme: «un test qui exerce un élément de fonctionnalité pour une unité de code, isolé des autres composants dépendants».

Selon cette définition, ce que vous faites (si cela nécessite d'ajouter un enregistrement à une base de données avant de pouvoir exécuter le test) n'est pas du tout un `` test unitaire '', mais plutôt ce qu'on appelle communément un `` test d'intégration ''. (Un vrai test unitaire, selon ma définition, n'atteindra pas la base de données, vous n'aurez donc pas besoin d'ajouter un enregistrement avant de le supprimer.)

Un test d'intégration exercera une fonctionnalité qui utilise plusieurs composants (comme une interface utilisateur et une base de données), et les conseils qui s'appliqueraient aux tests unitaires ne s'appliquent pas nécessairement aux tests d'intégration.

Comme d'autres l'ont mentionné dans leurs réponses, ce que vous faites n'est pas nécessairement faux même si vous faites des choses contraires à certaines directives de test unitaire. Au lieu de cela, essayez de raisonner sur ce que vous testez réellement dans chaque méthode de test, et si vous trouvez que vous avez besoin de plusieurs composants pour satisfaire votre test, et que certains composants nécessitent une préconfiguration, alors allez-y et faites-le.

Mais surtout, comprenez qu'il existe de nombreux types de tests logiciels (tests unitaires, tests système, tests d'intégration, tests exploratoires, etc.), et n'essayez pas d'appliquer les conseils d'un type à tous les autres.

Eric King
la source
Voulez-vous dire que vous ne pouvez pas supprimer les tests unitaires de la base de données?
ChrisF
Si vous frappez la base de données, c'est (par définition) un test d'intégration, pas un test unitaire. Donc, en ce sens, non. Vous ne pouvez pas effectuer de «test unitaire» en supprimant une base de données. Ce que vous pouvez tester uniquement, c'est que lorsque le code que vous testez est invité à supprimer certaines données, il interagit correctement avec le module d'accès aux données.
Eric King
Mais le fait est que certaines personnes peuvent définir différemment le «test unitaire», nous devons donc être prudents lors de l'application des indications du «test unitaire», car les indications peuvent ne pas s'appliquer de la manière que nous pensons.
Eric King
1

C'est exactement pourquoi l'une des autres directives est d'utiliser des interfaces. Si votre méthode prend un objet qui implémente une interface au lieu d'une implémentation de classe spécifique, vous pouvez créer une classe qui ne dépend pas du reste de la base de code.

Une autre alternative consiste à utiliser un cadre de simulation. Ceux-ci vous permettent de créer facilement ce type d'objets factices qui peuvent être transmis à la méthode que vous testez. Il est possible que vous deviez créer des implémentations de stub pour la classe factice, mais cela crée toujours une séparation de l'implémentation réelle et de ce qui concerne le test.

unholysampler
la source
1

Comme vous pouvez le voir si un test échoue, l'autre échouera également, car ils sont tous les deux nécessaires.

Donc?

... cela ne va-t-il pas à l'encontre des directives des tests unitaires?

Non.

Comment les cas comme celui-ci doivent-ils être traités?

Plusieurs tests peuvent être indépendants et tous échouent à cause du même bogue. C'est en fait normal. De nombreux tests peuvent - indirectement - tester certaines fonctionnalités courantes. Et tous échouent lorsque la fonctionnalité commune est rompue. Rien de mal à cela.

Les tests unitaires sont définis comme des classes précisément afin qu'ils puissent facilement partager du code, comme un enregistrement factice commun utilisé pour tester la mise à jour et la suppression.

S.Lott
la source
1

Vous pouvez utiliser un cadre factice ou utiliser un «environnement» avec une base de données en mémoire. Le dernier est une classe où vous pouvez créer tout ce dont vous avez besoin pour réussir le test, avant l'exécution du test.

Je préfère le dernier - les utilisateurs peuvent vous aider à saisir des données afin que vos tests se rapprochent le plus possible du monde réel.

André
la source
Vrai - mais vous ne testez pas la vraie connexion à la base de données ici. À moins que vous ne supposiez que cela fonctionnera toujours - mais les hypothèses sont dangereuses.
ChrisF