Comment indiquer à TDD que les résultats corrects sont renvoyés

12

Je commence un nouveau projet et j'essaie très fort d'utiliser TDD pour piloter la conception. Je pousse depuis des années et j'ai finalement obtenu l'autorisation de consacrer plus de temps à ce projet pour l'utiliser pendant que j'apprends à le faire correctement.

Il s'agit d'un nouveau module, à relier à un système existant. Actuellement, tous les accès aux données se font par le biais de services Web, qui pour la plupart ne sont qu'une mince enveloppe sur les procédures stockées de la base de données.

Une exigence est que pour un magasin donné, je retourne tous les bons de commande considérés comme valides pour cette application. Un bon de commande est considéré comme valide si sa date d'expédition tombe avec une plage donnée à partir de la date d'ouverture des magasins (c'est pour les nouveaux magasins).

Maintenant, je ne peux pas mettre cette logique dans le code de l'application, car je ne vais pas ramener un million de bons de commande juste pour que la douzaine qui s'applique puisse s'appliquer à ce magasin compte tenu de la contrainte ci-dessus.

Je pensais, je pouvais passer la plage de dates à un proc GetValidPOs, et lui faire utiliser ces valeurs pour retourner les bons de commande valides. Mais que se passe-t-il si nous ajoutons une autre exigence à ce qui est considéré comme un bon de commande valide?

Et comment puis-je tester cela et vérifier qu'il fonctionne toujours? Nous n'utilisons pas d'ORM, et il est peu probable que cela se produise. Et je ne peux pas appeler la DB dans mon test.

Je suis coincé.

Mon autre pensée, est d'avoir des simulations qui retournent des données valides, d'autres qui retournent des données incorrectes, et que le référentiel local lève une exception si de mauvaises données se produisent, et teste que l'exception est levée si des données invalides sont renvoyées par GetValidPOs proc (ou la maquette utilisée dans les tests).

Est-ce que ça a du sens? Ou existe-t-il une meilleure façon?

MISE À JOUR: Je suis capable d'utiliser EF semble-t-il. Il me suffit maintenant de comprendre comment l'utiliser et de le rendre testable, tout en étant capable de s'appuyer sur des procédures stockées et la difficulté d'avoir des données dispersées sur plusieurs bases de données.

CaffGeek
la source
Par curiosité, pourquoi ne pouvez-vous pas sélectionner uniquement les bons de commande valides avec une simple instruction SQL? (Cette question ou la réponse n'implique pas de solution.)
scarfridge

Réponses:

7

Il s'agit d'un inconvénient majeur des procédures stockées à l'ère du TDD. Ils ont de réels avantages, même maintenant, mais par définition, tout test qui exerce un proc stocké n'est pas un test unitaire; c'est au mieux un test d'intégration.

La solution habituelle, en supposant que l'architecture ne peut pas changer pour utiliser un ORM à la place, est de ne pas placer ces tests dans la suite de tests unitaires; placez plutôt les tests dans une suite d'intégration. Vous pouvez toujours exécuter le test chaque fois que vous souhaitez vérifier qu'il fonctionne, mais parce que le coût inhérent à la configuration du test (initialisation d'une base de données avec les données de test appropriées) est élevé et qu'il touche des ressources, l'agent de test unitaire de votre build-bot peut ne pas avoir accès à, il ne devrait pas être dans la suite de tests unitaires.

Vous pouvez toujours tester le code unitaire qui nécessite les données, en faisant abstraction de tout ce que vous ne pouvez pas tester unitaire (classes ADO.NET) dans une classe DAO que vous pouvez ensuite simuler. Vous pouvez ensuite vérifier que les appels attendus sont effectués en consommant du code et reproduire le comportement du monde réel (par exemple, ne trouver aucun résultat) permettant de tester divers cas d'utilisation. Cependant, la configuration réelle de SqlCommand pour appeler le proc stocké est à peu près la toute dernière chose que vous pouvez tester unitaire, en séparant la création de commandes de l'exécution de commandes et en se moquant de l'exécuteur de commandes. Si cela ressemble à beaucoup de séparation des préoccupations, cela peut être; rappelez-vous, "il n'y a pas de problème qui ne peut pas être résolu par une autre couche d'indirection, sauf pour avoir trop de couches d'indirection". À un moment donné, vous devez dire "assez; je ​​ne peux tout simplement pas le tester à l'unité, nous"

Autres options:

  • Testez le proc stocké à l'aide d'une instance de SGBD "de courte durée" comme SQLite. Il est généralement plus facile de le faire lors de l'utilisation d'un ORM, mais le test peut ensuite être effectué "en mémoire" (ou avec un fichier de base de données prédéfini inclus avec la suite de tests). Ce n'est toujours pas un test unitaire, mais il peut être exécuté avec un haut degré d'isolement (le SGBD fait partie du processus en cours, et pas quelque chose auquel vous vous connectez à distance qui peut être au milieu de la suite de tests conflictuelle de quelqu'un d'autre). L'inconvénient est que les modifications du processus stocké peuvent se produire en production sans que le test reflète le changement, vous devez donc être discipliné pour vous assurer que le changement est d'abord effectué dans un environnement de test.

  • Pensez à passer à un ORM. Un ORM avec un fournisseur Linq (pratiquement tous ceux couramment utilisés en ont un) vous permettrait de définir la requête comme une instruction Linq; cette instruction peut ensuite être donnée à un référentiel simulé qui a une collection en mémoire de données de test pour l'appliquer. Vous pouvez ainsi vérifier que la requête est correcte sans même toucher à la base de données (vous devez toujours exécuter la requête dans un environnement d'intégration, pour tester que le fournisseur Linq peut correctement digérer la requête).

