Liebe Leserinnen, liebe Leser,

in einem vorigen Beitrag habe ich diskutiert, wie ein Projekt, das zwei von einander getrennte (von qmake und cmake verwaltete) Unterprojekte enthält, in Qt Creator mit Unit-Tests einfach eingerichtet werden kann. Diese Projekte griffen auf die gleichen Dateien zu, um die Tests und das Programm von einander unhabhängig ausführen zu können. Obwohl dieser Ansatz ganz gut funktionierte, wollte ich eine bessere Integration der Tests in der Entwicklungsumgebung haben (Testfilterung, weniger Stördaten auf Bildschirm, eine bessere allgemeine Übersicht, usw.). Diese bessere Integration ist eigentlich unterstüzt, aber nicht wie ich die Buildwerkzeuge benutzt habe. Weiterhin ist das eine gute Idee, das Programm zu relativ kleinen Programmdateien und gemeinsamen Bibliotheken zu verteilen, aber das vorige Setup hatte diese Struktur auch nicht.

Um diese Probleme zu beheben, und um auf die integrierte Testmerkmale zuzugreifen, brauchte das ganze Projekt zu bloßem qmake migriert werden, die in Qt-Terminologie das Subdirs-Projekt bedeutet. Hier gibt’s die gewünschte Ordnerstruktur:

MyCoolProject
 |--MyCoolProj.pro
 |--MyCoolApp
    |--MyCoolApp.pro
    |--main.cpp
    |--mainwindow.h
    |--mainwindow.cpp
    |--awesomestuff.h
    \--awesomestuff.cpp
 |--MyCoolLibs
    |--MyCoolLibs.pro
    |--MyLib1
       |--MyLib1.pro
       |--mylib1decl.h
       |--mylib1.h
       \--mylib1.cpp
    \--MyLib2
       |--MyLib2.pro
       |--mylib2decl.h
       |--mylib2.h
       \--mylib2.cpp
  |--GoogleTest
     |--googletest
        |--ci
        |--googlemock
        |--googletest
        |-- ...
        \--WORKSPACE
     |--GoogleTest.pri
     \--GoogleTest.pro
  \--Tests
     |--Tests.pro
     |--main.cpp
     |--tst_mylib1.cpp
     \--tst_mylib2.cpp
     

Eine globale, oberste Projektdatei, die die Subdirds-Vorlage benutzt, ist nötig:

qmake (MyCoolProj.pro)

1
2
3
4
5
6
7
8
TEMPLATE = subdirs

SUBDIRS += MyCoolApp \
           MyCoolLibs \
           GoogleTest \
           Tests
           
           

Das ist offensichtlich, dass es 4 Teilprojekte gibt, aber warum ist GoogleTest dort? Manche erfahrenere Menschen sagen, dass es hochempfehlenswert ist, den GoogleTest-Quellencode als teil des Projektes zu haben, weil es ganz sensibel ist, wenn es nicht mit den gleichen Werkzeuge und Einstellungen wie das zu testende Hauptprojekt gebaut wird. Hier sind die Einstellungen für MyCoolProg:

qmake (MyCoolApp.pro)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
QT += core gui
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets

TARGET = MyCoolApp
TEMPLATE = app

DEFINES += QT_DEPRECATED_WARNINGS

CONFIG += c++14

DEPENDPATH += ../MyCoolLibs/MyLib1 \
              ../MyCoolLibs/MyLib2

INCLUDEPATH += ../MyCoolLibs/MyLib1 \
               ../MyCoolLibs/MyLib2

LIBS += -L../MyCoolLibs/MyLib1 -lMyLib1 \
        -L../MyCoolLibs/MyLib2 -lMyLib2

HEADERS += mainwindow.h \
           awesomestuff.h

SOURCES += main.cpp \
           mainwindow.cpp \
           awesomestuff.cpp
           
FORMS +=   mainwindow.ui

Hier gibt’s nichts Besonderes, das ist nur eine allgemeine Programmvorlage mit den gewöhnlichen Inhalten. Die interessante Sache ist die Bibliothek und die Test-Einstellungen. Weil es meherere Bibliotheken gibt, ist es eine gute Idee, sie getrennt zu kompilieren. Das ist wieder durch die Subdirds-Vorlage möglich:

qmake (MyCoolLibs.pro)

1
2
3
4
TEMPLATE = subdirs

SUBDIRS += MyLib1 \
           MyLib2

qmake (MyLib1.pro)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
QT += core

TARGET = MyLib1
TEMPLATE = lib

CONFIG += c++14
CONFIG -= debug_and_release

