Concepts : Raccords
Rubriques
Un composant est testé par l'envoi d'entrées à ses interfaces, l'attente de leur traitement par le composant, puis la vérification des résultats. Au cours du traitement, il est probable que le composant en sollicite d'autres en leur envoyant des entrées et en exploitant leurs résultats :

Figure 1: Test d'un composant que vous avez implémenté
Ces autres composants peuvent soulever des problèmes pour votre test :
- Ils peuvent n'avoir pas encore été implémentés.
- Ils peuvent comporter des failles empêchant le fonctionnement de votre test ou nécessitant beaucoup de temps jusqu'à découvrir un échec du test dont votre composant n'est pas en cause.
- Ils peuvent rendre difficile l'exécution des tests au moment opportun. S'il s'agit d'une base de données commerciale, vous pouvez ne pas avoir assez de licences flottantes pour tous les intervenants. Ou il peut s'agir d'un matériel qui n'est disponible qu'à certains moments et dans un laboratoire distinct.
- Ils peuvent ralentir les tests au point de réduire excessivement leur fréquence d'exécution. L'initialisation d'une base de données peut prendre cinq minutes par test, par exemple.
- Il peut être difficile d'induire les composants à générer certains résultats. Vous pouvez désirer, par exemple, que toutes vos méthodes impliquant des écritures sur disque gèrent les erreurs de type "disque saturé". Comment s'assurer alors que le disque se remplit juste au moment où la méthode est appelée ?
Pour contourner ces problèmes, vous pouvez choisir d'utiliser des composants de type raccord (aussi dénommés objets fictifs).
Ces raccords se comportent comme les composants réels, tout au moins en ce qui concerne les valeurs que leur envoie votre composant tout en répondant à ses tests. Ils peuvent aller plus loin et remplir le rôle d'émulateurs à usage général, qui imitent fidèlement la majorité ou la totalité des comportements du composant. Il est souvent judicieux, par exemple, de construire des émulateurs logiciels pour le matériel. Ils se comportent exactement comme le matériel correspondant, sauf qu'ils sont plus lents. Ils sont utiles puisqu'ils permettent un meilleur déboguage, leur nombre est moins restreint et ils peuvent être utilisés avant même que le matériel ne soit prêt.

Figure 2: Test d'un composant que vous avez implémenté par raccord d'un composant dont il dépend
Les raccords ont deux inconvénients :
-
Leur construction peut être coûteuse (ceci est particulièrement vrai des émulateurs). Comme il s'agit de logiciels, vous devez aussi assurer leur maintenance.
-
Ils peuvent masquer des erreurs. Supposez, par exemple, que votre composant utilise des fonctions trigonométriques mais qu'aucune bibliothèque ne soit encore disponible. Vos trois cas de test réclament le sinus de trois angles : 10 degrés, 45 degrés et 90 degrés.
Vous utilisez une calculatrice pour déterminer les valeurs correctes, puis construisez un raccord pour le sinus renvoyant respectivement 0,173648178, 0,707106781 et 1,0. Tout est parfait jusqu'au moment où vous intégrez votre composant avec la bibliothèque trigonométrique réelle, dont la fonction sinus reçoit des arguments exprimés en radians et retourne par conséquent -0,544021111,
0,850903525, et 0,893996664. Il s'agit ici d'un défaut de votre code qui n'est découvert que plus tard et au prix de plus d'efforts que souhaitable.
Sauf si les raccords sont construits avant que le composant réel ne soit disponible, vous pouvez prévoir de les conserver au delà du déploiement. Les tests qu'ils prennent en charge seront probablement importants pour la maintenance. Les raccords doivent, par conséquent, être rédigés avec des standards plus élevés que du code temporaire. Bien qu'ils n'aient pas à se conformer aux normes du code produit (la plupart, par exemple, ne requièrent pas leur propre suite de tests), les développeurs devront par la suite assurer leur maintenance suite à des modifications aux composants du produit. Si cette maintenance est trop complexe, les raccords seront abandonnés et l'investissement correspondant perdu.
Les raccords entraînent des modifications de la conception des composants, tout particulièrement lorsqu'ils destinés à être conservés. Supposez, par exemple, que votre composant utilise une base de données pour le stockage persistant de paires clé/valeur. Envisagez ces deux scénarios de conception :
Scénario 1: La base de données est utilisée tant pour les tests que pour un usage normal. L'existence de la base de données n'a pas à être cachée du composant. Vous pouvez l'initialiser avec le nom de la base de données :
public Component(String databaseURL) {
try {
databaseConnection =
DriverManager.getConnection(databaseURL);
...
} catch (SQLException e) {...}
}
Et, bien que le but ne soit pas que chaque position lisant ou écrivant une valeur construise une instruction SQL, certaines méthodes contiendront indubitablement du code SQL. Par exemple, le code du composant nécessitant une valeur pourrait appeler cette méthode :
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 par un raccord pour les tests. Le code du composant doit avoir la même apparence qu'il s'exécute face à la base de données réelle ou à un raccord. Il doit donc être codé de sorte à utiliser les méthodes d'une interface abstraite :
interface KeyValuePairs {
String get(String key);
void put(String key, String value);
}
Les tests implémenteront alors KeyValuePairs à l'aide d'un mécanisme 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 l'objet d'un test, le composant utilisera alors un objet adaptateur convertissant les appels à l'interface KeyValuePairs
en 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 avoir un constructeur unique pour les tests et d'autres clients. Ce constructeur recevrait un objet implémentant KeyValuePairs, ou il pourrait ne fournir cette interface que pour les tests, requérant alors des clients ordinaires du composant qu'ils communiquent le nom d'une base de données :
class Component {
public Component(String databaseURL) {
this.valueStash = new DatabaseAdapter(databaseURL);
}
// Pour les tests :
protected Component(KeyValuePairs valueStash) {
this.valueStash = valueStash;
}
}
Du point de vue des programmeurs de clients, les deux scénarios de conception débouchent donc sur la même interface de programmation (API), mais l'un est plus facilement testable que l'autre. Notez que certains tests peuvent utiliser la base de données réelle tandis que d'autres utiliseront celle du raccord.
Pour de plus amples informations concernant les raccords, reportez-vous à la documentation suivante :
|