KeithS
la source
2
-1 car TDD! = Test unitaire. Parfaitement bien pour inclure des tests de niveau d'intégration lors de TDD.
Steven A. Lowe
Les tests unitaires sont un sous-ensemble du développement piloté par les tests. Dans un développement piloté par les tests, vous créez un squelette ambulant de votre système, puis exécutez des tests unitaires, d'intégration et fonctionnels sur ce système. Vos tests d'intégration, unitaires ou d'acceptation échouent, vous les faites ensuite passer et écrivez d'autres tests.
CodeART
1
Je comprends tout cela, vous deux. Où ai-je dit que le fait de devoir être un test d'intégration signifiait que vous ne pouviez pas le TDD? Mon point était qu'une procédure stockée ne peut pas être testée isolément, ce que vous voulez faire pour autant de votre base de code que possible. Les tests de SP nécessitent plutôt des tests d'intégration plus complexes et plus longs; bien qu'ils soient encore meilleurs que les tests manuels, une suite de tests à forte intégration peut prendre des heures à s'exécuter et peut avoir un effet néfaste sur les efforts de CI.
KeithS
Les tests SP nécessitent souvent également un ensemble de données spécifique dans la base de données de test; le code pour obtenir la base de données dans le bon état pour obtenir les résultats escomptés est très souvent plus LoC et plusieurs fois plus long que le code que vous exercez réellement. Cela aggrave encore la complexité temporelle de la suite de tests, et souvent la configuration doit être répétée pour chaque test individuel (et il devrait probablement y en avoir plusieurs pour chaque SP, pour tester que chaque exigence fonctionnelle de la requête est satisfaite).
KeithS
Les procédures stockées peuvent être testées isolément. Sinon, comment seraient-ils validés? Pour Transact SQL, il y a tSQLt ( tsqlt.org )
kevin cline
4

Mon conseil est de diviser pour mieux régner . Oubliez la base de données et la persistance pour le moment et concentrez-vous sur le test de fausses implémentations de vos référentiels ou objets d'accès aux données.

Maintenant, je ne peux pas mettre cette logique dans le code de l'application, car je ne vais pas ramener un million de bons de commande juste pour que la douzaine qui s'applique puisse s'appliquer à ce magasin compte tenu de la contrainte ci-dessus.

