Il existe des réponses à la question sur la manière dont les classes de test qui se connectent à une base de données, par exemple "Les classes de test de service doivent-elles se connecter ..." et "Test unitaire - Application couplée à une base de données " .
En bref, supposons que vous ayez une classe A qui doit se connecter à une base de données. Au lieu de laisser A réellement se connecter, vous fournissez à A une interface que A peut utiliser pour se connecter. Pour tester, vous implémentez cette interface avec quelques éléments - sans vous connecter bien sûr. Si la classe B instancie A, elle doit passer une connexion "réelle" à la base A. Mais cela signifie que B ouvre une connexion à la base de données. Cela signifie que pour tester B vous injectez la connexion dans B. Mais B est instancié en classe C et ainsi de suite.
Donc, à quel moment dois-je dire "ici, je récupère les données d'une base de données et je n'écrirai pas de test unitaire pour ce morceau de code"?
En d'autres termes: Quelque part dans le code d'une classe, je dois appeler sqlDB.connect()
ou quelque chose de similaire. Comment puis-je tester cette classe?
Et est-ce la même chose avec le code qui doit traiter avec une interface graphique ou un système de fichiers?
Je veux faire des tests unitaires. Tout autre type de test n'est pas lié à ma question. Je sais que je ne testerai qu'une classe avec elle (je suis tellement d'accord avec toi Kilian). Maintenant, certaines classes doivent se connecter à une base de données. Si je veux tester cette classe et demander "Comment puis-je faire cela", beaucoup disent: "Utilisez l'injection de dépendance!" Mais cela ne fait que déplacer le problème vers une autre classe, n'est-ce pas? Alors je demande, comment puis-je tester la classe qui établit vraiment, vraiment la connexion?
Question bonus: certaines réponses ici se résument à "Utilisez des objets fictifs!" Qu'est-ce que ça veut dire? Je me moque des classes dont dépend la classe sous test. Dois-je me moquer de la classe à l’essai maintenant et effectivement tester la maquette (qui se rapproche de l’idée d’utiliser des méthodes modèles, voir ci-dessous)?
la source
Réponses:
L'intérêt d'un test unitaire est de tester une classe (en fait, il convient généralement de tester une méthode ).
Cela signifie que lorsque vous testez une classe
A
, vous lui injectez une base de données de test - quelque chose d’auto-écrit, ou une base de données ultra-rapide en mémoire, quel que soit le travail effectué.Cependant, si vous testez la classe
B
, qui est un client deA
, alors vous vous moquez de l'A
objet entier avec autre chose, probablement quelque chose qui fait son travail de manière primitive et préprogrammée - sans utiliser d'A
objet réel et certainement sans utiliser de données base (à moins queA
l’ensemble de la connexion à la base de données ne soit renvoyée à son appelant - mais c’est tellement horrible que je ne veux pas y penser). De même, lorsque vous écrivez un test unitaire pour la classeC
, qui est un client deB
, vous vous moquez de quelque chose qui prend le rôle deB
et qui vous fait oublierA
.Si vous ne le faites pas, ce n'est plus un test unitaire, mais un test système ou d'intégration. Celles-ci sont également très importantes, mais représentent une tout autre variété de poissons. Pour commencer, ils demandent généralement plus d'efforts à configurer et à exécuter, il n'est pas pratique d'exiger de les transmettre comme condition préalable à l'enregistrement, etc.
la source
Effectuer des tests unitaires avec une connexion à une base de données est parfaitement normal et courant. Il est tout simplement impossible de créer une
purist
approche où tout dans votre système est une dépendance injectable.La clé ici est de tester une base de données temporaire ou uniquement à tester, et d’avoir le processus de démarrage le plus léger possible pour créer cette base de test.
Pour les tests unitaires dans CakePHP, il y a des choses appelées
fixtures
. Les appareils sont des tables de base de données temporaires créées à la volée pour un test unitaire. Le projecteur a des méthodes pratiques pour les créer. Ils peuvent recréer un schéma à partir d'une base de production dans la base de tests, ou vous pouvez définir le schéma à l'aide d'une notation simple.La clé du succès consiste à ne pas implémenter la base de données métier, mais à se concentrer uniquement sur l'aspect du code que vous testez. Si vous disposez d'un test unitaire qui vérifie qu'un modèle de données ne lit que les documents publiés, le schéma de table de ce test ne doit contenir que les champs requis par ce code. Il n'est pas nécessaire de réimplémenter toute une base de données de gestion de contenu simplement pour tester ce code.
Quelques références supplémentaires.
http://en.wikipedia.org/wiki/Test_fixture
http://phpunit.de/manual/3.7/en/database.html
http://book.cakephp.org/2.0/fr/development/testing.html#fixtures
la source
Il existe, quelque part dans votre base de code, une ligne de code qui effectue l'action réelle de connexion à la base de données distante. Cette ligne de code est, 9 fois sur 10, un appel à une méthode "intégrée" fournie par les bibliothèques d'exécution spécifiques à votre langue et à votre environnement. En tant que tel, ce n'est pas "votre" code et vous n'avez donc pas besoin de le tester; aux fins d'un test unitaire, vous pouvez être sûr que cet appel de méthode fonctionnera correctement. Ce que vous pouvez et devriez toujours tester dans votre suite de tests unitaires, par exemple, vous assurer que les paramètres qui seront utilisés pour cet appel correspondent à ce que vous attendez, comme s’assurer que la chaîne de connexion est correcte, ou l’instruction SQL ou nom de procédure stockée.
C’est l’un des objectifs de la restriction selon laquelle les tests unitaires ne doivent pas quitter leur "bac à sable" d’exécution et dépendent de l’état externe. C'est en fait assez pratique; le but d'un test unitaire est de vérifier que le code que vous avez écrit (ou êtes sur le point d'écrire, en TDD) se comporte comme vous le pensiez. Le code que vous n'avez pas écrit, tel que la bibliothèque que vous utilisez pour effectuer vos opérations de base de données, ne devrait pas faire partie de la portée d'un test unitaire, pour la simple raison que vous ne l'avez pas écrit.
Dans votre suite de tests d' intégration , ces restrictions sont assouplies. Maintenant vous pouvezDes tests de conception qui touchent la base de données, pour vous assurer que le code que vous avez écrit joue bien avec du code que vous n'avez pas. Cependant, ces deux suites de tests doivent rester séparées, car votre suite de tests unitaires est d'autant plus efficace qu'elle s'exécute rapidement (vous pouvez ainsi vérifier rapidement que toutes les affirmations des développeurs concernant leur code sont toujours valables) et, presque par définition, un test d'intégration. est plus lent par ordres de grandeur en raison des dépendances supplémentaires sur les ressources externes. Laissez-le-robot gérer l'exécution de votre suite d'intégration complète toutes les quelques heures, en exécutant les tests qui bloquent les ressources externes, afin que les développeurs ne se marchent pas l'un sur l'autre en exécutant ces mêmes tests localement. Et si la construction casse, et alors? Il est beaucoup plus important de s’assurer que le build-bot n’échoue jamais une construction comme il se doit.
Maintenant, le degré de stricte conformité avec cela dépend de votre stratégie exacte pour vous connecter à la base de données et l'interroger. Dans de nombreux cas où vous devez utiliser la structure d'accès aux données "à l'état brut", telle que les objets SqlConnection et SqlStatement d'ADO.NET, une méthode complète que vous avez développée peut être constituée d'appels de méthode intégrés et d'un autre code dépendant de la présence d'un objet. connexion de base de données, et le mieux que vous puissiez faire dans cette situation est de simuler toute la fonction et de faire confiance à vos suites de tests d’intégration. Cela dépend également de votre volonté de concevoir vos classes afin de permettre le remplacement de lignes de code spécifiques à des fins de test (comme la suggestion de Tobi concernant le modèle de méthode, qui est un bon modèle car il permet des "simulations partielles".
Si votre modèle de persistance des données repose sur du code de votre couche de données (tels que des déclencheurs, des processus stockés, etc.), il n’existe tout simplement pas d’autre moyen d’exercer du code que vous écrivez vous-même que de développer des tests qui vivent dans la couche de données ou qui traversent la couche de données. limite entre le runtime de votre application et le SGBD. Un puriste dirait que ce modèle, pour cette raison, doit être évité en faveur de quelque chose comme un ORM. Je ne pense pas que j'irais aussi loin; Même à l'ère des requêtes intégrées dans la langue et des autres opérations de persistance dépendantes du domaine et vérifiées par le compilateur, je vois l'intérêt de verrouiller la base de données sur les seules opérations exposées via une procédure stockée. Bien entendu, ces procédures stockées doivent être vérifiées à l'aide de la méthode automatisée. tests. Mais, ces tests ne sont pas des tests unitaires . Ils sont l' intégration tests.
Si vous rencontrez un problème avec cette distinction, elle est généralement basée sur une grande importance accordée à la "couverture de code" complète, à savoir "couverture de test unitaire". Vous voulez vous assurer que chaque ligne de votre code est couverte par un test unitaire. Un objectif noble, mais je dis foutaise; cette mentalité se prête bien à des anti-schémas qui vont bien au-delà de ce cas particulier, tels que la rédaction de tests sans assertion qui s'exécutent sans exercervotre code. Ces types de fin d’année uniquement à des fins de couverture sont plus dommageables que d’assouplir votre couverture minimale. Si vous voulez vous assurer que chaque ligne de votre base de code est exécutée par un test automatisé, alors c'est facile; lors du calcul des métriques de couverture de code, incluez les tests d'intégration. Vous pouvez même aller un peu plus loin et isoler ces tests "Itino" contestés ("Intégration dans le nom uniquement"), et entre votre suite de tests unitaires et cette sous-catégorie de tests d'intégration (qui devrait néanmoins fonctionner assez rapidement), vous devriez proche de la couverture complète.
la source
Les tests unitaires ne doivent jamais se connecter à une base de données. Par définition, ils doivent tester une seule unité de code (une méthode) en totale isolation par rapport au reste de votre système. S'ils ne le font pas, ils ne constituent pas un test unitaire.
Hormis la sémantique, il y a une myriade de raisons pour lesquelles cela est bénéfique:
Les tests unitaires sont un moyen de vérifier votre travail. Ils doivent décrire tous les scénarios pour une méthode donnée, ce qui signifie généralement tous les chemins différents d'une méthode. C’est votre spécification à laquelle vous vous adressez, semblable à la comptabilité en partie double.
Ce que vous décrivez est un autre type de test automatisé: un test d’intégration. Bien qu'ils soient également très importants, idéalement, vous en aurez beaucoup moins. Ils doivent vérifier qu'un groupe d'unités s'intègre correctement les unes aux autres.
Alors, comment testez-vous les choses avec l'accès à la base de données? Tous vos codes d'accès aux données doivent se trouver dans une couche spécifique, de sorte que le code de votre application puisse interagir avec des services modulables au lieu de la base de données réelle. Peu importe que ces services soient sauvegardés par un type de base de données SQL, des données de test en mémoire ou même des données de service Web distantes. Ce n'est pas leur souci.
Idéalement (et cela est très subjectif), vous voulez que la majeure partie de votre code soit couverte par des tests unitaires. Cela vous donne la confiance que chaque pièce fonctionne indépendamment. Une fois les pièces construites, vous devez les assembler. Exemple - lorsque je hachais le mot de passe de l'utilisateur, je devrais obtenir cette sortie exacte.
Supposons que chaque composant se compose d'environ 5 classes. Vous souhaitez tester tous les points d'échec qui s'y trouvent. Cela équivaut à moins de tests pour s'assurer que tout est câblé correctement. Exemple - test vous pouvez trouver l'utilisateur dans la base de données à l'aide d'un nom d'utilisateur / mot de passe.
Enfin, vous souhaitez que certains tests d'acceptation garantissent que vous atteignez les objectifs de l'entreprise. Il y en a encore moins; ils peuvent s'assurer que l'application est en cours d'exécution et fait ce pour quoi elle a été conçue. Exemple - compte tenu de ces données de test, je devrais pouvoir me connecter.
Pensez à ces trois types de tests comme une pyramide. Vous avez besoin de nombreux tests unitaires pour tout supporter, puis vous progressez à partir de là.
la source
Le modèle de méthode de modèle pourrait aider.
Vous encapsulez les appels à une base de données dans des
protected
méthodes. Pour tester cette classe, vous testez en réalité un objet fictif qui hérite de la classe de connexion réelle à la base de données et remplace les méthodes protégées.De cette façon, les appels à la base de données ne sont jamais soumis à des tests unitaires, c'est exact. Mais ce ne sont que ces quelques lignes de code. Et c'est acceptable.
la source
Tester avec des données externes est un test d'intégration. Le test unitaire signifie que vous testez uniquement l'unité. Cela se fait principalement avec votre logique métier. Pour que votre unité de code soit testable, vous devez suivre certaines directives, tout comme vous devez rendre votre unité indépendante des autres parties de votre code. Lors du test unitaire, si vous avez besoin de données, vous devez alors injecter ces données avec une injection de dépendance. Il existe un cadre moqueur et moqueur.
la source