I. Test unitaire : le code minimal▲
Nous allons commencer par écrire un test simple permettant de vérifier que la fonction QString::toUpper copie correctement la chaine de caractères en convertissant en majuscule.
#include
<QtTest/QtTest>
class
TestQString: public
QObject
{
Q_OBJECT
private
slots
:
void
toUpper();
}
;
void
TestQString::
toUpper()
{
QString
str =
"Hello"
;
QCOMPARE
(str.toUpper(), QString
("HELLO"
));
}
QTEST_MAIN
(TestQString)
#include
"testqstring.moc"
La classe de test doit hériter de QObject. Les tests doivent impérativement être déclarés en tant que slots privés pour que QtTest puisse automatiquement les reconnaître et les exécuter.
La macro QCOMPARE vérifie que les deux objets passés en argument sont bien identiques et, dans le cas contraire, affiche la valeur de ces deux objets, ce qui facilite le débogage.
void
TestQString::
toUpper()
{
QString
str =
"Hello"
;
QCOMPARE
(str.toUpper(), QString
("HELLO"
));
}
A noter que cette macro est assez stricte : les objets comparés doivent être de même type. Par exemple, la comparaison entre un objet QString et une chaîne de caractères constante provoquera une erreur à la compilation.
// Erreur à la compilation
void
TestQString::
toUpper()
{
QString
str =
"Hello"
;
QCOMPARE
(str.toUpper(), "HELLO"
);
}
La macro QTEST_MAIN génère une fonction main exécutant tous les tests qui ont été déclarés. De plus, dans le cas présent, comme nous avons déclaré et implémenté les tests dans un fichier source (.cpp), il faut inclure le fichier généré par Qt en interne (.moc).
QTEST_MAIN
(TestQString)
#include
"testqstring.moc"
Pour la compilation avec qmake, il suffit de rajouter dans le .pro de votre projet :
QT
+=
testlib
Il est possible de modifier le comportement d'exécution par défaut des tests en passant des arguments à l'exécutable généré. Par exemple (voir la documentation pour une liste exhaustive) :
- -silent : affichage minimal, signale seulement les erreurs ;
- -xml : génère les résultats en xml ;
- -functions : liste toutes les fonctions présentes dans le test.
Voici la sortie générée par l'exemple :
********* Start testing of TestQString *********
Config: Using QTest library 4.7.0, Qt 4.7.0
PASS : TestQString::initTestCase()
PASS : TestQString::toUpper()
PASS : TestQString::cleanupTestCase()
Totals: 3 passed, 0 failed, 0 skipped
********* Finished testing of TestQString *********
La quatrième ligne correspond au test de toUpper et indique que celui-ci s'est correctement déroulé :
PASS : TestQString::toUpper()
Comme on peut le remarquer, deux tests supplémentaires ont été éxecutés :
PASS : TestQString::initTestCase()
(...)
PASS : TestQString::cleanupTestCase()
En effet, QtTest génère automatiquement ces deux tests qui permettent aux développeurs d'initialiser et de libérer les ressources utilisées dans les différents tests unitaires :
- initTestCase() : est appelé avant l'exécution du premier test ;
- cleanupTestCase() : est appelé après l'exécution du dernier test.
II. Exécuter un test sur un ensemble de données▲
QtTest permet de définir un ensemble de données (scénarios) pour un test, puis de réaliser le test sur chaque scénario défini.
Voici le résultat appliqué à l'exemple précédent :
#include
<QtTest/QtTest>
class
TestQString: public
QObject
{
Q_OBJECT
private
slots
:
void
toUpper_data();
void
toUpper();
}
;
void
TestQString::
toUpper_data()
{
QTest
::
addColumn<
QString
>
("string"
);
QTest
::
addColumn<
QString
>
("result"
);
QTest
::
newRow("tout en minuscules"
) <<
"hello"
<<
"HELLO"
;
QTest
::
newRow("minuscules et majuscules"
) <<
"Hello"
<<
"HELLO"
;
QTest
::
newRow("tout en majuscule"
) <<
"HELLO"
<<
"HELLO"
;
}
void
TestQString::
toUpper()
{
QFETCH
(QString
, string);
QFETCH
(QString
, result);
QCOMPARE
(string.toUpper(), result);
}
QTEST_MAIN
(TestQString)
#include
"testqstring.moc"
Dans un premier temps, il faut déclarer une fonction reprenant le nom du test suivi du suffixe _data :
void
TestQString::
toUpper_data()
QtTest fournit un mécanisme simple pour stocker les données des scénarios sous forme de tableau : on déclare d'abord la structure du tableau, ici deux colonnes, une contenant la chaîne à tester et l'autre le résultat attendu.
QTest
::
addColumn<
QString
>
("string"
);
QTest
::
addColumn<
QString
>
("result"
);
Les noms de ces deux colonnes serviront à la macro QFETCH pour récupérer les données associées.
Il ne reste plus qu'à remplir le tableau, via l'opérateur <<. La fonction newRow prend le nom du scénario en argument, qui sera affiché en cas d'échec du test.
QTest
::
newRow("tout en minuscules"
) <<
"hello"
<<
"HELLO"
;
QTest
::
newRow("minuscules et majuscules"
) <<
"Hello"
<<
"HELLO"
;
QTest
::
newRow("tout en majuscule"
) <<
"HELLO"
<<
"HELLO"
;
Pour terminer, la fonction de test a besoin d'être modifiée pour récupérer les données automatiquement à l'aide de la macro QFETCH (type attendu, nom de la colonne).
QFETCH
(QString
, string);
QFETCH
(QString
, result);
La fonction TestQString::toUpper() sera donc appelée trois fois, puisque le tableau contient trois lignes (scénarios).
III. Benchmark▲
Pour créer un benchmark, il suffit d'utiliser la macro QBENCHMARK dans un test.
#include
<QtTest/QtTest>
class
TestBenchmark: public
QObject
{
Q_OBJECT
private
slots
:
void
simple();
}
;
void
TestBenchmark::
simple()
{
QString
str =
" Hello "
;
QBENCHMARK
{
str.trimmed();
}
}
QTEST_MAIN
(TestQString)
#include
"testbenchmark.moc"
Le code contenu dans la macro sera exécuté plusieurs fois si nécessaire, afin d'obtenir un temps moyen d'exécution le plus précis possible. Ici, nous aurons donc le temps moyen mis par la fonction trimmed() pour s'exécuter (la fonction trimmed() permet de retirer les espaces présents en début et fin de chaîne).
Voici la sortie générée par l'exemple :
********* Start testing of TestBenchmark *********
Config: Using QTest library 4.7.0, Qt 4.7.0
PASS : TestBenchmark::initTestCase()
RESULT : TestBenchmark::simple():
0.000190 msecs per iteration (total: 100, iterations: 524288)
PASS : TestBenchmark::simple()
PASS : TestBenchmark::cleanupTestCase()
Totals: 3 passed, 0 failed, 0 skipped
********* Finished testing of TestBenchmark *********
Il possible de choisir la méthode de comptage utilisée par QtTest pour estimer le temps moyen d'exécution, ceci par le biais d'arguments de la ligne de commande.
Nom | Argument de la ligne de commande | Plateforme disponible |
---|---|---|
Temps d'exécution | (défaut) | Toutes les plateformes |
Compteur de ticks CPU | -tickcounter | Windows, Mac OS X, Linux, beaucoup de systèmes UNIX-like |
Valgrind/Callgrind | -callgrind | Linux (si installé) |
Compteur d'événements | -eventcounter | Toutes les plateformes |
IV. Conclusion▲
Merci à littledaem pour sa relecture et ses conseils.
Pour aller plus loin, le module QtTest permet également de tester les applications graphiques, en simulant par exemple un clic de souris, ou de tester les signaux émis par les objets de Qt.