Tests unitaires : comment les mettre en pratique ?

Bonjour à tous,

Dans notre équipe, on utilise actuellement des méthodes de test un peu… archaïques, et je voudrais mettre en place des tests unitaires. Le problème, c’est que je ne vois pas du tout comment faire dans le cas d’une application serveur dont la fonction principale et d’importer et de transformer des données provenant de différentes bases de données (Sybase, Oracle, Informix, etc.) vers un serveur SQL Server :

  • Comment tester dans le cas où les serveurs de base de données sont en lecture seule, et dont les données sont relativement éphémères ? Nos serveurs de test sont uniquement des SQL Server, (donc avec une syntaxe différente des serveurs de production), ce qui veut dire que mettre en place une base de donnée factice impliquerait la réécriture d’une grosse partie du code pour supporter SQL Server, code qui ne serait utilisé que pour les tests unitaires (et tester du code écrit spécifiquement pour un test, je vois pas trop l’intérêt :paf:)

  • Avez-vous des liens ou des conseils pour aborder ce genre de problèmes ?

  • Sinon, existe-t-il d’autres méthodes ou outils de tests plus appropriées ? Au minimum pour se protéger des régressions.

Merci d’avance !
Edité le 24/06/2009 à 19:07

Exposer une solution / méthode toute faite avec plein de bonnes pratiques est dangereux … ne serait ce pédagogiquement parlant.

Donc, partons du début : comment faites vous actuellement pour tester vos livrables ? A l’arrache ? :smiley:

Je crois que c’est malheureusement une très bonne description ! :ane:

Disons qu’au départ, on essaye quand même de tester au maximum les composants, notamment en vérifiant la cohérence des données générées, et à ce niveau ça ne fonctionne pas trop mal.

Le problème c’est surtout quand on apporte des modifications, on n’a pas toujours l’envie (et la motivation :whistle:) de tester à nouveau tous les différents cas de figure. Ce qui fait qu’au bout d’un moment, on fini toujours par effectuer une petit modif “innocente” qui fait tout planter. :confused:

Pour prendre un exemple concret, on peut avoir besoin de développer une fonction qui va lire des données dans une table Sybase du style :

+-----------------+
|Nom.....| Nombre |
+-----------------+
|Pierre..|..... 4 |
|Paul....|..... 2 |
|Jacques.|..... 3 |
|Pierre..|..... 7 |
|Pierre..|..... 2 |
|Jacques.|..... 5 |
|Paul....|..... 1 |
+--------+--------+

Et qui va juste les agréger et les insérer dans une table SQL Server du style :

+-----------------+
|Nom.....| Nombre |
+-----------------+
|Pierre..|.... 13 |
|Paul....|..... 3 |
|Jacques.|..... 8 |
+--------+--------+

Donc rien d’extraordinaire, mais le problème c’est que les données sur le serveur source ne sont pas persistantes, et donc peuvent disparaître au bout d’un moment… alors pour faire des tests unitaires, c’est pas top.

La solution à laquelle je pensais, ce serait sinon de monter une base de données avec des données factices persistantes, mais vient alors le deuxième souci, qui est que nous n’avons aucun serveur Sybase de test, donc tester un import Sybase -> SQL Server avec un import SQL Server -> SQL Server, ça limite beaucoup l’intérêt. :confused:

Est-ce que je suis un cas désespéré, ou bien est-ce qu’il y a une technique salvatrice pour ce genre de situation ?

Absolument pas ! Enfin, jvoulais dire pour le cas desespere. Je vais te donner une facon de tester. Elle vaut ce qu’elle vaut, c a d qu’elle est surement perfectible et tout et tout, mais l’objectif pour moi est de te donner qqch de simple, histoire de limiter l’effort.

Le besoin, si je le formule betement, ce serait :

Jusque là, ok. Pour tester, je charge des données dans la source, je lance mon programme, et je regarde la cible.

Côté contraintes, je note l’indisponibilité logicielle de Sybase.

