Comment écrire des tests unitaires pour les appels de base de données
je suis proche du début d'un nouveau projet et (gasp! pour la première fois, j'essaie d'inclure des tests unitaires dans un projet de mine.
j'ai du mal à concevoir certains tests de l'unité eux-mêmes. J'ai quelques méthodes qui ont été assez faciles à tester (passer dans deux valeurs et vérifier pour un résultat attendu). J'ai d'autres parties du code qui font des choses plus complexes comme lancer des requêtes contre la base de données et je ne sais pas comment les tester.
public DataTable ExecuteQuery(SqlConnection ActiveConnection, string Query, SqlParameterCollection Parameters)
{
DataTable resultSet = new DataTable();
SqlCommand queryCommand = new SqlCommand();
try
{
queryCommand.Connection = ActiveConnection;
queryCommand.CommandText = Query;
if (Parameters != null)
{
foreach (SqlParameter param in Parameters)
{
queryCommand.Parameters.Add(param);
}
}
SqlDataAdapter queryDA = new SqlDataAdapter(queryCommand);
queryDA.Fill(resultSet);
}
catch (Exception ex)
{
//TODO: Improve error handling
Console.WriteLine(ex.Message);
}
return resultSet;
}
cette méthode prend essentiellement tous les bits et morceaux nécessaires pour extraire certaines données de la base de données, et renvoie les données dans un objet DataTable.
la première question est probablement la plus complexe: Qu'est-ce que je devrais même tester dans une situation comme celle-ci?
une fois que cela est réglé vient la question de savoir si oui ou non il faut se débarrasser des composants de la base de données ou essayer de tester par rapport à la base de données réelle.
9 réponses
que testez-vous?
il y a trois possibilités, du haut de ma tête:
A. vous testez la classe DAO (data access object), en vous assurant que c'est correctement la sélection des valeurs/paramètres passés à la base de données,, et correctement la sélection/transformation/empaquetage des résultats obtenus frm la base de données.
Dans ce cas, vous n'avez pas besoin de se connecter à la base de données; vous juste besoin d'un test unitaire qui remplace la base de données (ou couche intermédiaire, par exemple., JDBC, (N)Hibernate, iBatis) avec un faux.
B. Vous testez la justesse syntaxique de SQL (généré).
dans ce cas, parce que les dialectes SQL diffèrent, vous voulez exécuter le SQL (éventuellement généré) contre la version correcte de votre RDBMS, plutôt que de tenter de se moquer de toutes les bizarreries de votre RDBMS (et de sorte que toutes les mises à jour RDBMS qui changent les fonctionnalités sont prises en compte par vos tests).
C. vous testez la sémantique exactitude de votre SQL, I. e, que pour un ensemble de données de base donné, vos opérations (accès/sélections et mutations/inserts et mises à jour) produisent le nouvel ensemble de données attendu.
pour cela, vous voulez utiliser quelque chose comme dbunit (qui vous permet de définir une ligne de base et de comparer un ensemble de résultats attendus), ou peut-être faire vos tests entièrement dans la base de données, en utilisant la technique que je souligne ici: meilleure façon de tester les requêtes SQL .
C'est pourquoi les tests unitaires (IMHO) peuvent parfois créer un faux sentiment de sécurité de la part des développeurs. Dans mon expérience avec les applications qui parlent à une base de données, les erreurs sont généralement le résultat de données étant dans un état inattendu (valeurs inhabituelles ou manquantes, etc.). Si vous avez l'habitude de simuler l'accès aux données dans vos tests unitaires, vous penserez que votre code fonctionne bien alors qu'il est en fait encore vulnérable à ce genre d'erreur.
je pense que votre meilleure approche est pour avoir une base de données de test à portée de main, rempli de données merdiques, et exécuter vos tests de composants de base de données contre cela. Tout en se souvenant que vos utilisateurs sera beaucoup beaucoup mieux que vous êtes au vissage vos données.
le point entier d'un essai unitaire est tester une unité (duh) isolément. Le point entier d'un appel de base de données est intégrer avec une autre unité (la base de données). Ergo: cela n'a pas de sens de tester les appels de base de données.
vous devriez, cependant, les appels de base de données de test d'intégration (et vous pouvez utiliser les mêmes outils que vous utilisez pour le test de l'unité si vous voulez).
pour L'amour de Dieu, ne testez pas contre une base de données vivante, déjà peuplée. Mais vous le saviez déjà.
en général, vous avez déjà une idée du type de données que chaque requête va récupérer, que vous soyez en train d'authentifier des utilisateurs, de rechercher des entrées dans un annuaire/organigramme, ou n'importe quoi d'autre. Vous savez quels sont les domaines qui vous intéressent et quelles sont les contraintes qui s'y rattachent (p. ex., UNIQUE
, NOT NULL
, etc.). Vous testez votre code qui interagit avec la base de données, pas la base de données elle-même, donc pensez en termes de comment tester ces fonctions. S'il est possible qu'un champ soit NULL
, vous devriez avoir un test qui s'assure que votre code gère correctement les valeurs NULL
. Si l'un de vos champs est une corde ( CHAR
, VARCHAR
, TEXT
, &c), tester pour être sûr que vous manipulez correctement les caractères échappés.
suppose que les utilisateurs vont essayer de mettre n'importe quoi* dans la base de données, et générer test cas en conséquence. Vous voudrez utiliser des objets simulés pour cela.
* y compris les entrées indésirables, malveillantes ou invalides.
vous pouvez tout tester à l'unité sauf: queryDA.Fill (resultSet);
dès que vous exécutez queryDA.Fill (resultSet), vous devez soit simuler/truquer la base de données, ou vous faites des tests d'intégration.
pour ma part, je ne vois pas le test de l'intégration comme étant mauvais, c'est juste qu'il va attraper une autre sorte de bug, a cote de faux négatifs et de faux positifs, n'est pas susceptible d'être fait très souvent parce que c'est si lent.
si j'étais unité à tester ce code, je validerais que les paramètres sont construits correctement, est-ce que la commande builder crée le bon nombre de paramètres? - Ils ont tous une valeur? Est-ce que nulls, empty strings et DbNull sont manipulés correctement?
en fait, remplir l'ensemble de données teste votre base de données, qui est un composant qui sort de la portée de votre DAL.
à proprement parler, un test qui écrit/lit à partir d'une base de données ou d'un système de fichiers n'est pas un test unitaire. (Bien qu'il puisse s'agir d'un test d'intégration et qu'il puisse être écrit en utilisant NUnit ou JUnit). Les tests unitaires sont censés tester les opérations d'une seule classe, isolant ses dépendances. Ainsi, lorsque vous écrivez unité-test pour l'interface et les couches business-logic, vous ne devriez pas avoir besoin d'une base de données du tout.
OK, mais comment tester la couche d'accès à la base de données? J'aime l' Conseil de ce livre: xUnit test Patterns (le lien pointe vers le chapitre" Testing w/ DB " du livre. Les clés sont:
- utilisation de l'aller-retour des tests
- n'écrivez pas trop de tests dans votre appareil de test d'accès aux données, car ils seront beaucoup plus lents que vos "vrais" tests unitaires
- si vous pouvez éviter de tester avec une base de données réelle, tester sans base de données
pour les tests unitaires, je simule ou simule la base de données. Ensuite, utilisez votre simulation ou votre fausse implémentation via l'injection de dépendances pour tester votre méthode. Vous aurez aussi probablement quelques tests d'intégration qui testeront les contraintes, les relations clés étrangères, etc. dans votre base de données.
quant à ce que vous testeriez, vous vous assureriez que la méthode utilise la connexion à partir des paramètres, que la chaîne de requête est assignée à la commande, et que votre résultat est retourné est le même que celui que vous fournissez via une attente sur la méthode de remplissage. Note -- il est probablement plus facile de tester une méthode Get qui renvoie une valeur qu'une méthode Fill qui modifie un paramètre.
afin de faire ceci correctement bien que vous devriez utiliser une certaine injection de dépendance (DI), et pour .NET il y en a plusieurs. J'utilise actuellement le cadre de L'unité, mais il y en a d'autres qui sont plus faciles.
voici un lien à partir de ce site sur ce sujet, mais il ya d'autres: injection de dépendance dans .NET avec des exemples?
cela vous permettrait de plus facilement moquer d'autres parties de votre application, en avoir une classe simulée implémenter l'interface, de sorte que vous pouvez contrôler la façon dont il va répondre. Mais, cela signifie aussi concevoir à une interface.
puisque vous avez posé des questions sur les meilleures pratiques, il s'agit de L'OMI.
alors, ne pas aller au db à moins que vous devez, comme suggéré est un autre.
si vous avez besoin de tester certains comportements, tels que les relations clé étrangère avec la suppression de cascade, vous pouvez écrire des tests de base de données pour cela, mais généralement, ne pas aller à une base de données réelle est préférable, PSR puisque plus d'une personne peut exécuter un test unitaire à la fois et si elles vont à la même base de données tests peuvent échouer que les données attendues peuvent changer.
Edit: Par unité de base de données de test que je veux dire, car il est conçu pour l'utilisation juste de t-sql, certains d'installation, de test et de démontage. http://msdn.microsoft.com/en-us/library/aa833233%28VS.80%29.aspx
sur le projet basé sur JDBC, la connexion JDBC peut être moquée, de sorte que les tests peuvent être exécutés sans RDBMS en direct, avec chaque cas de test isolé (pas de conflit de données).
il permet de vérifier, le code de persistance passe les requêtes/paramètres appropriés (par exemple https://github.com/playframework/playframework/blob/master/framework/src/anorm/src/test/scala/anorm/ParameterSpec.scala ) et traiter les résultats JDBC (parsing/mapping) comme prévu ("prend dans toutes les les bits et les morceaux nécessaires pour extraire certaines données de la base de données, et renvoie les données dans un objet DataTable").
Framework comme jOOQ ou Acolyte peut être utilisé pour: https://github.com/cchantep/acolyte .