Introduction au module QtTest
Date de publication : 16/09/2010
Par
Rémi Achard
Le module
QtTest
permet de réaliser simplement ses tests unitaires et benchmark avec le
framework Qt. Ce tutoriel s'inspire fortement de la série de
mini tutoriels présents dans la documentation Qt.
N'hésitez pas à commenter cet article !
Commentez
I. Test unitaire : le code minimal
II. Exécuter un test sur un ensemble de données
III. Benchmark
IV. Conclusion
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.
testqstring.cpp |
# 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.
testqstring.cpp |
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.
testqstring.cpp |
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).
testqstring.cpp |
QTEST_MAIN(TestQString)
# include " testqstring.moc "
|
Pour la compilation avec qmake, il suffit de rajouter
dans le .pro de votre projet :
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 :
testqstring.cpp |
# 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 :
testqstring.cpp |
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.
testqstring.cpp |
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.
testqstring.cpp |
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).
testqstring.cpp |
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 de d'utiliser la macro
QBENCHMARK dans un test.
testbenchmark.cpp |
# 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 *********
|
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.