Vient mes questions pour cibler l’objet du test :

  • Existe-il une logique métier lié à vos migrations ? Exemple : un champ renommé, une table splittée, etc.
    Ici, il est clairement question de savoir ce qui permet de valider ton ou tes programme(s). Ca ne sert à rien de surtester un programme, car cela a -entre autres- un effet polluant sur la compréhension du test.

  • De quoi se compose le programme ? Scripts Shell ? Programmes interpretés ou compilés ?
    Améliorer ou proposer l’outillage suffisant pour tes tests.

  • Est-il quand meme possible d’obtenir une licence de Sybase pour tester ?
    Dans le cas de test d’intégration “Vérifier que l’application s’intègre dans mes logiciels”, il vaut mieux que ce soit faisable …
    Edité le 23/06/2009 à 11:15

Merci pour ce début de réponse, en ce qui concerne tes questions :

  • Oui, il existe plusieurs règles métier, les tables subissant tout un tas de transformations. Par exemple, la table source va être une liste d’employées avec toutes les actions effectuées au cours d’une journée (une entrée par action) du style:

Timestamp AS datetime
NomEmployé AS nvarchar(MAX)
ID_Action AS int (valeur entre 1 et 3)

Le programme doit agréger les données pour avoir une entrée par employé et par jour, dans une table du style :


Timestamp AS datetime
NomEmployé AS nvarchar(MAX)
NombreAction1 AS int
NombreAction2 AS int
NombreAction3 AS int

La requête SQL utilisée est du style :


