Un composant est testé en envoyant des entrées à son interface, puis en attendant qu'il les traite et en vérifiant les
résultats. Lors du traitement, un composant a de forte chances d'utiliser d'autres composants en leur envoyant des
entrées et en utilisant leurs résultats :
Fig1 : Test d'un composant que vous avez implémenté
Ces autres composants peuvent vous poser problème pour votre test :
-
Il se peut qu'ils ne soient pas encore implémentés.
-
Ils peuvent entraîner des incidents qui empêchent le test de fonctionner ou vous font perdre beaucoup de temps
avant de vous rendre compte que l'échec du test n'a pas été causé par votre composant.
-
Ils peuvent rendre difficile l'exécution des tests dont vous avez besoin. Si le composant est une base de données
commerciale, votre société peut ne pas avoir assez de licences flottantes pour tout le monde. Il se peut aussi que
l'un des composants soit du matériel qui ne soit disponible qu'à des heures définies dans un laboratoire séparé.
-
Ils peuvent ralentir vos tests de sorte que ceux-ci ne sont pas assez souvent exécutés. L'initialisation de la base
de données, par exemple, peut prendre cinq minutes par test.
-
Il peut être difficile de forcer les composants à produire certains résultats. Vous voulez, par exemple, que chaque
méthode qui écrit dans le disque traite les erreurs "disque plein". Comment pouvez-vous être sûr que le disque se
remplit juste au moment où une méthode est appelée ?
Afin d'éviter ce genre de problèmes, vous pouvez décider d'utiliser des composants module de remplacement (aussi
appelés objets factices). Les composants module de remplacement se comportent comme les composants réels, du
moins en ce qui concerne les valeurs que votre composant leur envoie en exécutant ses tests. Ils peuvent aller plus
loin : ils peuvent être des émulateurs génériques qui cherchent à reproduire fidèlement la plupart ou tous les
comportements du composant. Il est souvent judicieux, par exemple, de construire des émulateurs logiciels pour le
matériel. Ils se comportent comme le matériel mais fonctionnent un peu plus lentement. Ils sont utiles car ils
supportent mieux le débogage, leurs copies disponibles sont plus nombreuses et on peut les utiliser avant que le
matériel ait terminé.
Fig2 : Test d'un composant que vous avez implémenté en 'simulant' un composant dont il dépend
Les modules de remplacement ont deux inconvénients.
-
Ils peuvent être chers à la construction. (C'est le cas en particulier des émulateurs.) Etant eux-mêmes des
logiciels, ils ont besoin de maintenance.
-
Ils peuvent masquer des erreurs. Supposez, par exemple, que votre composant utilise des fonctions de trigonométrie
mais qu'aucune bibliothèque ne soit encore disponible. Vos trois jeux de test demandent les sinus de trois angles :
10 degrés, 45 degrés et 90 degrés. Vous prenez votre calculatrice pour obtenir les valeurs exactes, qui sont
0,173648178, 0,707106781 et 1,0 respectivement et construisez un module de remplacement pour celles-ci. Tout se
passe bien jusqu'à ce que vous intégriez la bibliothèque trigonométrique réelle dont les paramètres effectifs de la
fonction sinus sont en radiant et qui donne alors comme résultat -0,544021111, 0,850903525, et 0,893996664.
Il s'agit d'une erreur dans votre code qui est découvert plus tard, et a demandé plus d'efforts, que vous n'auriez
voulu.
A moins qu'ils n'aient été construits parce que le composant réel n'était pas encore disponible, vous conserverez vos
modules de remplacement au-delà du déploiement. Les tests qu'ils supportent risquent d'être importants lors de la
maintenance du produit. Il faut donc écrire les modules de remplacement de manière plus formelle qu'un code jetable.
Bien qu'ils n'aient pas besoin de répondre aux normes du code du produit (la plupart n'ont par exemple pas besoin d'une
suite de tests qui leur soit propre), les développeurs de logiciels devront les maintenir en tant que composants du
changement du produit. Si cette maintenance s'avère trop difficile, les modules de remplacement seront éliminés et
l'investissement sera perdu.
Les modules de remplacement affectent la conception des composants, en particulier lorsqu'ils doivent être conservés.
Supposez, par exemple, que votre composant utilise une base de données pour stocker de façon permanente des paires
clé/valeur. Considérez deux scénarios de conception :
Scénario 1 : la base de données est aussi bien utilisée pour les tests que pour une utilisation normale.
L'existence de la base de données n'a pas besoin d'être cachée au composant. Vous pourriez l'initialiser avec le nom de
la base de données :
public Component(
String databaseURL) { try { databaseConnection = DriverManager.getConnection(databaseURL); ... } catch (SQLException e) {...} }
Bien que vous ne souhaitiez pas que chaque localisation lisant ou écrivant une valeur construise une instruction SQL, vous
aurez certainement des méthodes qui contiendront du SQL. Par exemple, un code de composant ayant besoin d'une valeur peut
appeler cette méthode de composant :
public String get(String key) { try { Statement stmt = databaseConnection.createStatement(); ResultSet rs = stmt.executeQuery(
"SELECT value FROM Table1 WHERE key=" + key); ... } catch (SQLException e) {...} }
Scénario 2 : la base de données est remplacée pour le test par un module de remplacement. Le code du composant doit
être le même, qu'il fonctionne pour la base de données réelle ou pour le module de remplacement. Il doit donc être codé
pour utiliser les méthodes d'une interface abstraite :
interface KeyValuePairs { String
get(String key); void
put(String key, String value); }
Les tests implémenteraient KeyValuePairs avec un élément simple comme une table de hachage :
class FakeDatabase implements KeyValuePairs { Hashtable table = new Hashtable(); public String
get(String key) { return (String) table.get(key); } public void
put(String key, String value) { table.put(key, value); } }
Lorsqu'il n'est pas utilisé dans un test, un composant utilise un objet adaptateur
qui a converti les appels vers l'interface KeyValuePairs dans les instructions SQL :
class DatabaseAdapter implements KeyValuePairs { private Connection databaseConnection; public DatabaseAdapter(String databaseURL) { try { databaseConnection = DriverManager.getConnection(databaseURL); ... } catch (SQLException e) {...} } public String
get(String key) { try { Statement stmt = databaseConnection.createStatement(); ResultSet rs = stmt.executeQuery( "SELECT value FROM Table1 WHERE key=" + key); ... } catch (SQLException e) {...} } public void
put(String key, String value) { ... } }
Votre composant peut n'avoir qu'un seul constructeur pour les tests et les autres clients. Ce constructeur peut prendre un
objet qui implémente KeyValuePairs. Il peut aussi fournir cette interface pour les tests uniquement,
requérant que les clients habituels du composant fassent passer le nom d'une base de données :
class Component {
public Component(String databaseURL) { this.valueStash = new DatabaseAdapter(databaseURL); } // For testing.
protégé Component(KeyValuePairs valueStash) { this.valueStash = valueStash; } }
Du point de vue des programmeurs client, les deux scénarios de conception donnent la même API, mais l'un est plus facile à
tester que l'autre. (Notez bien que certains tests peuvent utiliser la base de données réelle et d'autres, la base de
données module de remplacement.)
Pour plus d'informations concernant les modules de remplacement, voir :
|