Testen mit Qt - Teil 3/3

Projektordner Example 3

In diesem Teil wird das Projekt aus Teil 2 example2 weiter verfeinert. Nehmen wir an die Klasse Calculator ist unsere zu testende Komponente (Unit). Soweit bleibt alles wie bisher. Im Unterschied zuvor, nutzt die Klasse jetzt aber eine Hilfsklasse Adder, die eigentliche Berechnung durchführt.

Das Beispiel ist bewusst einfach gehalten, um sich auf Technik des des Testen zu konzentrieren. Stelle Sie sich vor, wir Testen ein Buchungssystem und wollen eine Rechnung schreiben. Oberflächlich betrachtet werden hier auch nur ein paar Zahlen addiert. Tatsächlich ist hier aber meist ein ganzen Ökosystem von Klassen betrachtet. WIr wollen hier nur die Klasse testen mit dem das Buchungssystem das Schreiben der Rechnung steuert. Unser einfacher Test kann in diesem Szenario dazu dienen zu validieren, das die Rechnungssystem korrekt aus den bestellten Artikeln und den Versandkosten berechnet wird.

Als erstes führen wir eine weitere Strukturierung unseres Projekts durch. Der src-Ordner wird unterteilt in ein app-Unterprojekt für die Applikaton und libs-Unterprojekt für alle benötigten Bibliotheken. Im libs-Ordner wird ein Unterprojekt calculator angelegt, das die Klassen Calculator und Adder enthält. Die main-Methode wird in das app-Projekt verschoben und die Header-Datei und Implementierung der Klasse Calculator in den das calculator-Projekt. Die scr.pro-Datei aus example2 wird jeweils in den app-Ordner und calculator-Ordner kopiert. Den Dateinamen dabei an den Ordnernamen anpassen!

Die Projektdatei der Applikation muss wie folgt angepasst werden. Die Header-Datei und die Implementierung der Calculator-Klasse werden entfernt. Dafür wird der include-Path erweitert und die neue calculator-Bibliothek eingebunden (siehe die letzten beiden Zeilen von app.pro). In der Datei main.cpp muss dann noch die include-Zeile für die Header-Datei der Calculator-Klasse angepasst werden.

app.pro

TARGET = example3
QT += core
QT -= gui
CONFIG += console
CONFIG -= app_bundle
SOURCES += main.cpp
INCLUDEPATH += ../libs
LIBS += -L../libs/calculator -lcalculator

main.cpp

#include <iostream>

#include <calculator/calculator.h>

int main()
{
    Calculator calculator;
    std::cout << "sum: " << calculator.sum(3, 2) << std::endl;
    return 0;
}

Als nächsten widmen wir uns der neuen Bibliothek calculator. Es wird eine neue Klasse Adder angelegt, die nun die Berechnung durchführt. Die Calculator-Klasse delegiert nun an die Adder-Klasse.

adder.h

#ifndef ADDER_H
#define ADDER_H

class Adder
{
public:
    int sum(int a, int b);
};

#endif // ADDER_H

calculator.h

#ifndef CALCULATOR_H
#define CALCULATOR_H

class Calculator
{
public:
    int sum(int a, int b);
};

#endif // CALCULATOR_H

adder.cpp

#include "adder.h"

int Adder::sum(int a, int b)
{
    return a + b;
}

calculator.cpp

#include "calculator.h"

#include "adder.h"

int Calculator::sum(int a, int b)
{
    Adder adder;
    return adder.sum(a, b);
}

Nun noch die obligatorische Projektdatei für die calculator-Bibliothek. Als Template kommt natürlich lib zum Einsatz. Der Einfachheit erstellen wir eine statische Bibliothek CONFIG+=staticlib. Damit müssen wir uns nicht mit relativen Pfaden rumschlagen zwischen Applikation und verwendeten Bibliotheken.

calculator.pro

TEMPLATE = lib
QT += core
QT -= gui
CONFIG += staticlib
HEADERS += calculator.h adder.h
SOURCES += calculator.cpp adder.cpp

Der Fluch der Unterprojekte ist die Anzahl der benötigten Projektdateien. Doch da sie aus nicht mehr als einer Liste der nachgelagerten Projekte bestehen hier ohne weitere Worte:

libs.pro

TEMPLATE = subdirs
SUBDIRS = calculator
CONFIG += ordered

src.pro

TEMPLATE = subdirs
SUBDIRS = libs app
CONFIG += ordered

example3.pro

TEMPLATE = subdirs
SUBDIRS = src tests
CONFIG += ordered

Erwähnenswert ist CONFIG+=ordered, damit werden die Unterprojekte in der unter SUBDIRS angegebenen Reihenfolge gebaut. Das ist sowohl für das Kompilieren der calculator-Bibliothek und Applikation sowie wie für die Tests entscheidend.

Der Test erfährt die selbe leichte Änderung wie die Applikation. Die Header-Datei und die Implementierung der Calculator-Klasse werden entfernt. Dafür wird der include-Path erweitert und die neue calculator-Bibliothek eingebunden (siehe die letzten beiden Zeilen von calculator.pro). In der Datei tst_calculatortest.cpp muss dann noch die include-Zeile für die Header-Datei der Calculator-Klasse angepasst werden.

Als letzte noch fehlende Datei fasst die Projektdatei tests.pro alle Tests zusammen.

tst_calculatortest.cpp

#include <QtTest>

#include <calculator/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"

calculator.pro

TARGET = tst_calculatortest
QT += testlib
QT -= gui
CONFIG += console testcase
CONFIG -= app_bundle
SOURCES += tst_calculatortest.cpp
DEFINES += SRCDIR=\\\"$$PWD/\\\"
INCLUDEPATH += ../../src/libs
LIBS += -L../../src/libs/calculator -lcalculator

tests.pro

TEMPLATE = subdirs
SUBDIRS = calculator
CONFIG += ordered

Mit dieser Projektstruktur kann das Projekt effektiv in Komponenten, sprich Bibliotheken und Plugins (Ordner plugins analog zu libs), zerlegt werden. Die main-Methode ist nur dafür da die Komponenten zusammenzustecken, zu initialisiern und die Applikation zu starten.

Ebenso können Unit Tests sauber in diese Projektstruktur integriert werden. Sind diese alle mit CONFIG+=testcase gekennzeichnet, lassen sie sich als Teil des Builds mit make check automatisch ausführen.

Ein Nachteil hat diese Projektkonfiguration noch. Statisch gelinkte Bibliotheken sind nicht immer gewünscht. Möchte man mit dynamisch gelinkten Bibliotheken arbeiten gibt es zwei Ansätze zur Verbesserung:

  1. MIt der Option DESTDIR kann das Zielverzeichnis für Bibliotheken und ausführbare Dateien gesetzt werden. Werden damit alle Artefakte im selben Verzeichnis abgelegt, funktioniert alles wie bisher.
  2. MIt Hilfe von Umgebungsvariablen wie LD_LIBRARY_PATH (Linux/Unix) und DYLD_LIBRARY_PATH (Mac OS X) kann der Suchpfad nach den Bibliotheken erweitert werden.

Zurück