SELECT
 (CAST(FLOOR(CAST(Timestamp AS FLOAT)) AS SMALLDATETIME) AS Timestamp,
 NomEmployé, 
 SUM(CASE ID_Action WHEN 1 THEN 1 ELSE 0 END) AS NombreAction1, 
 SUM(CASE ID_Action WHEN 2 THEN 1 ELSE 0 END) AS NombreAction2, 
 SUM(CASE ID_Action WHEN 3 THEN 1 ELSE 0 END) AS NombreAction3

FROM table_source

GROUP BY
 (CAST(FLOOR(CAST(Timestamp AS FLOAT)) AS SMALLDATETIME) AS Timestamp,
 NomEmployé

La requête SQL est générée dynamiquement à partir d’une classe qui prend une description de table en entrée. Cette classe est utilisée à plusieurs endroits du programme, et est améliorée au fur et à mesure des besoins, et c’est donc l’une des classes que j’aurais besoin de tester contre la regréssion.

  • Le programme est un projet VB.NET relativement gros, avec un bon nombre de DLLs satellites, compilé à partir de Visual Studio 2003 (mais on est actuellement en train de migrer vers 2008). J’ai déjà testé certains outils de tests unitaires comme ceux intégrés à VS2008 ainsi que NUnit, mais dans mon cas particulier je n’ai pas compris comment je pouvais les mettre à profit. :confused:

  • Pour la licence Sybase, ce n’est pas vraiment envisageable pour l’instant. On a quand même la possibilité d’utiliser les serveurs de production en tant que serveurs de test hors des heures de production, mais ce n’est pas toujours très pratique. Par contre, une fois que le code originel est testé et fonctionne, l’accès aux serveurs de production est quand même plus souple, et on peut tester les modifications dans la journée sans (trop) craindre de se faire taper sur les doigts, l’accès étant de toutes façons en lecture seule.

Après, quitte à faire ça à l’arrache, c’est vrai que je pourrais mettre en place des tests unitaires qui se connectent directement aux serveurs de productions, mais le problème c’est que ces données ne sont pas persistantes (certaines sont stockées plusieurs mois, mais d’autres sont mises à jour quasiment en temps réel), donc tôt ou tard ça coincerait. :confused:

Ok je vois mieux, et ce à quoi je m’attendais à peu près. Dans la suite du message, je tacherai d’etre le plus concis possible, mais n’hésite pas à me demander de détailler ou justifier un point ou un autre.

Alors pour une stratégie de tests unitaires, je te recommanderais de ne tester, dans un premier temps, que sur la logique applicative. L’objectif théorique, c’est :

  1. (simple) de tester en une fois un minimum de code, afin de garder le test le plus clair possible.
  2. (iso fct) de s’assurer que ton test doit ne doit pas influer l’exécution des autres tests.

De là, différentes questions et suggestions (dans le sens des objectifs précédents) :

  • (simple) disposer de bases vierges
  • (iso fct) principe de base : ouvrir une transaction, faire un test, annuler la transaction, recommencer
  • (simple) est ce que tu utilises un framework de persistance (nHibernate ?)
  • écriture en TDD : test + code pour que ca compile, exécution du test KO, code pour test OK, refactor
  • (simple) pas besoin de se pencher sur la prod. si une anomalie est détectée, écrire un test qui permet de le vérifier
  • (iso fct) écrire un test sous entend d’initialiser le jeu de données correspondant

A propos du fwt de persistance : en général, et sauf si tu le sais déjà, il a l’avantage de t’éviter d’écrire du SQL. Ainsi, tu pourrais migrer sur n’importe quoi qu’on s’en ficherait … presque ! L’idée, c’est de faire confiance à nHibernate parce qu’il est testé, ce qui par conséquent et utilisation, te permet de réduire ton volume de code source, et donc le volume de tests associés.

Bon jme rends compte que ca a l’air lourd dingue à lire … donc par l’exemple :

1. Créer un schéma de base vierge Sybase (ou se débrouiller pour avoir une zone libre de tout accès et vierge)

2. Créer un schéma de base vierge SQL Server

3. Créer une classe MaMigrationTest avec une méthode simple :

  • ouvrir une transaction Sybase
  • ouvrir une transaction SQL Server
  • insérer des enregistrement dans le schéma Sybase
  • exécuter le programme dans MaMigration
  • vérifier la bonne forme des enregistrements dans le schéma SQL Server
  • rollback sur les deux transactions

4. Exécuter MaMigrationTest. Le test doit s’arreter à “vérifier la bonne …”, du genre : “expected 130, but was 0”

5. Compléter le programme MaMigration

6. Exécuter MaMigrationTest. Le test doit etre OK. Si non, vérifier le test, et éventuellement revenir à 5.

Ayé fini pour MaMigration !

Epilogue 1 : Si tu dois faire MaMigrationNextGeneration, eh bien meme topo, sauf qu’après 6, il faut lancer MaMigrationTest pour vérifier la non regression.

Epilogue 2 : Paf ! Anomalie car besoin mal foutu ou jeu de données insuffisant, ou programme mal foutu. Pas de probleme, revenir à l’étape 3 en écrivant un second test.

Merci encore pour ces précieuses informations ; au final le principe reste le même que pour un test unitaire classique, à quelques petites différences près… ça paraît donc relativement fastidieux (vu le nombre de tables et de données), mais je pense que le résultat en vaut la peine, et c’est de toutes façons un passage obligé pour avoir du code fiable !

Sinon je vais regarder du côté de nHibernate, le nom me dit quelque chose mais je ne m’en suis jamais servi… ça a l’air plutôt intéressant. :slight_smile:

Dans l’ordre (ou presque) :

  • de rien :stuck_out_tongue:

  • oui, le principe reste exactement le meme pour tout test unitaire. La “difficulté”, c’est de mettre en place des librairies, composants, assembly, etc. pour faciliter l’écriture et l’exécution de tes tests.

  • dans le cas où tu ferais partie de ces développeurs doués, qui ont une capacité d’abstraction élevée, une recommandation : prends ton temps pour le refactoring.

Refactoring : ca correspond à l’étape 7 de la démarche précédente. Une fois que tu as écrit ton premier test et ton premier code, il faut que tu sois convaincu qu’il s’agisse de la manière la plus simple d’écrire les deux. Et ce meme si tu penses qu’il y a moyen de factoriser le test pour le réutiliser plus tard. Parce que ce qui peut etre fait plus tard devrait etre fait plus tard. Concretement, ce devrait etre à partir du second test et du second code que tu identifies les lignes qui peuvent être factorisées tout en s’assurant que le test soit toujours compréhensible.

Bon, si tu t’es renseigné sur le sujet, tu conclueras que ma démarche est très orientée XP et TDD. Je ne te le cache pas … :slight_smile:

Oui, mais ça tombe bien, car c’est la direction vers laquelle je souhaite m’orienter. :wink:

Pour le refactoring, je ne sais pas si je suis doué ou pas, mais c’est en tout cas mon passe-temps favori. Par manque de temps, ce n’est pas toujours faisable, mais c’est clair que pour la maintenance, ça fait toute la différence ! (c’est un peu contradictoire, je sais, mais je pense que je ne t’apprend rien :ane:)