Pour info: cet article est une version élargie de mon discours lors des SQA Days # 25.Sur la base de mon expérience avec des collègues, je peux affirmer: le test de code DB n'est pas une pratique largement répandue. Cela peut être potentiellement dangereux. La logique DB est écrite par des êtres humains comme tous les autres codes "habituels". Il peut donc y avoir des pannes qui peuvent avoir des conséquences négatives sur un produit, une entreprise ou des utilisateurs. Qu'il s'agisse de procédures stockées aidant le backend ou de modifications de données ETL dans un entrepôt - il y a toujours un risque et les tests contribuent à le diminuer. Je veux vous dire ce qu'est tSQLt et comment il nous aide à tester le code DB.
Le contexte
Il existe un grand entrepôt utilisant SQL Server et contenant différentes données d'essais cliniques. Il est rempli à partir d'une variété de sources (bases de données orientées documents principalement). De nombreux ETL transforment les données de l'entrepôt à plusieurs reprises. Ces données peuvent être chargées dans des bases de données plus petites pour être utilisées par des applications Web orientées sur de petites tâches spécifiques. Certains clients du client ont demandé à implémenter des API pour leurs besoins. Ces API utilisent souvent des procédures stockées et des requêtes différentes.
En général, il y a une assez grande quantité de code du côté SGBD.
Pourquoi en avons-nous besoin?
Comme vous pouvez le voir dans l'introduction, le code DB fait partie du code de l'application et peut également contenir des bogues.
Je suppose que beaucoup d'entre nous connaissent la courbe de Boehm: les bogues sont toujours plus chers à corriger plus tard dans le processus. Une erreur commise à un stade de développement antérieur et localisée à un stade ultérieur peut coûter plus cher. Cela est dû à la nécessité de passer par de nombreuses étapes intermédiaires (codage, tests unitaires, tests d'intégration, tests système, etc.) deux fois: pour le débogage et pour ramener le code à l'étape où il a été trouvé. Cet effet est également vrai pour le cas d'entrepôt. S'il y a une erreur dans une procédure ETL et que les données sont modifiées plusieurs fois, nous devons:
- passer par toutes les étapes de transformation des données jusqu'à la source du problème
- résoudre le problème
- dériver à nouveau les données appropriées (des modifications manuelles supplémentaires peuvent être nécessaires)
- assurez-vous qu'il n'y a pas d'autres données cassées causées par le bogue.
N'oubliez pas que nous ne vendons pas de peluches. Une erreur dans un domaine tel que les essais cliniques peut nuire non seulement aux entreprises mais également à la santé humaine.
Comment tester?
Puisque nous parlons de tests de code, nous entendons les tests unitaires et d'intégration. Ces choses sont très répétitives et impliquent une régression persistante. À strictement parler, ces tests ne sont jamais effectués manuellement (enfin, sauf dans certains cas singuliers).
Bon bonus: les tests peuvent être des supports pour la documentation du code. Par exemple, les exigences peuvent ressembler à ceci (cliquable):
Fichier XLS, 2 colonnes avec exigences + informations supplémentaires fragmentées dans d'autres colonnes + balisage déroutant. Il peut être difficile de restaurer les souhaits initiaux si nécessaire. Les tests peuvent aider à enregistrer les nuances d'implémentation. Bien sûr, ils ne doivent pas être considérés comme un remplacement de la documentation.
Malheureusement, la complexité des tests augmente avec la croissance de la complexité du code, donc cet effet peut être atténué.
Les tests peuvent constituer une couche de sécurité supplémentaire contre les fusions spontanées. Les tests automatiques CI aident à résoudre ce problème en raison de leur formalisme.
Donc, si nous avons décidé d'utiliser l'automatisation, nous devons choisir un outil pour cela.
Que faut-il utiliser pour les tests?
En cas de test de code DB, je vois 2 approches: alimenté par SQL (lorsqu'un outil fonctionne directement dans le SGBD) et non alimenté par SQL. Voici les principales différences que j'ai trouvées:
Dans le cas de SQL Server, nous avons plusieurs choix:
L'échelle "Excellent - Échec" est subjective, désolé, il est difficile de trouver un moyen de contourner.
«Première apparition» - la première date d'apparition du framework que j'ai pu trouver - la première version ou validation.
Comme vous pouvez le voir, les alternatives basées sur SQL ont été abandonnées il y a assez longtemps et tSQLt est le seul produit actuellement pris en charge. De plus, tSQLt gagne fonctionnellement. La seule chose est que TST possède un ensemble d'assertions un peu plus riche que tSQLt; cependant, je doute que cela puisse l'emporter sur tous les inconvénients.
La documentation tSQLt a quelques nuances, je les décrirai plus tard.
Dans le monde non propulsé par SQL, les choses ne sont pas aussi claires. Des alternatives se développent, bien qu'elles ne soient pas super-actives. DbFit est un outil assez intéressant basé sur le framework FitNesse. Cela implique d'utiliser le balisage wiki pour écrire des tests. Slacker est également intéressant: l'approche BDD est suggérée pour les tests de code DB.
Je devrais dire à propos des assertions dans les solutions non propulsées par SQL. À première vue, le nombre d'assertions est moindre et nous pouvons penser que de tels outils sont pires. Mais nous devons garder à l'esprit qu'ils sont fondamentalement différents de tSQLt, donc un tel regard superficiel est incorrect.
La dernière ligne - "NUnit, etc." - ressemble plus à un rappel. Un grand nombre de frameworks de tests unitaires habituels peuvent être appliqués au code DB à l'aide de bibliothèques supplémentaires. Il y a beaucoup de N / A dans cette ligne car cette ligne, en fait, comprend plusieurs outils. C'est la source des "nuances" dans la colonne "assertions" - différents outils peuvent fournir différents ensembles et il n'y a aucune garantie que toutes les assertions peuvent être appliquées à DB.
Comme autre mesure intéressante, nous pouvons considérer
les tendances de Google .
Nuances:
- J'ai décidé de ne pas inclure Slacker car ce nom peut signifier différentes choses (et des requêtes comme "Slacker framework" sont à peine visibles sur le graphique).
- Par curiosité (et parce qu'un emplacement est resté vide), j'ai ajouté la tendance TST. Mais cela nous montre à peine la vraie image car c'est une abréviation qui peut aussi signifier différentes choses.
- Je n'ai pas inclus NUnit et ses analogues. Ces outils sont des cadres pour les tests de code "habituels", donc leurs tendances ne sont pas descriptives pour notre contexte.
Comme vous pouvez le voir, tSQLt est l'outil le plus consultable de la liste. Un autre outil (moins) populaire est DbFit. D'autres outils ont une popularité limitée.
Dans l'ensemble, nous pouvons voir que tSQLt brille en arrière-plan.
Qu'est-ce que tSQLt?
Il est facile de deviner que tSQLt est un framework de tests unitaires basé sur SQL. Le site officiel est
https://tsqlt.org .
Il est promis que tSQLt prend en charge SQL Server à partir de 2005 SP2. Je n'ai pas vérifié ces révisions précoces, mais je ne vois aucun problème avec 2012 sur notre serveur de développement et 2017 sur ma machine locale.
Open source, licence Apache 2.0,
disponible sur GitHub . Comme d'habitude, nous pouvons créer, contribuer, utiliser gratuitement dans des projets commerciaux et, ce qui est plus important, ne pas avoir peur des logiciels espions dans CLR.
La mécanique
Les cas de test sont des procédures stockées. Ils peuvent être combinés en classes de test (suite de tests en terminologie xUnit).
Les classes de test ne sont rien d'autre que des schémas DB. tSQLt nécessite de les enregistrer avec la procédure NewTestClass qui ajoute des classes de test à une table spéciale.
Il est possible de déterminer une procédure SetUp. Cette procédure s'exécutera avant chaque exécution de scénario de test séparé.
La procédure de démontage après l'exécution du scénario de test n'est pas requise. Chaque scénario de test avec sa configuration est exécuté dans une transaction séparée qui est annulée après la collecte des résultats. C'est très pratique mais a des conséquences négatives - je les décrirai un peu plus tard.
Le cadre permet d'exécuter des cas de test un par un, toutes les classes de test en même temps ou même toutes les classes de test enregistrées avec une seule commande.
Caractéristiques et exemples
Ne voulant pas répéter le guide officiel, je montrerai les fonctionnalités de tSQLt sur des exemples.
Avertissement:- les exemples sont simplifiés
- le code d'origine n'est pas complètement à moi - ce sont des créations plutôt collectives
- l'exemple 2 est fictif par moi afin de démontrer plus complètement les fonctionnalités.
Exemple # 1: CsvSql
Ce qui suit a été mis en œuvre à la demande de l'un des clients du client. Il existe des requêtes SQL stockées dans les champs Nvarchar (MAX). L'interface utilisateur minimale est créée pour les afficher. Les jeux de résultats générés par ces requêtes sont utilisés dans le backend pour une composition ultérieure sous forme de fichiers CSV. Les fichiers CSV peuvent être demandés par un appel API.
Les jeux de résultats sont volumineux et contiennent un grand nombre de colonnes. Un exemple hypothétique d'un tel ensemble de résultats:
Cet ensemble de résultats représente les données des essais cliniques. Examinons de plus près le calcul [ClinicsNum]. Nous avons 2 tableaux: [Trial] et [Clinic].
Il existe un FK: [Clinic]. [TrialID] -> [Trial]. [TrialID]. De toute évidence, il suffit d'utiliser COUNT (*) pour dériver un certain nombre de cliniques:
SELECT COUNT(*), ... FROM dbo.Trial LEFT JOIN dbo.Clinic ON Trial.ID = Clinic.TrialID WHERE Trial.Name = @trialName GROUP BY ...
Comment tester une telle requête? Tout d'abord, utilisons stub FakeTable, ce qui rendra notre travail beaucoup plus facile.
EXEC tSQLt.FakeTable 'dbo.Trial'; EXEC tSQLt.FakeTable 'dbo.Clinic';
FakeTable fait une chose simple - renomme les anciennes tables et en crée de nouvelles avec le même nom. Les mêmes noms, les mêmes colonnes, mais sans contraintes ni déclencheurs.
Nous en avons besoin parce que:
- La base de données de test peut contenir certaines données qui peuvent empêcher l'exécution correcte du test. FakeTable nous permet de ne pas dépendre d'eux.
- Habituellement, nous devons remplir seulement quelques colonnes aux fins du test. La table peut en contenir beaucoup, contenant souvent des contraintes et des déclencheurs. Nous facilitons l'insertion des données plus tard - nous n'insérerons que les informations requises pour le test, en gardant le test aussi minimaliste que possible.
- Il n'y aura pas de déclenchements indésirables, nous n'avons donc pas à nous soucier des post-effets.
Ensuite, nous insérons les données de test requises:
INSERT INTO dbo.Trial ([ID], [Name]) VALUES (1, 'Valerian'); INSERT INTO dbo.Clinic ([ID], [TrialID], [Name]) VALUES (1, 1, 'Clinic1'), (2, 1, 'Clinic2');
Nous dérivons la requête de la base de données, créons une table [réelle] et la remplissons avec le résultat
défini à partir de la requête.
DECLARE @sqlStatement NVARCHAR(MAX) = (SELECT… CREATE TABLE actual ([TrialID], ...); INSERT INTO actual EXEC sp_executesql @sqlStatement, ...
Maintenant, nous remplissons [Attendu] - nos valeurs attendues:
CREATE TABLE expected ( ClinicsNum INT ); INSERT INTO expected SELECT 2
Je veux attirer votre attention sur le fait que nous n'avons qu'une seule colonne dans le tableau [Expected], bien que nous ayons l'ensemble complet dans la colonne [Actual].
Cela est dû à une caractéristique utile de la procédure AssertEqualsTable que nous utiliserons pour la vérification des valeurs.
EXEC tSQLt.AssertEqualsTable 'expected', 'actual', 'incorrect number of clinics';
Il compare uniquement les colonnes qui sont présentées dans les deux tableaux. C'est très pratique dans notre cas car la requête en cours de test retourne beaucoup de colonnes, chacune connectée à une logique assez compliquée. Nous ne voulons pas gonfler les cas de test, donc cette fonctionnalité aide vraiment. Bien sûr, cette fonctionnalité est une épée à double tranchant. Si [Réel] est rempli via SELECT TOP 0 et à un moment donné une colonne inattendue apparaît, un tel cas de test ne le détectera pas. Vous devez rédiger des chèques supplémentaires pour couvrir cela.
Procédures jumelles AssertEqualsTable
Il convient de mentionner que tSQLt contient 2 procédures comme AssertEqualsTable. Ce sont AssertEqualsTableSchema et AssertResultSetsHaveSameMetaData. Le premier fait la même chose que AssertEqualsTable mais sur les métadonnées des tables. Le second fait de même mais sur les métadonnées des jeux de résultats.
Exemple # 2: Contraintes
L'exemple précédent nous a montré comment supprimer les contraintes. Mais que faire si nous devons les vérifier? Techniquement, les contraintes font également partie de la logique, et elles peuvent être considérées comme candidates à la couverture par des tests.
Considérez la situation de l'exemple précédent. 2 tableaux - [Essai] et [Clinique]; [TrialID] FK:
Essayons d'écrire un cas de test pour le vérifier. Tout d'abord, comme dans le cas précédent, nous simulons les tables:
EXEC tSQLt.FakeTable '[dbo].[Trial]' EXEC tSQLt.FakeTable '[dbo].[Clinic]'
Le but est le même: éliminer les limites inutiles. Nous voulons des chèques isolés sans effort indu.
Ensuite, nous retournons la contrainte que nous voulons tester en utilisant ApplyConstraint:
EXEC tSQLt.ApplyConstraint '[dbo].[Clinic]', 'Trial_FK';
Nous avons maintenant une configuration pour le contrôle. La vérification elle-même est que la tentative d'insertion de données provoquera une exception. Pour que le scénario de test réussisse, nous devons intercepter cette exception. Le gestionnaire d'exceptions ExpectException peut vous aider.
EXEC tSQLt.ExpectException @ExpectedMessage = 'The INSERT statement conflicted...', @ExpectedSeverity = 16, @ExpectedState = 0;
Nous pouvons essayer d'insérer non insérable après le paramètre du gestionnaire.
INSERT INTO [dbo].[Clinic] ([TrialID]) VALUES (1)
L'exception a été interceptée. Test réussi.
Procédures jumelles ApplyConstraint
La manière de tester les déclencheurs proposée par les auteurs de tSQLt est similaire à celle des contraintes de test. Nous pouvons utiliser la procédure ApplyTrigger pour retourner le déclencheur à la table. Après cela, tout se passe comme dans l'exemple ci-dessus - démarrez le déclencheur, vérifiez le résultat.
ExpectNoException - l'Antonyme d'ExpectException
Il existe une procédure ExpectNoException pour les cas où aucune exception ne doit se produire. Il fonctionne de la même manière que ExpectException, sauf que le test échoue en cas d'exception.
Exemple # 3: sémaphore
Il existe des procédures stockées et des services Windows. Le début de leur exécution peut être provoqué par différents événements extérieurs. Cependant, l'ordre de leur exécution est fixe. Il est donc nécessaire d'implémenter le contrôle d'accès du côté DB - c'est-à-dire un sémaphore. Dans notre cas, le sémaphore est un groupe de procédures stockées fonctionnant ensemble.
Regardons une procédure dans le sémaphore. Nous avons 2 tableaux - [Process] et [ProcStatus]:
La table [Processus] contient une liste des processus autorisés à être exécutés. [ProcStatus], évidemment, contient la liste des statuts du processus du tableau précédent.
Alors, que fait notre procédure? Tout d'abord, il effectue les vérifications suivantes:
- Nous avons passé un nom de processus comme l'un des paramètres d'entrée de la procédure. Ce nom est recherché dans le champ [Nom] de la table [Processus].
- Si le nom du processus a été trouvé, il vérifie l'indicateur [IsRunable] de la table [Process].
- Si l'indicateur est activé, nous considérons que le processus peut s'exécuter. La dernière vérification a lieu dans la table [ProcStatus]. Nous devons nous assurer que le processus n'est pas actuellement exécuté, ce qui signifie l'absence d'enregistrements sur le processus avec le statut "InProg" dans la table [ProcStatus].
Si tout va bien et que toutes les vérifications sont réussies, nous ajoutons un nouvel enregistrement sur notre processus dans la table [ProcStatus] avec le statut "InProg". L'ID de ce nouvel enregistrement est retourné avec le paramètre de sortie ProcStatusId.
En cas de problème, nous nous attendons à ce qui suit:
- Un e-mail à un administrateur système est envoyé.
- ProcStatusId = -1 est retourné.
- Aucun nouvel enregistrement [ProcStatus] ajouté.
Créons un cas de test pour vérifier le cas d'absence de processus dans la table [Processus].
Nous utilisons à nouveau FakeTable. Ce n'est pas si critique ici, mais cela peut être pratique car:
- Il est garanti qu'aucune donnée ne pourra perturber l'exécution du scénario de test.
- La vérification supplémentaire de l'absence de nouveaux enregistrements [ProcStatus] sera simplifiée.
EXEC tSQLt.FakeTable 'dbo.Process'; EXEC tSQLt.FakeTable 'dbo.ProcStatus';
Il existe une procédure [SendEmail] dont le nom parle de lui-même. Nous devons saisir son appel. tSQLt suggère d'utiliser la simulation SpyProcedure pour cela.
EXEC tSQLt.SpyProcedure 'dbo.SendEmail'
SpyProcedure effectue les opérations suivantes:
- Crée une table avec un nom qui ressemble à [dbo]. [ProcedureName_SpyProcedureLog]
- Tout comme FakeTable, remplace la procédure d'origine par une procédure générée automatiquement, avec le même nom, mais avec une logique de journalisation à l'intérieur. Vous pouvez également ajouter votre propre logique à la procédure générée si nécessaire.
Il n'est pas difficile de deviner que les journaux sont enregistrés dans la table [dbo]. [SendEmail_SpyProcedureLog]. Ce tableau contient une colonne [_ID_] qui correspond aux numéros de séquence des appels. Les colonnes suivantes sont nommées d'après les paramètres passés à la procédure et utilisées pour les collecter, de sorte que les valeurs des paramètres peuvent également être vérifiées.
La dernière chose que nous devons faire avant l'appel du sémaphore est de créer une variable pour stocker la valeur [ProcStatusId] (pour être plus exact, -1, car l'enregistrement ne sera pas ajouté).
DECLARE @ProcStatusId BIGINT;
Nous appelons le sémaphore:
EXEC dbo.[Semaphore_JobStarter] 'SomeProcess', @ProcStatusId OUTPUT; -- here we get -1
Nous avons maintenant toutes les données nécessaires pour les contrôles. Commençons par vérifier
que le message a bien été envoyé.
IF NOT EXISTS ( SELECT * FROM dbo.SendEmail_SpyProcedureLog) EXEC tSQLt.Fail 'SendEmail has not been run.';
Dans ce cas, nous ne vérifions pas les paramètres passés et testons le fait d'envoyer uniquement. Je souhaite attirer votre attention sur la procédure d'échec. Cela nous permet d'échouer «officiellement» un cas de test. Si vous avez besoin de construire une construction sophistiquée, Fail peut vous aider.
Nous vérifions maintenant l'absence d'enregistrements dans la table [ProcStatus] avec la procédure AssertEmptyTable.
EXEC tSQLt.AssertEmptyTable 'dbo.ProcStatus';
C'est là que FakeTable que nous avons utilisé au début nous a aidés. Avec cela, nous pouvons nous attendre à une table vide et tester en utilisant une seule ligne de code. La bonne façon de vérifier cela sans falsification de table serait de comparer le nombre de lignes avant et après l'exécution de la procédure, ce qui nécessiterait plus d'actions.
Nous pouvons facilement vérifier l'égalité ProcStatusId = -1 avec AssertEquals.
EXEC tSQLt.AssertEquals -1, @ProcStatusId, 'Wrong ProcStatusId.';
AssertEquals est minimaliste. Il compare juste 2 valeurs, rien d'extraordinaire.
Procédures jumelles AssertEquals
Nous avons les procédures suivantes pour la comparaison des valeurs:
- AssertEquals
- AssertNotEquals
- AssertEqualsString
- Assertike
Je pense que les noms sont explicites. La seule procédure que je veux souligner est AssertEqualsString. C'est la procédure dédiée à la vérification des valeurs de chaîne. Pourquoi avons-nous besoin d'une procédure de plus, compte tenu des AssertEquals universels donnés? Le problème est que AssertEquals / AssertNotEquals / AssertLike fonctionne avec le type SQL_VARIANT. NVARCHAR (MAX) n'est pas inclus dans SQL_VARIANT, les développeurs tSQLt ont donc dû effectuer une procédure supplémentaire.
Fausse fonction
Sur simple pression, nous pouvons appeler FakeFunction une procédure similaire à SpyProcedure. Ce faux permet de remplacer n'importe quelle fonction par une plus simple. Comme les fonctions SQL Server fonctionnent comme un tube de dentifrice (le résultat est renvoyé par le seul «trou»), il est techniquement impossible d'implémenter une fonctionnalité de journalisation. Le remplacement de la logique interne est le seul moyen disponible.
Pièges
Je veux vous parler de certains pièges auxquels vous pouvez faire face lors de l'utilisation de tSQLt. Dans ce cas, les «pièges» signifient certains problèmes qui sont causés par des restrictions SQL Server et / ou qui sont impossibles à résoudre par les développeurs de framework.
Annulation et annulation de transactions
Le premier et le principal problème rencontré par notre équipe est le retour en arrière et la condamnation des transactions. SQL Server ne peut pas restaurer la transaction imbriquée séparément. Il annule toujours toutes les transactions jusqu'à l'extrême. Étant donné que tSQLt encapsule chaque test dans une transaction distincte, cela peut devenir un problème car la restauration dans une procédure stockée peut interrompre un test avec une erreur d'exécution non descriptive.
Pour contourner ce problème, nous utilisons des points de sauvegarde. L'idée est simple. Au début, nous vérifions si nous sommes dans une transaction ou non. Si oui, nous supposons que c'est une transaction tSQLt et mettons un point de sauvegarde, nous allons donc y revenir si nécessaire. Si non, nous commençons une nouvelle transaction. En fait, nous n'autorisons pas l'imbrication.
Le problème est compliqué par le transfert de transaction - il peut se produire si une exception a été levée. Une transaction vouée à l'échec ne peut pas être validée ni annulée dans un point de sauvegarde, nous devons donc la restaurer à nouveau jusqu'à la transaction la plus externe.
Compte tenu des points décrits ci-dessus, nous devons utiliser la structure suivante:
DECLARE @isNestedTransaction BIT = CASE WHEN @@trancount > 0 THEN 'true' ELSE 'false' END; BEGIN TRY IF @isNestedTransaction = 'false' BEGIN TRANSACTION ELSE SAVE TRANSACTION SavepointName;
Passons en revue le code morceau par morceau. Tout d'abord, nous devons déterminer si nous sommes dans une transaction ou non.
DECLARE @isNestedTransaction BIT = CASE WHEN @@trancount > 0 THEN 'true' ELSE 'false' END;
Après avoir dérivé l'indicateur @isNestedTransaction, nous pouvons démarrer le bloc TRY et définir un point de sauvegarde ou démarrer une transaction en fonction de la situation.
BEGIN TRY IF @isNestedTransaction = 'false' BEGIN TRANSACTION ELSE SAVE TRANSACTION SavepointName;
Après avoir fait quelque chose d'utile, nous validons les résultats s'il s'agit d'une "vraie" exécution de procédure.
Bien sûr, s'il s'agit d'un scénario de test, nous n'avons besoin de rien engager. tSQLt annulera automatiquement les modifications à la fin.
Si quelque chose a mal tourné et que nous entrons dans le bloc CATCH, nous devons déterminer si la transaction est validable ou non.
BEGIN CATCH DECLARE @isCommitable BIT = CASE WHEN XACT_STATE() = 1 THEN 'true' ELSE 'false' END;
Nous ne pouvons revenir au point de sauvegarde que si:
- La transaction est engageable
- C'est un test, donc, le point de sauvegarde existe.
Dans tous les autres cas, nous devons annuler l'ensemble de la transaction.
IF @isCommitable = 'true' AND @isNestedTransaction = 'true' ROLLBACK TRANSACTION SavepointName; ELSE ROLLBACK; THROW; END CATCH;
Oui, malheureusement, si nous avons atteint un état de transaction non validable lors d'un test, nous obtenons toujours l'erreur d'exécution.
Faketable et la question des clés étrangères
Passons en revue les tableaux familiers [Essai] et [Clinique]
Nous nous souvenons de [TrialID] FK. Quel problème peut-il causer? Dans les exemples ci-dessus, nous avons appliqué FakeTable sur les deux tables. Si nous l'utilisons uniquement sur l'un d'entre eux, nous atteindrons la configuration suivante:
Ainsi, une tentative d'insertion d'un dossier dans [Clinic] peut échouer même si nous avons préparé des données dans la fausse version de [Trial].
[dbo].[Test_FK_Problem] failed: (Error) The INSERT statement conflicted with the FOREIGN KEY constraint "Trial_Fk". The conflict occurred in database "HabrDemo", table "dbo.tSQLt_tempobject_ba8f36353f7a44f6a9176a7d1db02493", column 'TrialID'.[16,0]{Test_FK_Problem,14}
Conclusion: faux tout ou rien. Dans le cas contraire, vous devez évidemment préparer une base de données avec toutes les données de test requises.
SpyProcedure sur les procédures système
Malheureusement, nous ne pouvons pas espionner les procédures système:
[HabrDemo].[test_test] failed: (Error) Cannot use SpyProcedure on sys.sp_help because the procedure does not exist[16,10] {tSQLt.Private_ValidateProcedureCanBeUsedWithSpyProcedure,7}
Dans l'exemple du sémaphore, nous avons suivi les appels de la procédure [SendEmail], qui a été créée par nos développeurs. Dans ce cas, il n'était pas requis uniquement par les tests. Il était nécessaire de créer une procédure distincte car il est nécessaire de préparer certaines données avant l'envoi. Mais vous devez être mentalement prêt à écrire une procédure intercouche afin de répondre aux objectifs de test.
Avantages
Installation rapide
L'installation de tSQLt se compose de 2 étapes et dure environ 2 minutes. Vous devez activer CLR s'il n'est pas actuellement actif et exécuter un seul script SQL. C'est tout: vous pouvez maintenant ajouter votre première classe de test et écrire des cas de test.
Apprentissage rapide
tSQLt est facile à apprendre. Cela m'a pris un peu plus d'une journée de travail. J'ai demandé à mes collègues et cela semble prendre environ 1 journée de travail pour les autres aussi. Je doute que cela puisse prendre beaucoup plus de temps.
Intégration CI rapide
Il a fallu environ 2 heures pour configurer l'intégration CI sur notre projet. Le temps peut varier, bien sûr, mais ce n'est pas un problème en général, et cela peut se faire rapidement.
Un large éventail d'instruments
C'est subjectif, mais à mon avis, la fonctionnalité tSQLt est riche et la part du lion des besoins peut être couverte par elle. Si cela ne suffit pas, vous pouvez toujours utiliser la procédure d'échec pour les cas rares et sophistiqués.
Documentation pratique
Les guides officiels sont pratiques et cohérents. Vous pouvez facilement comprendre l'utilisation de tSQLt en peu de temps, même s'il s'agit de votre premier outil de test unitaire.
Sortie claire
La sortie du test peut être prise dans un format texte illustratif:
[tSQLtDemo].[test_error_messages] failed: (Failure) Expected an error to be raised. [tSQLtDemo].[test_tables_comparison] failed: (Failure) useful and descriptive error message Unexpected/missing resultset rows! |_m_|Column1|Column2| +---+-------+-------+ |< |2 |Value2 | |= |1 |Value1 | |= |3 |Value3 | |> |2 |Value3 | +----------------------+ |Test Execution Summary| +----------------------+ |No|Test Case Name |Dur(ms)|Result | +--+------------------------------------+-------+-------+ |1 |[tSQLtDemo].[test_constraint] | 83|Success| |2 |[tSQLtDemo].[test_trial_view] | 83|Success| |3 |[tSQLtDemo].[test_error_messages] | 127|Failure| |4 |[tSQLtDemo].[test_tables_comparison]| 147|Failure| ----------------------------------------------------------------------------- Msg 50000, Level 16, State 10, Line 1 Test Case Summary: 4 test case(s) executed, 2 succeeded, 2 failed, 0 errored. -----------------------------------------------------------------------------
Il peut également être dérivé de la base de données (cliquable) ...
... ou même en XML.
<?xml version="1.0" encoding="UTF-8"?> <testsuites> <testsuite id="1" name="tSQLtDemo" tests="3" errors="0" failures="1" timestamp="2019-06-22T16:46:06" time="0.433" hostname="BLAHBLAHBLAH\SQL2017" package="tSQLt"> <properties /> <testcase classname="tSQLtDemo" name="test_constraint" time="0.097" /> <testcase classname="tSQLtDemo" name="test_error_messages" time="0.153"> <failure message="Expected an error to be raised." type="tSQLt.Fail" /> </testcase> <testcase classname="tSQLtDemo" name="test_trial_view" time="0.156" /> <system-out /> <system-err /> </testsuite> </testsuites>
Le dernier format permet l'intégration CI sans aucun problème. Plus précisément, nous utilisons tSQLt avec Atlassian Bamboo.
Prise en charge de Redgate
En tant que l'un des pros, je peux nommer le support de l'un des plus grands fournisseurs d'outils DBA - RedGate. Leur plug-in SQL Server Management Studio nommé SQL Test fonctionne avec tSQLt depuis le début. De plus, RedGate aide le développeur principal de tSQLt avec dev-environment, selon ses mots dans
les groupes Google .
Inconvénients
Pas de simulation de tables temporaires
tSQLt ne permet pas de truquer des tables temporaires. Réflexions, en cas de nécessité, vous pouvez utiliser un addon non officiel. Malheureusement, cet addon fonctionne uniquement avec SQL Server 2016+.
Travailler avec des bases de données externes
tSQLt est conçu pour fonctionner avec le code dans la même base de données dans laquelle le framework est installé. Ainsi, il peut être impossible de l'utiliser avec une base de données externe. Au moins, les contrefaçons ne fonctionneront pas.
CREATE PROCEDURE [tSQLtDemo].[test_outer_db] AS BEGIN SELECT TOP 10 * FROM [AdventureWorks2017].[Person].[Password] EXEC tSQLt.FakeTable '[AdventureWorks2017].[Person].[Password]' SELECT TOP 10 * FROM [AdventureWorks2017].[Person].[Password] END
Il semble que les assertions fonctionnent, mais leur ouvrabilité n'est pas garantie, bien sûr.
CREATE PROCEDURE [tSQLtDemo].[test_outer_db_assertions] AS BEGIN SELECT TOP 1 * INTO
Bogues de documentation
Même si j'ai mentionné ci-dessus que les guides sont pratiques et cohérents, la documentation présente certains problèmes. Il contient des parties obsolètes.
Exemple 1.
«Guide de démarrage rapide» suggère de télécharger le framework depuis SourceForge.
Ils ont quitté SourceForge
jusqu'en 2015 .
Exemple 2.
Le guide ApplyConstraint utilise une conception volumineuse avec la procédure Fail dans un exemple de capture d'exception. Cela peut être remplacé par du code simple et clair à l'aide d'ExpectException.
CREATE PROCEDURE ConstraintTests.[test ReferencingTable_ReferencedTable_FK prevents insert of orphaned rows] AS BEGIN EXEC tSQLt.FakeTable 'dbo.ReferencedTable'; EXEC tSQLt.FakeTable 'dbo.ReferencingTable'; EXEC tSQLt.ApplyConstraint 'dbo.ReferencingTable','ReferencingTable_ReferencedTable_FK'; DECLARE @ErrorMessage NVARCHAR(MAX); SET @ErrorMessage = ''; BEGIN TRY INSERT INTO dbo.ReferencingTable ( id, ReferencedTableId ) VALUES ( 1, 11 ) ; END TRY BEGIN CATCH SET @ErrorMessage = ERROR_MESSAGE(); END CATCH IF @ErrorMessage NOT LIKE '%ReferencingTable_ReferencedTable_FK%' BEGIN EXEC tSQLt.Fail 'Expected error message containing ''ReferencingTable_ReferencedTable_FK'' but got: ''',@ErrorMessage,'''!'; END END GO
Et cela est attendu, à cause de ...
Abandon partiel
Il y a eu une pause prolongée dans le développement du début de 2016 à juin 2019. Oui, malheureusement, cet outil est partiellement abandonné. Le développement a lentement commencé en 2019,
selon GitHub . Bien que les groupes Google officiels
aient un fil de discussion où Sebastian, le principal développeur de tSQLt, a été interrogé sur l'avenir du projet. La dernière question a été posée le 2 mars 2019, sans réponse.
Problème avec SQL Server 2017
L'installation de tSQLt peut nécessiter des actions supplémentaires si vous utilisez SQL Server 2017. Microsoft a implémenté la première modification de sécurité depuis 2012 dans cette version. Un indicateur de niveau serveur "CLR strict security" a été ajouté. Ce drapeau interdit la création d'assemblages non signés (même SAFE). Une description détaillée mérite un article séparé (et, heureusement, nous en avons
déjà un bon; voir également les articles suivants dans la séquence. Soyez juste préparé mentalement pour cela.
Bien sûr, je pourrais attribuer ce problème aux "pièges", mais ce problème peut être résolu par les développeurs de tSQLt.
Le problème GitHub a déjà été soulevé . Pourtant, il n'a pas été résolu depuis octobre 2017.
Alternatives (±) pour d'autres SGBD
tSQLt n'est pas unique en son genre. Bien que vous ne puissiez pas l'utiliser dans d'autres SGBD en raison des nuances CLR et T-SQL, vous pouvez toujours trouver quelque chose de similaire. Il convient de mentionner que ces alternatives ne sont pas très proches de tSQLt, donc je veux dire une approche basée sur SQL.
Par exemple, les utilisateurs de PostgreSQL peuvent essayer
pgTAP . Il s'agit d'un outil bien développé et en développement actif utilisant PL / pgSQL natif pour les tests et le format de sortie TAP. L'outil similaire
MyTap peut vous aider avec des tests sous MySQL. Ce framework est un peu moins fonctionnel que pgTAP mais peut toujours être utile. Et c'est aussi en développement actif. Si vous êtes un utilisateur Oracle heureux, vous avez la possibilité d'utiliser l'outil très puissant
utPLSQL . Il se développe très activement et offre un grand nombre de fonctionnalités.
Conclusion
Je voulais transmettre 2 idées:
Le premier: l'utilité des tests de code DB. Ce n'est pas important si vous utilisez SQL Server, Oracle, MySQL ou autre chose. Si votre base de données contient une logique non testée, vous prenez des risques. Comme tous les autres bogues de tout autre code, les bogues de code DB peuvent endommager le produit et la société qui le fournit.
Le second: le choix de l'outil. Pour ceux qui travaillent avec SQL Server, tSQLt, même s'il n'est pas gagnant à 100%, mérite certainement l'attention. Malgré un développement lent et certains problèmes, c'est toujours un cadre pratique qui pourrait rendre votre travail beaucoup plus facile.
Sources qui m'ont aidé (liste non exhaustive)