Accueil Nos publications Blog Créer des tests unitaires pour du code SQL dans Visual Studio

Créer des tests unitaires pour du code SQL dans Visual Studio

Visual Studio 2010On parle beaucoup des tests unitaires sur du code C# ou Java, mais un peu moins de tests unitaires sur du code SQL. Entendez par là, tester les fonctions, triggers et procédures stockées définies dans SQL Server pour une application, de manière totalement indépendante du code de la couche d’accès aux données, selon le principe même des tests dits unitaires : tester la plus petite unité de code possible, de manière isolée. Je vous propose de découvrir comment les mettre en place dans Visual Studio.

 

Différents outils existent pour tester efficacement du code SQL Server : sous forme de frameworks comme TSQL Test, d’extensions pour SQL Server Management Studio telles que SQL Test chez Redgate, ou directement dans Visual Studio avec SQL Server Data Tools.

Cette dernière option se présente comme un projet de test unitaire C# classique, généré par un designer rendant les tests très rapides à écrire, en T-SQL. Pour utiliser le designer et générer automatiquement le code associé aux tests, il est nécessaire d’utiliser un projet SQL Server Database (qu’il est très facile de mettre en place à partir d’une base de données existante, mais les tests peuvent s’exécuter indépendamment de celui-ci, par exemple dans le cadre d’une industrialisation). Le designer est disponible sur les éditions Professional et supérieures depuis la version 2010 de Visual Studio. Les illustrations de cet article ont été créées avec Visual Studio 2013 édition Professional.

Dans cet article nous allons définir une série de tests sur une procédure stockée d’un projet SQL Server Database en utilisant le designer de SSDT. Nous passerons sur les différentes conditions prédéfinies et jetterons un œil à ce qui est généré en arrière-plan. Après un tour d’horizon des ressources complémentaires au sujet, nous conclurons sur un petit retour d’expérience.

 

Mise en place des tests sous Visual Studio

 

Présentation de l’exemple

Nous allons effectuer les tests sur une procédure stockée qui permet d’appliquer des modifications aux données d’une table, dans le cadre d’une synchronisation entre bases de données utilisant le Change Tracking.

Schéma de l'exemple

Pour faire court, on récupère les modifications sur une table originale à partir d’un instant t. Les lignes modifiées sont insérées dans la table products_changes avec un caractère spécifiant le type de modification : U pour update, I pour insert, D pour delete, R pour reinit.
Notre procédure stockée sync_products s’occupe de reporter toutes ces modifications sur la table product, pour qu’elle soit finalement une copie conforme de la table originale.

 

Création du projet de tests

Ouvrez le SQL Server Object Explorer (disponible dans le menu VIEW), faites un clic-droit sur la procédure stockée à tester et sélectionnez “Create Unit Tests”.

Création du projet de tests

Dans la nouvelle fenêtre, sélectionnez “Create a new Visual C# test project…” dans la liste déroulante, et donnez un nom au projet et à la classe de tests.

création du projet de tests 2

Il vous faudra ensuite choisir la base de données à utiliser. Vous pouvez aussi définir une connection string spécifique aux tests. Il est possible de déployer automatiquement le projet de base de données avant d’exécuter les tests, en cochant la case sous « Deployment » et en finalisant la configuration.

creation projet

Le projet et la classe de test sont maintenant créés. Supprimez la classe UnitTest1.cs qui est ajoutée par défaut.

 

Création de notre premier test

Nous allons commencer par tester le mode « Réinit » de notre procédure. Renommez le test ajouté par défaut en sélectionnant « Rename » en haut de la fenêtre.

Création du test

L’idée sera d’exécuter la procédure dans un certain contexte et de vérifier son contenu. On va donc supprimer la dernière ligne à l’écran pour plutôt sélectionner toutes les lignes de la table product.

creation test R

 

Les Pre et Post Scripts

Pour mettre en place un contexte de test adéquat, il est possible d’exécuter un script avant le test. Le designer est accessible en sélectionnant “Pre-Test” dans la liste déroulante du haut.

pre-test

Nous insérons donc quelques lignes dans product_changes avec une opération de « Réinit » (flag « R »).

De la même manière, il est possible de sélectionner “Post-Test” et d’ajouter un script qui sera exécuté après le test, à des fins de nettoyage par exemple.

 

Exécution du test

Pour exécuter le test, ouvrez l’explorer de test (menu TEST > Windows > Tests Explorer) et faites un clic-droit sur le test dans la liste (s’il n’apparaît pas, vous devrez builder la solution). Choisir “Run Selected Tests”.

execution test Le test s’exécute et échoue.

execution test inconclusive

 

Les différents types de conditions prédéfinies

 

La fenêtre designer contient un espace “Test Conditions” permettant d’ajouter des assertions au test. Par défaut, une condition “Inconclusive” est dans la liste, qui fait échouer le test quel que soit le résultat. Après l’avoir supprimée en la sélectionnant et en cliquant sur la croix, on peut ajouter nos propres conditions. Celles-ci sont exécutées dans l’ordre établi dans l’éditeur. Il est possible de les réordonner avec les flèches sur la droite. Pour chacune, le nom est personnalisable et la condition peut être désactivée si nécessaire (« Enabled »).

Un certain nombre de conditions prédéfinies sont disponibles pour vérifier le résultat de notre test.

liste des conditions

 

 

 

Checksum Data

