Qt Test Bonnes pratiques
Nous vous recommandons d’ajouter des Qt Tests pour les corrections de bogues et les nouvelles fonctionnalités. Avant de tenter de corriger un bogue, ajoutez un test de régression (de préférence automatique) qui échoue avant la correction, reproduisant ainsi le bogue, et qui réussit après la correction. Lorsque vous développez de nouvelles fonctionnalités, ajoutez des tests pour vérifier qu’elles fonctionnent comme prévu.
Le respect d'un ensemble de normes de codage augmentera les chances que les autotests Qt fonctionnent de manière fiable dans tous les environnements. Par exemple, certains tests doivent lire des données sur le disque. Si aucune norme n'est définie concernant la manière de procéder, certains tests ne seront pas portables. Par exemple, un test qui part du principe que ses fichiers de données de test se trouvent dans le répertoire de travail actuel ne fonctionne que pour une compilation « in-source ». Dans une compilation « shadow » (en dehors du répertoire source), le test ne parviendra pas à trouver ses données.
Les sections suivantes contiennent des directives pour l'écriture de tests Qt Test :
- Principes généraux
- Rédaction de tests fiables
- Améliorer la sortie des tests
- Écrire du code testable
- Configuration des machines de test
Les conseils relatifs aux considérations de sécurité doivent être pris en compte parallèlement à ces bonnes pratiques.
Principes généraux
Les sections suivantes fournissent des lignes directrices générales pour l'écriture de tests unitaires :
- Vérifier les tests
- Donner des noms descriptifs aux fonctions de test
- Écrivez des fonctions de test autonomes
- Évitez les dépendances externes
- Tester l'ensemble de la pile
- Réalisez rapidement des tests complets
- Utilisez les tests pilotés par les données
- Utiliser des outils de couverture
- Sélectionnez les mécanismes appropriés pour exclure des tests
- Évitez Q_ASSERT
Vérifiez les tests
Écrivez et validez vos tests en même temps que votre correction ou votre nouvelle fonctionnalité sur une nouvelle branche. Une fois que vous avez terminé, vous pouvez extraire la branche sur laquelle repose votre travail, puis y intégrer les fichiers de test correspondant à vos nouveaux tests. Cela vous permet de vérifier que les tests échouent bien sur la branche précédente, et qu’ils détectent donc effectivement un bug ou testent une nouvelle fonctionnalité.
Par exemple, le workflow pour corriger un bug dans la classe ` QDateTime ` pourrait se présenter ainsi si vous utilisez le système de contrôle de version Git :
- Créez une branche pour votre correction et vos tests :
git checkout -b fix-branch dev - Écrivez un test et corrigez le bogue.
- Compilez et testez en intégrant à la fois le correctif et le nouveau test, afin de vérifier que ce dernier réussit une fois le correctif appliqué.
- Ajoutez le correctif et le test à votre branche :
git add tests/auto/corelib/time/qdatetime/tst_qdatetime.cpp src/corelib/time/qdatetime.cpp - Validez le correctif et le test dans votre branche :
git commit -m 'Fix bug in QDateTime' - Pour vérifier que le test détecte bien le problème pour lequel vous aviez besoin de la correction, récupérez la branche sur laquelle vous avez basé votre propre branche :
git checkout dev - Extrayez uniquement le fichier de test de la branche de correction :
git checkout fix-branch -- tests/auto/corelib/time/qdatetime/tst_qdatetime.cppLe reste de l'arborescence source reste sur la branche « dev », sans le correctif, mais avec le nouveau test prêt à être exécuté dessus.
- Compilez et exécutez le test pour vérifier qu’il échoue sur la branche « dev », et qu’il détecte donc bien un bug.
- Vous pouvez désormais revenir à la branche « fix » :
git checkout fix-branch - Vous pouvez également restaurer votre arborescence de travail à un état vierge sur « dev » :
git checkout HEAD -- tests/auto/corelib/time/qdatetime/tst_qdatetime.cpp
Lorsque vous révisez une modification, vous pouvez adapter ce workflow pour vérifier que la modification s’accompagne bien d’un test ciblant le problème qu’elle corrige.
Donnez des noms descriptifs aux fonctions de test
Le choix du nom des cas de test est important. Le nom du test apparaît dans le rapport d’échec d’une exécution de test. Pour les tests pilotés par les données, le nom de la ligne de données apparaît également dans le rapport d’échec. Des noms bien choisis peuvent donner à ceux qui lisent le rapport une première indication de ce qui n’a pas fonctionné.
Les noms des fonctions de test doivent indiquer clairement ce que la fonction cherche à tester. N’utilisez pas simplement l’identifiant du système de suivi des bogues, car ces identifiants deviennent obsolètes si le système de suivi est remplacé. De plus, les développeurs travaillant hors ligne ne peuvent pas accéder à votre outil de suivi des bogues et certains de ces outils peuvent ne pas être accessibles à tous les utilisateurs. Lorsque le rapport de bogue est susceptible d’intéresser des lecteurs ultérieurs du code de test, vous pouvez le mentionner dans un commentaire à côté d’une partie pertinente du test.
De même, lorsque vous écrivez des tests pilotés par les données, donnez aux cas de test des noms descriptifs qui indiquent sur quel aspect de la fonctionnalité chacun se concentre. Ne vous contentez pas de numéroter le cas de test ou d’utiliser des identifiants du système de suivi des bogues. Une personne lisant la sortie du test n’aura aucune idée de la signification de ces numéros ou identifiants. Vous pouvez ajouter un commentaire sur la ligne de test mentionnant l’identifiant du système de suivi des bogues, le cas échéant. Il est préférable d’éviter les espaces et les caractères pouvant avoir une signification particulière pour les interpréteurs de ligne de commande sur lesquels vous pourriez vouloir exécuter les tests. Cela facilite la spécification du test et du tag sur la ligne de commande de votre programme de test — par exemple, pour limiter une exécution à un seul cas de test.
Écrivez des fonctions de test autonomes
Au sein d’un programme de test, les fonctions de test doivent être indépendantes les unes des autres et ne doivent pas dépendre de l’exécution préalable d’autres fonctions de test. Vous pouvez vérifier cela en exécutant la fonction de test seule à l’aide de ` tst_foo testname`. Pour les tests pilotés par les données, évitez de même les dépendances entre les lignes du tableau de données du test afin que ` tst_foo function:tag ` puisse être utilisé pour exécuter une seule ligne de manière isolée (par exemple, pour déterminer pourquoi elle échoue).
Ne réutilisez pas les instances de la classe testée dans plusieurs tests. Les instances de test (par exemple les widgets) ne doivent pas être des variables membres des tests, mais doivent de préférence être instanciées sur la pile afin de garantir un nettoyage correct même si un test échoue, de sorte que les tests n’interfèrent pas les uns avec les autres.
Si votre test implique d’effectuer des modifications globales, veillez à ce que l’état antérieur soit restauré à la fin du test, qu’il réussisse ou échoue. Étant donné qu’un échec empêche l’exécution du code situé après le point de contrôle ayant échoué, la restauration à la fin du test ne fonctionne pas lorsque celui-ci échoue. La méthode la plus fiable pour restaurer l’état même en cas d’échec consiste à instancier un objet RAII (Resource Acquisition Is Initialization) dont le destructeur rétablit l’état antérieur. Cela peut souvent être réalisé de manière pratique à l’aide de ` qScopeGuard()`, par exemple
const auto restoreDefaultLocale = qScopeGuard([prior = QLocale()]() {
QLocale::setDefault(prior);
});avant le premier appel à ` QLocale::setDefault()` dans un test devant contrôler les paramètres régionaux utilisés par le code testé.
Évitez les dépendances externes
Les fonctions de test doivent également éviter toute dépendance vis-à-vis de ressources externes. Une telle dépendance risque de faire échouer le test si cette ressource est temporairement indisponible. Même lorsqu’elle est disponible, la ressource peut bloquer l’accès du système de test — par exemple, lorsque les tests sont exécutés fréquemment, la ressource peut interpréter le test comme une charge indésirable sur son service.
Ignorer le test lorsqu’il est inaccessible pourrait masquer un problème qui aurait été révélé si le test avait été exécuté, ce qui en fait une mauvaise solution à ces problèmes. Il vaut mieux créer une simulation locale de la ressource, avec suffisamment de ses caractéristiques pour les besoins du test.
Les dépendances externes posent également des problèmes aux développeurs travaillant hors ligne, ainsi que lors des tests dans un environnement isolé, tel qu’une machine virtuelle isolée, lors de l’évaluation de modifications de code provenant de sources non fiables. Voir « Considérations de sécurité relatives à l’Qt Test » pour les préoccupations associées.
Tester l’ensemble de la pile
Si une API est implémentée à l’aide de backends modulaires ou spécifiques à une plateforme qui effectuent le gros du travail, veillez à écrire des tests qui couvrent les chemins d’exécution jusqu’au niveau des backends. Tester les parties de l’API de la couche supérieure à l’aide d’un backend simulé est un bon moyen d’isoler les erreurs de la couche API de celles des backends, mais cela vient en complément des tests qui exécutent l’implémentation réelle avec des données reflétant fidèlement les conditions réelles.
Réalisez les tests rapidement
Les tests ne doivent pas faire perdre de temps en étant inutilement répétitifs, en utilisant des volumes de données de test excessifs ou en introduisant des temps morts inutiles.
Cela vaut particulièrement pour les tests unitaires, où chaque seconde supplémentaire d’exécution allonge la durée des tests d’intégration continue (CI) d’une branche sur plusieurs cibles. N’oubliez pas que les tests unitaires sont distincts des tests de charge et de fiabilité, pour lesquels on s’attend à des volumes de données de test plus importants et à des exécutions plus longues.
Les tests de référence, qui exécutent généralement le même test plusieurs fois, doivent être placés dans un répertoire distinct tests/benchmarks et ne doivent pas être mélangés aux tests unitaires fonctionnels.
Utilisez les tests pilotés par les données
Les tests pilotés par les données facilitent l’ajout de nouveaux tests pour les conditions limites identifiées dans des rapports de bogues ultérieurs.
Le recours à un test piloté par les données, plutôt que de tester plusieurs éléments à la suite dans un même test, évite la répétition de code très similaire et garantit que les cas ultérieurs sont testés même lorsque les précédents échouent. Cela favorise également des tests systématiques et uniformes, car les mêmes tests sont appliqués à chaque échantillon de données.
Lorsqu’un test est piloté par les données, vous pouvez spécifier son étiquette de données (data-tag) ainsi que le nom de la fonction de test, sous la forme function:tag, sur la ligne de commande du test afin d’exécuter le test sur un seul cas de test spécifique, plutôt que sur tous les cas de test de la fonction. Cette méthode peut être utilisée aussi bien pour une balise de données globale que pour une balise locale, identifiant une ligne issue des données propres à la fonction ; vous pouvez même les combiner comme suit : function:global:local.
Utilisez des outils de couverture
Utilisez un outil de couverture tel que Coco ou gcov pour vous aider à écrire des tests couvrant autant d’instructions, de branches et de conditions que possible dans la fonction ou la classe testée. Plus cette étape est effectuée tôt dans le cycle de développement d’une nouvelle fonctionnalité, plus il sera facile de détecter les régressions ultérieurement, lors de la refactorisation du code.
Choisissez les mécanismes appropriés pour exclure les tests
Il est important de choisir le mécanisme approprié pour exclure les tests inapplicables.
Utilisez ` QSKIP()` pour gérer les cas où une fonction de test entière s’avère, lors de l’exécution, inapplicable dans l’environnement de test actuel. Lorsqu’il s’agit uniquement d’ignorer une partie d’une fonction de test, une instruction conditionnelle peut être utilisée, éventuellement accompagnée d’un appel à ` qDebug() ` pour signaler la raison de l’ignorance de la partie inapplicable.
Lorsqu’il existe des échecs de test connus qui devraient être corrigés à terme, l’utilisation de ` QEXPECT_FAIL ` est recommandée, car cette fonction permet d’exécuter le reste du test, lorsque cela est possible. Elle vérifie également que le problème persiste et informe le responsable de la maintenance du code s’il le corrige par inadvertance, un avantage dont on bénéficie même en utilisant le drapeau ` Abort `.
Les fonctions de test ou les lignes de données d’un test piloté par les données peuvent être limitées à des plateformes particulières, ou à des fonctionnalités spécifiques activées à l’aide de ` #if`. Cependant, méfiez-vous des limitations de moc lorsque vous utilisez ` #if ` pour ignorer des fonctions de test. Le préprocesseur ` moc ` n’a pas accès à toutes les macros ` builtin ` du compilateur, souvent utilisées pour la détection des fonctionnalités du compilateur. Par conséquent, moc peut obtenir un résultat différent pour une condition de préprocesseur par rapport à celui observé par le reste de votre code. Cela peut entraîner la génération par moc de métadonnées pour un emplacement de test que le compilateur réel ignore, ou l’omission des métadonnées pour un emplacement de test qui est en réalité compilé dans la classe. Dans le premier cas, le test tentera d’exécuter un slot qui n’est pas implémenté. Dans le second cas, le test ne tentera pas d’exécuter un slot de test alors qu’il le devrait.
Si un programme de test entier n’est pas applicable à une plateforme spécifique ou si une fonctionnalité particulière n’est pas activée, la meilleure approche consiste à utiliser la configuration de compilation du répertoire parent pour éviter de compiler le test. Par exemple, si le test tests/auto/gui/someclass n’est pas valable pour macOS, encapsulez son inclusion en tant que sous-répertoire dans tests/auto/gui/CMakeLists.txt dans une vérification de plateforme :
if(NOT APPLE)
add_subdirectory(someclass)
endifou, si vous utilisez ` qmake`, ajoutez la ligne suivante à ` tests/auto/gui.pro` :
mac*: SUBDIRS -= someclassVoir également Ignorer des tests avec QSKIP.
Évitez Q_ASSERT
La macro Q_ASSERT provoque l’interruption du programme dès que la condition vérifiée est false, mais uniquement si le logiciel a été compilé en mode débogage. Dans les versions de production et de débogage-et-production, Q_ASSERT n’a aucun effet.
Q_ASSERT Il convient d’éviter cette macro car elle modifie le comportement des tests selon qu’une version de débogage est testée ou non, et parce qu’elle provoque l’interruption immédiate d’un test, sautant ainsi toutes les fonctions de test restantes et renvoyant des résultats de test incomplets ou erronés.
Elle ignore également toute opération de nettoyage ou de libération de ressources censée avoir lieu à la fin du test, ce qui peut laisser l’espace de travail dans un état désordonné et entraîner des complications pour les tests suivants.
Au lieu de ` Q_ASSERT`, il convient d’utiliser les variantes de macro ` QCOMPARE()` ou ` QVERIFY()`. Elles provoquent l’échec du test en cours et sa fin, mais permettent l’exécution des fonctions de test restantes et la fin normale de l’ensemble du programme de test. ` QVERIFY2()` permet même d’enregistrer un message d’erreur descriptif dans le journal de test.
Rédaction de tests fiables
Les sections suivantes fournissent des recommandations pour l’écriture de tests fiables :
- Éviter les effets secondaires dans les étapes de vérification
- Évitez les délais d’expiration fixes
- Méfiez-vous des comportements dépendants du temps
- Éviter la capture et la comparaison de bitmaps
Éviter les effets secondaires dans les étapes de vérification
Lors de l'exécution d'étapes de vérification dans un autotest à l'aide de ` QCOMPARE()`, ` QVERIFY()`, etc., il convient d'éviter les effets secondaires. Les effets secondaires dans les étapes de vérification peuvent rendre un test difficile à comprendre. De plus, ils peuvent facilement faire échouer un test de manière difficile à diagnostiquer lorsque celui-ci est modifié pour utiliser QTRY_VERIFY(), QTRY_COMPARE() ou QBENCHMARK. Ces fonctions peuvent exécuter l’expression transmise à plusieurs reprises, reproduisant ainsi les effets secondaires.
Lorsque les effets secondaires sont inévitables, veillez à ce que l’état antérieur soit restauré à la fin de la fonction de test, même si le test échoue. Cela nécessite généralement l’utilisation d’une classe RAII (voir « Écrire des fonctions de test autonomes » ci-dessus) ou d’une méthode cleanup(). Ne vous contentez pas de placer le code de restauration à la fin du test. Si une partie du test échoue, ce code sera ignoré et l’état antérieur ne sera pas rétabli.
Évitez les délais d’expiration fixes
Évitez d’utiliser des délais d’attente codés en dur, tels que ` QTest::qWait()` pour attendre que certaines conditions soient remplies. Envisagez d’utiliser la classe ` QSignalSpy `, les macros ` QTRY_VERIFY()` ou ` QTRY_COMPARE()`, ou encore la classe ` QSignalSpy ` en association avec les variantes de la macro ` QTRY_ `.
La fonction qWait() permet de définir un délai fixe entre l’exécution d’une action et l’attente de l’achèvement d’un comportement asynchrone déclenché par cette action. Par exemple, modifier l’état d’un widget puis attendre que celui-ci soit rafraîchi. Cependant, ces délais d’expiration entraînent souvent des échecs lorsqu’un test écrit sur un poste de travail est exécuté sur un appareil, où le comportement attendu peut prendre plus de temps à se produire. Augmenter le délai d’expiration fixe à une valeur plusieurs fois supérieure à celle nécessaire sur la plateforme de test la plus lente n’est pas une bonne solution, car cela ralentit l’exécution du test sur toutes les plateformes, en particulier pour les tests pilotés par des tableaux.
Si le code testé émet des signaux Qt à la fin du comportement asynchrone, une meilleure approche consiste à utiliser la classe ` QSignalSpy ` pour signaler à la fonction de test que l’étape de vérification peut désormais être effectuée.
En l’absence de signaux Qt, utilisez les macros ` QTRY_COMPARE() ` et ` QTRY_VERIFY() `, qui vérifient périodiquement une condition spécifiée jusqu’à ce qu’elle devienne vraie ou qu’un délai d’attente maximal soit atteint. Ces macros empêchent le test de durer plus longtemps que nécessaire, tout en évitant les dysfonctionnements lorsque les tests sont développés sur des systèmes plus rapides puis exécutés sur des systèmes plus lents.
S’il n’y a pas de signaux Qt et que vous écrivez le test dans le cadre du développement d’une nouvelle API, demandez-vous si l’API pourrait bénéficier de l’ajout d’un signal signalant l’achèvement du comportement asynchrone. Si cela facilite vos tests, cela pourrait également s’avérer utile pour les utilisateurs de votre API.
Méfiez-vous des comportements dépendants du temps
Certaines stratégies de test sont sensibles aux comportements dépendants du timing de certaines classes, ce qui peut conduire à des tests qui échouent uniquement sur certaines plateformes ou qui ne renvoient pas de résultats cohérents.
Les widgets de saisie de texte en sont un exemple : ils comportent souvent un curseur clignotant qui peut faire que les comparaisons de bitmaps capturés aboutissent ou échouent selon l’état du curseur au moment de la capture du bitmap. Cela peut, à son tour, dépendre de la vitesse de la machine exécutant le test.
Lors du test de classes dont l’état change en fonction d’événements de temporisation, il convient de prendre en compte ce comportement lors de la réalisation des étapes de vérification. En raison de la diversité des comportements dépendants du timing, il n’existe pas de solution générique unique à ce problème de test.
Pour les widgets de saisie de texte, les solutions possibles consistent notamment à désactiver le clignotement du curseur (si l'API offre cette fonctionnalité), à attendre que le curseur se trouve dans un état connu avant de capturer une image bitmap (par exemple, en s’abonnant à un signal approprié si l’API en propose un), ou encore en excluant la zone contenant le curseur de la comparaison des images bitmap.
Éviter la capture et la comparaison d’images bitmap
Bien qu’il soit parfois nécessaire de vérifier les résultats de test en capturant et en comparant des images bitmap, cette méthode peut s’avérer assez fragile et demander beaucoup de travail.
Par exemple, un widget particulier peut présenter un aspect différent selon les plateformes ou les styles de widget ; il peut donc s’avérer nécessaire de créer plusieurs fois des images bitmap de référence, puis de les maintenir à l’avenir à mesure que l’ensemble des plateformes prises en charge par Qt évolue. Apporter des modifications affectant le bitmap implique donc de devoir recréer les bitmaps attendus sur chaque plateforme prise en charge, ce qui nécessiterait un accès à chacune d’entre elles.
Les comparaisons d’images bitmap peuvent également être influencées par des facteurs tels que la résolution d’écran de la machine de test, la profondeur de bits, le thème actif, la palette de couleurs, le style de widget, les paramètres régionaux actifs (symboles monétaires, sens d’écriture, etc.), la taille de police, les effets de transparence et le choix du gestionnaire de fenêtres.
Dans la mesure du possible, utilisez des moyens programmatiques, tels que la vérification des propriétés des objets et des variables, plutôt que de capturer et de comparer des images bitmap.
Amélioration des résultats de test
Les sections suivantes fournissent des recommandations pour produire des résultats de test lisibles et utiles :
- Tester les avertissements
- Évitez d’afficher les messages de débogage issus des autotests
- Écrivez un code de diagnostic bien structuré
Vérification des avertissements
Tout comme lors de la compilation de votre logiciel, si les résultats des tests sont encombrés d’avertissements, vous aurez plus de mal à repérer un avertissement qui serait en réalité un indice de l’apparition d’un bogue. Il est donc prudent de vérifier régulièrement vos journaux de test à la recherche d’avertissements et d’autres sorties superflues, et d’en rechercher les causes. Lorsqu’il s’agit de signes d’un bogue, vous pouvez configurer les avertissements pour qu’ils déclenchent l’échec du test.
Lorsque le code testé est censé générer des messages, tels que des avertissements concernant une utilisation incorrecte, il est également important de vérifier qu’il les génère bien dans ce cas de figure. Vous pouvez vérifier la présence des messages attendus provenant du code testé, générés par qWarning(), qDebug(), qInfo() et leurs équivalents, à l’aide de QTest::ignoreMessage(). Cela permettra de vérifier que le message est bien généré et de le filtrer hors de la sortie de l’exécution du test. Si le message n’est pas généré, le test échouera.
Si un message attendu n’est généré que lorsque Qt est compilé en mode débogage, utilisez QLibraryInfo::isDebugBuild() pour déterminer si les bibliothèques Qt ont été compilées en mode débogage. L’utilisation de #ifdef QT_DEBUG ne suffit pas, car elle vous indiquera uniquement si le test a été compilé en mode débogage, ce qui ne garantit pas que les bibliothèques Qt l’aient également été.
Vos tests peuvent vérifier qu’ils ne déclenchent pas d’appels à qWarning() en appelant QTest::failOnWarning(). Sans paramètre (depuis Qt 6.8), cela fait échouer le test si un avertissement est généré, et affiche l’avertissement. Vous pouvez éventuellement passer à ` failOnWarning() ` soit un message d’avertissement à tester, soit une expression régulière (` QRegularExpression `) à faire correspondre, afin de limiter ce comportement aux avertissements correspondants. (Ces versions filtrées ont été introduites dans Qt 6.3.)
Vous pouvez également définir la variable d’environnement QT_FATAL_WARNINGS pour que les avertissements soient traités comme des erreurs fatales. Voir qWarning() pour plus de détails ; cela ne concerne pas spécifiquement les autotests. Si les avertissements risquent de se perdre dans de vastes journaux de test, une exécution occasionnelle avec cette variable d’environnement définie peut vous aider à les repérer et à éliminer ceux qui surviennent.
Évitez d’afficher des messages de débogage provenant des autotests
Les autotests qui réussissent ne doivent produire aucun avertissement ni message de débogage non géré. Cela permettra à CI Gate de traiter les nouveaux avertissements ou messages de débogage comme des échecs de test. À l’instar des avertissements émis par les compilateurs lors de la compilation de votre code, si les avertissements sont rares, ils constituent des indices utiles sur d’éventuels problèmes, mais s’ils sont systématiques, ils peuvent masquer des problèmes importants aux développeurs qui testent leurs modifications.
L’ajout de messages de débogage pendant le développement est acceptable, mais ceux-ci doivent être soit désactivés, soit supprimés avant la validation d’un test.
Écrivez un code de diagnostic bien structuré
Toute sortie de diagnostic susceptible d’être utile en cas d’échec d’un test doit faire partie de la sortie normale du test, plutôt que d’être mise en commentaire, désactivée par des directives de préprocesseur ou activée uniquement dans les versions de débogage. Si un test échoue lors de l’intégration continue, le fait de disposer de toutes les sorties de diagnostic pertinentes dans les journaux d’intégration continue peut vous faire gagner beaucoup de temps par rapport à l’activation du code de diagnostic et à la réexécution du test. Surtout si l’échec s’est produit sur une plateforme dont vous ne disposez pas sur votre ordinateur.
Les messages de diagnostic dans les tests doivent utiliser les mécanismes de sortie de Qt, tels que qDebug() et qWarning(), plutôt que les mécanismes de sortie stdio.h ou iostream.h. Ces derniers contournent la gestion des messages de Qt et empêchent l’option de ligne de commande ` -silent ` de masquer les messages de diagnostic. Cela pourrait entraîner la dissimulation de messages d’échec importants au sein d’un volume important de sortie de débogage.
Écrire du code testable
Les sections suivantes fournissent des recommandations pour écrire du code facile à tester :
Séparer les dépendances
Le principe des tests unitaires consiste à utiliser chaque classe de manière isolée. Étant donné que de nombreuses classes instancient d’autres classes, il n’est pas possible d’instancier une classe séparément. Vous devez donc utiliser une technique appelée « injection de dépendances », qui sépare la création des objets de leur utilisation. Une fabrique est chargée de construire des arborescences d’objets. D’autres objets manipulent ces objets via des interfaces abstraites.
Cette technique fonctionne bien pour les applications orientées données. Pour les applications avec interface graphique, cette approche peut s’avérer difficile car les objets sont fréquemment créés et détruits. Pour vérifier le comportement correct des classes qui dépendent d’interfaces abstraites, on peut recourir à la simulation (mocking). Voir par exemple le framework Googletest Mocking (gMock).
Compiler toutes les classes dans des bibliothèques
Dans les projets de petite à moyenne envergure, un script de compilation répertorie généralement tous les fichiers source, puis compile l’exécutable en une seule fois. Cela signifie que les scripts de compilation destinés aux tests doivent à nouveau répertorier les fichiers source nécessaires.
Il est plus simple de répertorier les fichiers source et les en-têtes une seule fois dans un script permettant de construire une bibliothèque statique. La fonction « main() » sera alors liée à la bibliothèque statique pour construire l’exécutable, et les tests seront liés aux bibliothèques statiques.
Pour les projets où les mêmes fichiers source sont utilisés pour construire plusieurs programmes, il peut être plus approprié de regrouper les classes partagées dans une bibliothèque liée dynamiquement (ou objet partagé) que chaque programme, y compris les programmes de test, peut charger au moment de l’exécution. Là encore, le fait de disposer du code compilé dans une bibliothèque permet d’éviter les redondances dans la description des composants à combiner pour créer les différents programmes.
Configuration des machines de test
Les sections suivantes traitent des problèmes courants liés à la configuration des machines de test :
- Économiseurs d'écran
- Boîtes de dialogue système
- Utilisation de l'affichage
- Gestionnaires de fenêtres
Tous ces problèmes peuvent généralement être résolus grâce à une utilisation judicieuse de la virtualisation.
Économiseurs d'écran
Les économiseurs d’écran peuvent interférer avec certains tests des classes d’interface graphique, ce qui rend les résultats de test peu fiables. Il est recommandé de les désactiver afin de garantir la cohérence et la fiabilité des résultats de test.
Boîtes de dialogue système
Les boîtes de dialogue affichées de manière inattendue par le système d’exploitation ou d’autres applications en cours d’exécution peuvent détourner le focus des widgets impliqués dans un test automatisé, provoquant ainsi des échecs non reproductibles.
Parmi les problèmes courants, on peut citer les boîtes de dialogue de notification de mise à jour en ligne sous macOS, les fausses alertes des antivirus, les tâches planifiées telles que les mises à jour des signatures antivirus, les mises à jour logicielles déployées sur les postes de travail, ainsi que les fenêtres contextuelles des programmes de messagerie instantanée qui s’affichent par-dessus la pile.
Utilisation de l’écran
Certains tests utilisent l’écran, la souris et le clavier de la machine de test ; ils peuvent donc échouer si celle-ci est utilisée à d’autres fins simultanément ou si plusieurs tests sont exécutés en parallèle.
Le système d’intégration continue (CI) utilise des machines de test dédiées pour éviter ce problème, mais si vous ne disposez pas d’une machine de test dédiée, vous pouvez éventuellement résoudre ce problème en exécutant les tests sur un deuxième écran.
Sous Unix, il est également possible d’exécuter les tests sur un serveur X imbriqué ou virtuel, tel que Xephyr. Par exemple, pour exécuter l’ensemble des tests sur Xephyr, exécutez les commandes suivantes :
Xephyr :1 -ac -screen 1920x1200 >/dev/null 2>&1 &
sleep 5
DISPLAY=:1 icewm >/dev/null 2>&1 &
cd tests/auto
make
DISPLAY=:1 make -k -j1 checkLes utilisateurs de pilotes binaires NVIDIA doivent noter que Xephyr pourrait ne pas être en mesure de fournir les extensions GLX. Forcer l’utilisation de la bibliothèque libGL de Mesa peut aider :
export LD_PRELOAD=/usr/lib/mesa-diverted/x86_64-linux-gnu/libGL.so.1Cependant, lorsque les tests sont exécutés sur Xephyr et sur le serveur X réel avec des versions différentes de libGL, le cache disque QML peut provoquer le plantage des tests. Pour éviter cela, utilisez ` QML_DISABLE_DISK_CACHE=1`.
Vous pouvez également utiliser le plugin « offscreen » :
TESTARGS="-platform offscreen" make check -k -j1Gestionnaires de fenêtres
Sous Unix, au moins deux autotests (tst_examples et tst_gestures) nécessitent qu’un gestionnaire de fenêtres soit en cours d’exécution. Par conséquent, si vous exécutez ces tests sous un serveur X imbriqué, vous devez également exécuter un gestionnaire de fenêtres dans ce serveur X.
Votre gestionnaire de fenêtres doit être configuré pour positionner automatiquement toutes les fenêtres à l’écran. Certains gestionnaires de fenêtres, tels que Tab Window Manager (twm), disposent d’un mode permettant de positionner manuellement les nouvelles fenêtres, ce qui empêche la suite de tests de s’exécuter sans intervention de l’utilisateur.
Remarque : Tab Window Manager n’est pas adapté à l’exécution de la suite complète d’autotests Qt, car l’autotest « tst_gestures » lui fait oublier sa configuration et le fait revenir au placement manuel des fenêtres.
© 2026 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.