DEFINES += QT_DEPRECATED_WARNINGS
DEFINES += MYLIB1_LIBRARY

HEADERS += \
    mylib1decl.h \
    mylib1.h \

SOURCES += \
    mylib1.cpp \
    

Die Bibliothekdeklaration ist normal, mal wieder ist es nichts Besonderes. Es ist auch wichtig in Erinnerung zu bringen, dass Q_DECL_EXPORT und Q_DECL_IMPORT in einer *decl.h Datei definiert werden soll. Man kann sich über die Einzelheiten der Erstellung einer Bibliothek in der offiziellen Dokumentation informieren. In diesem Fall sieht diese Deklaration so aus:

C++

1
2
3
4
5
6
7
8
9
10
11
12
#ifndef MYLIB1DECL_H
#define MYLIB1DECL_H

#include <QtCore/qglobal.h>

#if defined(MYLIB1_LIBRARY)
#  define MYLIB1_EXPORT Q_DECL_EXPORT
#else
#  define MYLIB1_EXPORT Q_DECL_IMPORT
#endif

#endif // MYLIB1DECL_H

GoogleTest wird auch als eine Bibliothek kompiliert, und hier ist dafür die .pro Datei.

qmake (GoogleTest.pro)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
TEMPLATE = lib
CONFIG += c++14
CONFIG += static exceptions
CONFIG -= debug_and_release

TARGET = GoogleTest

INCLUDEPATH += \
    googletest/googletest/include \
    googletest/googlemock/include \
    googletest/googletest \
    googletest/googlemock

SOURCES = \
    googletest/googletest/src/gtest-all.cc \
    googletest/googlemock/src/gmock-all.cc

Eine zusätzliche .pri Datei ermöglicht eine einfachere Einfügung von Google-Test in das Projekt:

qmake (GoogleTest.pri)

1
2
3
4
5
INCLUDEPATH += \
    $$PWD/googletest/googletest/include \
    $$PWD/googletest/googlemock/include

LIBS += -L../GoogleTest -lGoogleTest

Und endlich kommt hier der schwärere Teil. Das Testprogramm ist eine abgesonderte Anwendung, also wenn das Programm durch Creator ausgeführt wird, soll nur das normale Programm starten, und nicht die Tests. Wenn man die Tests ausführen will, ist es durch die “Test results” Registerkarte von Creator möglich.

qmake (Tests.pro)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
QT += testlib         #Include QtTest to use SignalSpy, QTest::mouseClick, etc

TARGET = Tests
TEMPLATE = app

CONFIG += c++14
CONFIG += testcase    #Creates 'check' target in Makefile.
CONFIG -= debug_and_release
CONFIG += console

INCLUDEPATH += ../MyCoolLibs/MyLib1 \
               ../MyCoolLibs/MyLib2

LIBS += -L../MyCoolLibs/MyLib1 -lMyLib1 \
        -L../MyCoolLibs/MyLib2 -lMyLib2

include(../GoogleTest/GoogleTest.pri)

HEADERS += 


SOURCES += main.cpp \
           tst_mylib1.cpp \
           tst_mylib2.cpp

Wie es offensichtlich ist, ist das auch eine App-Vorlage, aber testcase wurde zu den Einstellungen hinzugefügt, und die obengenannte GoogleTest.pri wurde auch eingeschlossen. Diese letzte Datei könnte während einer Migration hilfreich sein, weil die Bearbeitung von Projektdateien nicht nötig ist, qmake über den Ort von Google Test zu informieren.

Das ist alles. Mit den oben dargestellten Einstellungen ist es möglich, zwei gesonderte Programmen (Test und Hauptprogramm) zu erstellen. Beide Programmen benutzen die gleiche Bibliotheken, Creator ist in der Lage das Hauptprogramm auszuführen, die Tests erscheinen in der “Test results” Registerkarte, und das Testauswahl funktioniert auch. Das ist auch erwähnenswert, dass Qt Creator die Umgebungsvariable LD_LIBRARY_PATH nicht immer aktualisiert, und deshalb kann das Programm nicht immert ausgeführt werden. Die Fehlermeldung ist das folgende:

Shell

libMyLib1.so.1: cannot open shared object file: No such file or directory

In diesem Fall sollte in

Projects (mode) –> Run Settings (under Build & Run) –> Run Environment (expand the Details) –> LD_LIBRARY_PATH (somewhere in the list)

sichergestellt werden, dass LD_LIBRARY_PATH das Bibliothekpfad enthält. Wenn es fehlt, sollte die Umgebungsvariable aktualisiert werden, und dann wird das Programm normal starten.

Wie immer, vielen Dank fürs Lesen.