Testen mit Qt - Teil 2/3

Projektordner Example 2

Für den zweiten Teil nehmen wir das Projekt example1 aus dem ersten Teil als Ausgangsbasis. Dazu wird das Projekt kopiert und in example2 umbenannt. Danach zwei Unterordner angelegen: src für die Quellen und tests für die Tests. Alle vier Dateien aus example1 werden in den Unterordner src verschoben. Die Projektdatei example1.pro muss dabei in src.pro umbenannt werden. Dies ist notwendig, weil Qt bei der Verwendung von Unterprojekten erwartet, dass die Projektdatei wie der Ordner benannt ist, in dem sie liegt.

Da der Name der ausführbaren Datei per Konvention ebenfalls vom Ordnernamen abgeleitet wird, fügen wir der Projektdatei eine Zeile hinzu, um den Dateinamen selbst festzulegen:

src.pro

TARGET = example2
QT += core
QT -= gui
CONFIG += console
CONFIG -= app_bundle
HEADERS += calculator.h
SOURCES += main.cpp calculator.cpp

Wie bereits im ersten Teil angesprochen, wollen wir die Tests vom Produktivcode trennen. Das geht mit Qt ganz leicht mit Hilfe des subdirs-Templates. Im Projektordner wird die folgende neue Projektdatei angelegt:

example3.pro

TEMPLATE = subdirs
SUBDIRS = src tests
CONFIG += ordered

Damit werden die beiden Unterordner src und tests als Unterprojekte definiert. Das Unterprojekt haben wir bereits eingerichtet, um weiterhin eine ausführbare Datei zu kompilieren. Jetzt wollen wir mit dem zweiten Unterprojekt die Tests formaler gestalten, als im ersten Teil. Dafür nutzen wir das Framework QTestLib, was mit Qt ausgeliefert wird.

QTestLib bringt alles mit, was man benötigt, um auf Qt-Code Unit Tests ausführen zu können. Um den Test aus dem ersten Teil als Unit Test auszuführen ist folgende Quelldatei notwendig:

tst_calculatortest.cpp

#include <QtTest>

#include "../src/calculator.h"

class CalculatorTest : public QObject
{
    Q_OBJECT
    
private Q_SLOTS:
    void testSum();
};

void CalculatorTest::testSum()
{
    Calculator calculator;
    int expectedSum = 5;

    int actualSum = calculator.sum(3, 2);

    QCOMPARE(actualSum, expectedSum);
}

QTEST_APPLESS_MAIN(CalculatorTest)

#include "tst_calculatortest.moc"

Der Einfachheit verzichten wir auf eine Header-Datei und definieren die Testklasse direkt im Quelltext. Jeder Unit Test wird in Qt als eine eigene Testklasse implementiert und führt zu je einer ausführen Datei. Das Makro QTEST_APPLESS_MAIN erzeugt die notwendige main-Methode. Natürlich kann jede Testklasse beliebig viele Tests in Form von Slot-Methoden ausführen. Ebenso gibt es die bekannten Methoden füpr set up und tear down, dazu dienen die folgende vier Slots:

  • initTestCase() wird einmal vor dem ersten Test ausgeführt.
  • cleanupTestCase() wird einmal nach allen Tests ausgeführt.
  • init() wird vor jedem einzelnen Test ausgeführt.
  • cleanup() wird nach jedem einzelnen Test ausgeführt.

Zum Schluss fehlt nur noch die Projektdatei für das Testunterprojekt:

tests.pro

TARGET = tst_calculatortest
QT += testlib
QT -= gui
CONFIG += console testcase
CONFIG -= app_bundle
HEADERS += ../src/calculator.h
SOURCES += tst_calculatortest.cpp ../src/calculator.cpp
DEFINES += SRCDIR=\\\"$$PWD/\\\"

Die Projektdatei ähnelt stark dem einer normalen Applikation. Wichtig sind die folgenden Unterschiede. Um QTestLib nutzen zu können muss dies mit QT+=testlib deklariert werden. Die Option CONFIG+=testcase erlaubt es uns den Test als Teil des Builds automatisch auszuführen, dazu muss nur make mit dem Argument check aufgerufen werden. Die Befehle zu Bauen heißen jetzt also:

qmake
make check

Die Ausgabe beim Kompilieren enthält nun folgendes Testergebnis:

********* Start testing of CalculatorTest *********
Config: Using QTest library 4.8.3, Qt 4.8.3
PASS   : CalculatorTest::initTestCase()
PASS   : CalculatorTest::testSum()
PASS   : CalculatorTest::cleanupTestCase()
Totals: 3 passed, 0 failed, 0 skipped
********* Finished testing of CalculatorTest *********

Will man die Tests nichts ausführen beim Kompilieren, dann lässt man check weg.

Für eine einfache Bibliothek oder Applikation ist dieser Ansatz schon ganz gut. Bei komplexeren Projekten gibt es aber noch folgende Probleme zu lösen:

  • Die zu testenden Klassen müssen sowohl in der Projektdatei des Produktcodes als auch in der Projektdatei der Tests alle aufgeführt werden. Neben dem Aufwand der doppelten Pflege kommt hinzu, dass hier auch die Quelltextdatei im Testprojekt spezifiziert werden müssen, die nur nur implizit getestet werden. Gemeint sind Dateien die für den Tester nicht relevant sind, weil sie nur Hilfsklassen sind oder sich häufiger ändern können. Ein gutes Beispiel für solche Dateien sind Pointer-Implementierungen.
  • MIt dieser Projektstruktur lassen sich keine Komponenten Testen. Aber man ahnt es, das subdirs-Template kann hier natürlich helfen.

Im nächsten und letzten Teil, Teil 3, erläutere ich wie man den hier vorgestellten Ansatz auf beliebig große Projekte mit mehren Komponenten hochskaliert.

Zurück