Regrouper applications, librairies et tests dans un seul projet Qt.

Voici un article qui présente une manière d’organiser son projet Qt de façon à avoir l’application, les librairies et les tests dans un seul et même projet. Il existe d’autres façons de faire, mais dans la jungle des possibilités (et des fichiers .pro), avoir un patron qui fonctionne, ça peut aider.
L’organisation du projet reprend les concepts décrits dans l’article sur la gestion des dépendances et la compilation répartie : SUBDIRS - handling dependencies. Le projet donné en exemple est donc découpé en une application app, une librairie lib et des tests tst_class1 et tst_class2, comme ceci :
 
 /projet
 |--- projet.pro
 |--- app.pri
 `--- /src
      |--- /app
      |    |--- app.pro
      |    `--- ... 
      |--- /lib
      |    |--- lib.pro
      |    `--- ... 
      `--- /tests
           |--- /tst_class1
           |    |--- tst_class1.pro
           |    `--- ... 
           `--- /tst_class2
                |--- tst_class2.pro
                `--- ... 

L’application et les tests dépendent de la libraire. La présence d’une ou plusieurs librairies est très importante car cela permet d’isoler les composants et de les tester efficacement. Pour plus d’informations sur ce sujet, je vous conseille de lire “QtTest Unit Testing Framework” de Justin Noel de la société ICS.

Le projet

Le projet global contient un fichier project.pro très simple qui décrit simplement les sous-répertoires (subdirs) et les dépendances.
fichier project.pro
TEMPLATE = subdirs
 
SUBDIRS = \
    lib \
    app \
    tests

# folders where to find the sub projects
lib.subdir = src/lib
app.subdir  = src/app
tests.subdir = src/tests
 
# dependencies
app.depends = lib
tests.depends = lib

Les deux dernières lignes décrivent les dépendances de l’application et des tests vis à vis de la librairie. Celle-ci sera donc compilée en premier. Ensuite l’application et les tests pourront être compilés en parallèle.

La librairie

La librairie contient l’ensemble des classes utiles à l’application. Pour créer celle-ci, on peut s’aider du wizard de création de Qt Creator.
Rien de particulier concernant le fichier .pro de la librairie. La seule chose importante, c’est la définition de la variable DESTDIR qui va copier le résultat de la compilation (la librairie, la DLL, les fichiers de symboles pdb) dans un répertoire “lib” situé juste en dessous de la racine du projet. Les autres modules pourront ainsi facilement y accéder pour linker correctement leur exécutable.
fichier lib.pro
QT -= gui

TARGET = ProjectLib
CONFIG (release, debug|release) {
    TARGET = ProjectLib
    DESTDIR = $$PWD/../../lib/debug
}
CONFIG (debug, debug|release) {
    TARGET = ProjectLibd
    DESTDIR = $$PWD/../../lib/release
}

TEMPLATE = lib
CONFIG += dll

DEFINES += PROJECTLIB_LIBRARY

SOURCES += \
    class1.cpp \
    class2.cpp

HEADERS += \
    projectlib_global.h \
    class1.h \
    class2.h

La distinction debug/release n’est pas obligatoire, c’est une habitude de compilation sur plateforme Windows.

Le fichier commun "app.pri"

Comme nous avons plusieurs applications, l’application principale et les tests unitaires, nous allons écrire dans un seul fichier la configuration commune à toutes. Cela évite de dupliquer celle-ci dans chacun des fichiers .pro
fichier app.pri
PROJECTLIB = $$PWD/lib

CONFIG(release, debug|release) {
    LIBS += -L$$PROJECTLIB/release -lProjectLib
}

CONFIG(debug, debug|release){
    LIBS += -L$$PROJECTLIB/debug -lProjectLibd
}

INCLUDEPATH += $$PWD/src/lib

Pour que la librairie soit visible et accessible au niveau du linkage de l’application et des tests, on place celle ci dans le répertoire $$PWD/lib, qui se situe en dessous de la racine du projet. Au niveau de la librairie, cela est rendu possible grâce la variable DESTDIR (voir chapitre précédent sur la librairie).

 /projet
  |--- projet.pro
  |--- app.pri
  |--- /lib
  |    |--- /debug
  |    `--- /release
  `--- /src

La définition de la variable LIBS sera différente pour les autres cibles comme Linux ou MacOS. L’exemple s’adresse à un projet construit sur une plateforme Windows.
La définition de la variable INCLUDEPATH permet au compilateur d’aller trouver les fichiers entêtes de la librairie. Encore une fois, on va au plus simple dans cet exemple, il existe d’autres façons de faire, et chaque plateforme a ses spécificités.

L’application

C’est elle qui contient la fonction main principale et qui va construire l’application à partir des classes fournies par la libraire. Dans ce schéma, les tests unitaires se font sur les classes de la librairie et les tests fonctionnels seront réalisés sur l’application.
Le fichier .pro est relativement simple, on peut utiliser le wizard de Qt Creator pour créer une nouvelle application. L’instruction importante à retenir, c’est include(../../app.pri) qui va chercher la configuration commune pour linker correctement avec la librairie.
fichier app.pro
include(../../app.pri)


QT += core

TEMPLATE = app
TARGET = Project

SOURCES += main.cpp

Les Tests

Dans notre exemple, il y a deux tests, et chacun se comporte comme une application indépendant grâce à la macro QTEST_MAIN(TestClass) qui crée une méthode main()autonome et qui va exécuter les tests définis dans la classe passé en paramètre - voir Qt Documentation : Chapter 1: Writing a Unit Test sur l’écriture des tests unitaires.
On notera que la macro QTEST_MAIN() instancie un objet application de type QCoreApplication. Si ce n’est pas nécessaire, il est possible de faire appel à la macro QTEST_APPLESS_MAIN().

Le fichier .pro est aussi relativement simple, on peut utiliser le wizard de Qt Creator pour créer un nouveau test. Comme pour l’application, l’instruction importante à ne pas oublier, c’est include(../../../app.pri) qui va chercher la configuration commune pour linker correctement avec la librairie.
fichier tst_class1.pro
include (../../../app.pri)

QT += testlib
QT -= gui

CONFIG += qt console warn_on depend_includepath testcase
CONFIG -= app_bundle

TEMPLATE = app

SOURCES +=  tst_class1.cpp

Utiliser Qt Creator pour compiler et exécuter l’application et les tests

Depuis Qt Creator, ouvrir le fichier racine project.pro. Dans une arborescence unique du projet, on va pouvoir accéder aussi bien aux fichiers de l’application, des librairies et des tests.

 
  • Lancer qmake pour générer les Makefile.
  • Lancer la compilation globale du projet (Build All). Dans l’ordre c’est la librairie qui sera compilée en premier, l’application et les tests seront compilés par la suite (séquentiellement ou en parallèle).
  • Exécuter l’application ou les tests (en choisissant le type de run).

Conclusion

Voici les grands principes pour tout avoir dans un seul et même projet Qt. Il existe surement d’autres méthodes, mais l’avantage de ce système c’est cette intégration dynamique de l’application et des tests unitaires autour d’une ou plusieurs librairies. Toute modification de la librairie entraînera une recompilation automatique de l’application et des tests, ce qui garantira l’intégrité du projet.

Pour ce qui est de l’exécution des tests, il y a un nouveau système qui est apparu avec la version 4.3.0 de Qt Creator. Ce nouvel outil permet d’exécuter individuellement ou globalement tous les tests et fera l’objet d’un autre article.



Fin de l'article.