Pour notre test de « Réinit », on va vérifier si toutes les lignes ont bien été reportées sur la table product. Notre table ne contenant pas de colonne risquant d’être différente d’un test à l’autre (colonne de date, identity, etc), la condition qui s’impose est le Data Checksum. Il s’agit de comparer le checksum du jeu de résultat à un checksum de référence.

Sélectionnez « Data Checksum » dans la liste des conditions, et cliquez sur « + » pour l’ajouter. Une nouvelle ligne est ajoutée spécifiant que le checksum doit être configuré.

checksum

Allez dans le panneau des propriétés de la condition et cliquer sur le bouton « Press to configure ».

checksum bis

Sélectionnez la connexion à utiliser, puis assurez-vous que la table est bien dans l’état désiré après exécution de la procédure, en ajoutant du code T-SQL dans l’éditeur. Pour ma part, je me suis contentée d’exécuter la procédure stockée, c’est le mode par défaut. Puis cliquez sur « Retrieve » et le résultat s’affiche en dessous.

checksum

En cliquant sur OK, le checksum est calculé et ajouté à la condition.

checksum 3

Ré-exécutez le test : il passe !

checksum 4

 

(Not) Empty ResultSet et Row Count

Créons un deuxième test sur notre procédure, vérifiant l’insertion d’un nouveau produit. Cliquez sur le « + » et entrez le nom du nouveau test. Tous les tests restent à disposition via la liste déroulante en haut à gauche.

insert
Nous allons garder le même code de test, mais modifier l’état de la table dans un pré-test afin que la procédure effectue une insertion. Copiez le code T-SQL du test « Réinit » dans l’éditeur du test « Insert », puis ajoutez du code dans la zone pré-test, de manière à avoir une table product vide et une seule ligne dans product_changes avec le flag « I ».

Après avoir supprimé la clause « Inconclusive », on peut par exemple vérifier que le jeu de résultat n’est pas vide et qu’il contient une seule ligne.

Sélectionner « Not Empty ResultSet » dans la liste des conditions et cliquez sur le « + » pour l’ajouter. Cette condition ne nécessite pas de configuration supplémentaire. Puis sélectionnez « Row Count » et ajoutez la condition de la même manière. Dans le panneau des propriétés de la condition, modifiez le paramètre « Row Count » à 1.

insert row countinsert row count propriétés

De la même manière, on peut ajouter un test pour la fonction « Delete » de la procédure stockée, en utilisant la condition « Empty ResultSet », qui vérifiera que le jeu de résultat est vide.

delete condition Sauvegardez et lancez le test !

 

Scalar Value

La condition « Scalar Value » permet de vérifier la valeur d’une colonne sur une ligne donnée du jeu de résultats. Nous allons l’appliquer à la fonction « Update » de la procédure.

Créez le test comme précédemment et ajoutez un pré-test adéquat.

Ajoutez ensuite une condition « Scalar Value » dans l’éditeur du test. Dans les propriétés de la condition, ajustez les paramètres « Row number » (numéro de ligne du jeu de résultat, nous allons laisser « 1 »), « Column number » (colonne à tester, dans notre cas ce sera le prix, colonne 3) et « Expected Value ».

update condtion et properties

update test ok

 

 

 

 

 

 

Execution Time, Expected Schema

La condition « Exécution Time » permet de vérifier le temps d’exécution du code T-SQL entré dans l’éditeur « Test », c’est-à-dire que les temps d’exécution des codes de Pre-test et Post-test ne sont pas compris dans la condition. La durée maximale est personnalisable et est par défaut à 30 secondes.execution time

« Expected Schema » teste les colonnes et les types du schéma de retour du test. La condition se configure de la même manière que « Checksum Data », via le panneau des propriétés, en récupérant le format du jeu de résultats attendu via du T-SQL.

 

Génération de la classe de test

arborescence
Une classe C# est ainsi générée automatiquement, utilisant le framework classique de tests unitaires de Visual Studio. Une méthode est créée par test (Réinit, Insert, Delete, Update).

Le code T-SQL est lui stocké dans un fichier de ressource.

code methoderesx

 

 

Quelques éléments un peu plus avancés

 

Créer ses propres conditions

Il est possible de créer et d’installer des conditions personnalisées dans le projet de tests. Cela se présente comme une librairie de classes C# à ajouter au projet.

Expected Failures

Il est parfois utile de créer des tests dont le résultat attendu est l’échec. C’est possible en ajoutant l’annotation ExpectedSqlException à la méthode de test dans la classe.

Transactions

Pour les tests un peu plus complexes, il est possible d’utiliser des transactions sur une méthode de test, ou autour de tous les tests d’une classe.

Intégration continue avec Team Foundation Server

Le projet de test peut s’intégrer à une build sur TFS.

 

Conclusion

Nous avons vu comment créer des tests unitaires pour des objets SQL Server dans Visual Studio. Tester une application apporte de nombreux bénéfices et il est tout aussi important de tester les procédures stockées, triggers, fonctions SQL, comme on teste les méthodes d’une couche d’application, de manière isolée et unitaire. J’ai particulièrement apprécié la rapidité avec laquelle les tests peuvent être mis en place, ajoutés au système de versioning utilisé pour l’application, aux côtés du projet SQL Server Database. Les tests se sont révélés indispensables pour gérer sereinement les inévitables modifications de dernière minute et la maintenance sur le projet.