Je me moquerais du référentiel qui renvoie les bons de commande. Créez une maquette avec vingt bons de commande impairs.

Je pensais, je pouvais passer la plage de dates à un proc GetValidPOs, et lui faire utiliser ces valeurs pour retourner les bons de commande valides. Mais que se passe-t-il si nous ajoutons une autre exigence à ce qui est considéré comme un bon de commande valide?

Stub un appel à GetValidPOs afin qu'il appelle votre maquette, plutôt que la procédure de base de données.

Et comment puis-je tester cela et vérifier qu'il fonctionne toujours? Nous n'utilisons pas d'ORM, et il est peu probable que cela se produise. Et je ne peux pas appeler la DB dans mon test.

Vous avez besoin d'un test unitaire pour vous assurer que les données correctes sont renvoyées à partir d'une maquette.

Vous avez également besoin d'un test d'intégration pour vous assurer que les données correctes sont renvoyées à partir d'une base de données. Le test d'intégration nécessiterait une configuration et un nettoyage. Par exemple, avant d'exécuter le test d'intégration, amorcez votre base de données en exécutant un script. Vérifiez que votre script a fonctionné. Recherchez la base de données en appelant vos procédures stockées. Vérifiez que vos résultats sont corrects. Nettoyez la base de données.

Mon autre pensée, est d'avoir des simulations qui retournent des données valides, d'autres qui retournent des données incorrectes, et que le référentiel local lève une exception si de mauvaises données se produisent, et teste que l'exception est levée si des données invalides sont renvoyées par GetValidPOs proc (ou la maquette utilisée dans les tests).

Comme je l'ai déjà dit, vous avez besoin d'une maquette qui renvoie au moins certaines données que vous pouvez interroger.

Lorsque vous interrogez des données, vous voulez vous assurer que votre système peut gérer les exceptions avec élégance. Par conséquent, vous vous moquez du comportement afin qu'il lève des exceptions dans certains scénarios. Vous écrivez ensuite des tests pour vous assurer que votre système peut gérer ces exceptions avec élégance.

CodeART
la source
Voilà ce que j'essaie de faire. J'ai juste du mal à écrire une implémentation réelle qui fonctionnera de la même manière que la maquette, car notre accès aux données n'est pas propice à l'utilisation d'un ORM. La majorité des données dont j'ai besoin se trouvent dans plusieurs systèmes et sont censées être accessibles via des services Web ... même lors de la mise à jour.
CaffGeek
0

Tout comme le test unitaire Java ou Javascript signifie l'écriture de tests unitaires en utilisant le langage Java pour java et le test unitaire des fonctions Javascript avec Javascript, l'écriture de tests automatisés pour vous conduire à écrire des procédures stockées signifie que la bibliothèque de tests unitaires que vous recherchez est basée sur stockée procédures.

Autrement dit, utilisez des procédures stockées pour tester les procédures stockées car:

  • étant donné que vous développez dans le langage de procédure, vous devez avoir la capacité de rédiger vos tests dans le langage de procédure
  • la rédaction de tests dans votre langage de procédure augmentera vos compétences dans le langage de procédure, ce qui contribuera à son tour au développement de votre produit
  • vous aurez un accès direct à tous les outils fournis par votre base de données et vous pouvez également utiliser ces outils pour garder vos tests unitaires aussi simples que possible
  • les tests unitaires qui sont stockés dans la même base de données que les procédures qu'ils testent seront rapides (ce qui se rapproche le plus des tests unitaires comme les vitesses) parce que vous ne traversez pas les limites du système

Tout comme TDD dans une langue OO, vous voulez que votre test unitaire ne configure qu'une ou deux lignes de données pour tester ce dont il a besoin pour la procédure (minimalisme, ne disposez que de ce dont vos tests simples ont besoin). Le résultat est que vous aurez plusieurs tests unitaires simples pour chaque procédure stockée. Ces tests simples seront plus faciles à maintenir que les tests compliqués qui dépendent d'un grand ensemble de données qui ne correspond pas facilement aux besoins réels du test.

Lance Kind
la source