Ce chapitre vise à décrire des approches pour résoudre des problématiques de tests récurrentes et présente des outils complémentaires qui répondent à ces problématiques spécifiques.
Tester des applications concurrentes.
Faire appel à la concurrence dans une application peut être nécessaire, souvent pour des raisons de performance. Toutefois, la multiplication des processus, des threads ou des fibres peut créer des bugs (interblocage, famine…) car il est difficile de bien synchroniser l'application.
De plus, l'ordonnancement des tâches par les systèmes d'exploitation multi-tâches ou sur les systèmes parallèles (processeurs multi-cœurs ou systèmes multi-processeurs) n'est pas toujours le même à chaque exécution de l'application ou des applications, ce qui complique les tests.
Tout d'abord, il convient de mener une analyse statique du code de l'application, cela devrait éliminer les erreurs fréquentes.
Tester des applications distribuées.
Qu'il s'agisse d'applications client-serveurs, d'une application en architecture n-tiers, ou d'un système distribué en grande échelle (tel qu'un système pair-à-pair) : ces systèmes sont intrinsèquement concurrents. En plus de l'aspect distribué, ils posent donc aussi bien la problématique du test de système concurrent.
Une des solutions consiste, via un outil spécialisé, à faire intervenir, pour tous les nœuds un seul et même "TestRunner" centralisé faisant appel aux différents tests les uns après les autres, en attendant que chaque machine soit synchrone par rapport au déroulement du test général. Cette synchronisation centralisée lève l'indéterminisme induit par la distribution et permet de tester normalement. C'est l'approche retenue par deux outils :
Cette approche à l'avantage de permettre de tester un système "in situ", vraiment distribué (avec les temps de latence réseau notamment). Principal inconvénient, pour automatiser le déroulement des tests, il faudra également automatiser le déploiement.
Tester une application avec des données persistantes.
http://www.dbunit.org/
Les « fixtures ».
Lorsque le système testé exploite une base de données, l'écriture des tests devient compliquée.
En effet, si on utilise une base de données de test présente sur la machine du développeur, les données risquent de changer au cours du temps (tests manuels).
De même, si on exécute plusieurs tests de suite sur une même base de données et qu'un test qui modifie les données en base échoue, on a laissé la base dans un état indéterminé, peut-être incohérent, ce qui peut invalider le reste des tests.
C'est pourquoi il faut absolument cloisonner les tests et, avant chaque test, remettre la base dans un état cohérent et déterminé.
Cela peut être laborieux et cela peut aboutir à des tests qui prennent 100 lignes pour insérer des données en base (avec le risque d'erreur) puis 5 lignes pour faire effectivement le test.
C'est ici que les « fixtures » interviennent : ces "fixtures" sont des doublures qui vont venir peupler la base (vidée entre chaque test) pour la placer dans un état cohérent juste avant le test.
On décrit ce petit jeu de données dans un fichier lisible, souvent au format YAML (plus rarement en XML ou en JSON).
Par exemple :
miserables:
 title: Les misérables
 author: Victor Hugo
 year: 1862
lotr:
 title: The Lord of the Rings
 author: J. R. R. Tolkien
 year: 1954
dunces:
 title: A Confederacy of Dunces
 author: John Kennedy Toole
 year: 1980
À chaque modification du schéma de la base, on crée ou on adapte ces petits fichiers.
Une API permet alors d'interpréter ces fichiers et de charger les données en base depuis les tests.
Par exemple, avec le framework Play! (Java) :
@Before
public void setUp() {
 Fixtures.deleteAll();
 Fixtures.load("data.yml");
En l'état actuel, il faut reconnaître qu'il n'existe pas ou peu d'outils indépendants pour les fixtures. Ces derniers sont plutôt fournis avec les frameworks Web comme c'est le cas dans Play! (Java), Django (Python), Ruby on Rails ou CakePHP. Cela est bien dommage, l'usage des bases de données ne se limitant pas aux applications Web.
« Fixtures » se traduirait par « garniture », mais il semble plus pertinent d'utiliser le terme « échantillons ».
Tester les procédures embarquées d'une base de données.
Pour des raisons de performances, il est possible que des traitements "métiers" sur les données soient réalisés en base.
Ces traitements sont des codes stockés directement dans le SGBD, ils sont codés dans un langage tels que PL/SQL ou PL/pgSQL.
Plutôt que de tester ces procédures au milieu des autres tests de l'application, il est préférable de mener les tests au plus près, avec des outils insérant les données et déclenchant les procédures.
De simples requêtes de lecture en base et des assertions permettent de s'assurer que les traitements effectués par les procédures stockées produisent de bons résultats.
Parmi ces outils, on peut citer SQLUnit.
Résoudre des dépendances vers des composants complexes.
S'il est possible d'écrire des doublures pour des petits composants, certains composants complexes ne peuvent pas être vraiment doublés.
Par exemple, une base de données ou un serveur de mail (SMTP) pour une application qui envoie des e-mails à ses utilisateur.
Dans ce cas, il faut plutôt essayer de remplacer ces composants usuels par d'autres briques plus adaptées, voire prévues pour les tests.
Bases de données.
Il est fort probable que l'application a vocation à être utilisée, en production, sur une base de données client/serveur (MySQL, PostgreSQL ou autre).
Cependant, on peut envisager de profiter d'une couche d'abstraction de la base de données pour substituer cette base client/serveur par une base de données embarquée (comme SQLite pour PHP/Python/Ruby ou HSQLDB pour Java).
Bien que ces bases de données soient moins performantes et ne gèrent souvent pas la concurrence ou d'autres fonctionnalités attendues d'une bonne base de données, elles sont très souvent amplement suffisantes pour travailler sur un petit ensemble de données de test.
Serveur de mail.
L'application peut avoir besoin d'envoyer des courriels automatiques à ses utilisateurs (notifications, confirmations…). Sur leurs machines, les développeurs, lors des tests peuvent substituer au serveur de mail (SMTP) un simulacre comme Wiser pour Java ou fakemail pour Python (utilisable aussi avec SimpleTest pour PHP). Ces simulacres s'installent et se mettent à l'écoute du port 25, pour recevoir les requêtes SMTP envoyées par l'application.
Ces solutions permettent d'utiliser l'application et de provoquer l'envoi de courriels sans risquer de vraiment envoyer les courriels. Par contre, ces outils permettent de vérifier qu'un courriel a bien été envoyé. Illustrons cela à l'aide d'un extrait de la documentation de Wiser :
Wiser wiser = new Wiser();
wiser.setPort(2500); // Default is 25
wiser.start();
// Après envoi du courrier 
for (WiserMessage message : wiser.getMessages()) {
 String envelopeSender = message.getEnvelopeSender();
 String envelopeReceiver = message.getEnvelopeReceiver();
 MimeMessage mess = message.getMimeMessage();
 // il n'y a plus qu'à vérifier que ces messages ont le bon
 // destinataire, le bon contenu…
Système de fichiers.
Pour tester des applications qui font appel au stockage de données sur le système de fichiers (partage de fichiers en réseau, logiciels de sauvegarde, de téléchargement ou de transferts de fichiers…), il peut être utile d'avoir une doublure pour simuler le système de fichiers et pouvoir faire des vérifications sur les données lues ou écrites.
Tester des interfaces graphiques.
Le monkey testing.
Un « monkey test » (littéralement "test du singe") est en fait un test de fuzzing appliqué aux interfaces graphiques. Le singe va lancer l'interface, et appuyer au hasard sur des boutons, entrer des données aléatoires etc. On peut ainsi détecter les cas où une mauvaise entrée provoque une erreur.
Les développeurs Android (une plateforme pour téléphone mobile qui permet de réaliser des applications graphiques prenant en charge un écran tactile) peuvent utiliser un singe fourni avec le Kit de développement pour tester leurs applications. Le singe appuie sur des boutons, remplit les formulaires mais peut aussi simuler des glisser-déposer sur l'écran tactile.
Tester des interfaces Web.
Divers outils spécialisés permettent de programmer des cas d'utilisation d'une interface Web, par exemple :
L'outil peut également proposer, plutôt que de programmer les tests, d'enregistrer la série de manipulations effectuées par un utilisateur dans un navigateur (clics, soumission de formulaires...) et de la reproduire dans les tests.
Parmi ces outils, citons :
Ces outils sont comparés dans l'article « List of web testing tools » de Wikipedia anglophone.
Tellurium Automated Testing Framework, JWebUnit.
Tester des interfaces lourdes.
De la même façon, des outils sont spécialisés pour le test fonctionnel d'interfaces utilisateurs lourdes basées sur toolkit comme SWING (Java), par exemple.
Tester du code destiné à fonctionner dans un serveur d'application.
En Java, si vous développez une application faisant appel à des composants JEE, tels qu'EJB, JPA ou autre, vous n'allez pas tester en déployant votre application dans un JBoss durant les tests. Vous pouvez essayer de remplacer ces dépendances par d'autres plus légères (comme Apache OpenEJB). Depuis Java 1.6, il doit être possible d'utiliser EJB de façon embarquée via javax.ejb.embeddable.*.
Tester une application Android.
L'API Android intègre déjà des éléments permettant d'écrire des tests pour les activités, ils se trouvent dans package android.test.*. Un outils de doublures est également inclus dans package android.test.mock.*. La documentation développeur intègre plusieurs chapitres consacrés au tests.
Il existe également des outils spécialisés :
Gérer du code patrimonial.
Le code patrimonial (« code légué » ou « legacy code ») est une base de code souvent de mauvaise qualité, dont plus personne ou peu de personnes n'a encore la connaissance. Il s'agit de projets anciens faisant souvent appel à des technologies anciennes voire obsolètes mais qui sont toujours en production.
Les tests peuvent aider à maintenir ce genre de code. Lorsqu'on doit ainsi corriger un bogue dans une telle application et qu'il n'y a pas ou peu de tests, on peut commencer par écrire des tests, simples d'abord. On poursuit ensuite en raffinant les tests, pour se rapprocher de l'origine du bogue. On a trouvé le bogue une fois qu'on a écrit un test qui ne passe pas alors qu'il devrait. Le bogue, ainsi isolé, peut maintenant être corrigé. On peut repasser tous les tests écrits depuis le début pour vérifier qu'on n'a pas provoqué une régression en modifiant le code. On parle alors de « characterization test », des tests qui assurent que le programme fonctionne de la même manière qu'au moment où les tests ont été écrits.
En procédant ainsi pour chaque bogue découvert, on constitue une base de tests et on tend ainsi à retrouver un environnement de travail plus sain.