Aus Linux-Magazin 01/2011

Qt-Programme mit Qtestlib und Ctest testen und überwachen

© Rike, Pixelio.de

Wer zuverlässige Software schreiben will, kommt um Absturzversuche in kontrollierter Umgebung nicht herum. Qt-Entwickler dürfen sich glücklich schätzen, dass ihre Klassenbibliothek bereits Hilfsmittel zum Bau von Crashtest-Dummys enthält. Das Tool Ctest bereitet danach die Bruchstücke übersichtlich auf.

Professionelle Software-Entwicklung verliert den Mythos, dass das Programmieren allein als Königsdisziplin gilt: Mehr denn je legen Auftraggeber Wert auf strukturiert getestete Software. Entwickler für Qt können sich glücklich schätzen, dass der Bibliotheksanbieter Nokia dafür schon Vorkehrungen getroffen hat.

Um die Schritte eines strukturierten Testablaufs nachzuvollziehen, soll im Folgenden ein austauschbares Beispielprojekt herhalten: Die Anwendung, die XML-Daten zur Speicher- und Registerplanung in einem Chipentwurf verarbeitet, hat ein Entwickler bereits vollständig mit Qt und C++ implementiert. Sie lässt sich mit »cmake« und »make« problemlos kompilieren und starten.

Intern baut sie auf einem XML-Modell auf, dem Simple-DOM-Modell [1], und besitzt eine einfache Benutzeroberfläche (Abbildung1). Diese oft in Qt-Anwendungen auftretenden Objekte lassen sich einfach auf eigene Tests übertragen.

Nun geht es darum, das Programm auf Konformität mit der Spezifikation zu prüfen. Dazu liegt eine vom Auftraggeber bereitgestellte Beispieldatei vor, die die Software tadellos verarbeitet. Aus den Startadressen der einzelnen Elemente ermittelt sie bei korrekter Umsetzung die zugehörigen Speichergrößen, die mit vorab dokumentierten Sollwerten für das Chipdesign übereinstimmen.

Modell als Blackbox

Die erste Aufgabe des Softwaretesters besteht darin, das Modell mit Hilfe der Datenstruktur aus der vorgegebenen Referenzdatei zu verifizieren. Das ist zwar kein Beweis im mathematischen Sinne, aber weil die gelieferten Daten bereits spezielle Fälle abdecken, schaffen sie ein Grundmaß an Sicherheit. Zudem lässt sich mit dieser Methode die Anwendung auch bei späteren Änderungen immer wieder testen und prüfen.

Das Herzstück der Beispielanwendung bildet das erwähnte Modell, das die Speicherstruktur des geplanten Chips repräsentiert. Das Laden einer Speicherstruktur im XML-Format füllt das Modell mit Daten. Um die Speicherstruktur zu bearbeiten und zu visualisieren, greifen anschließend verschiedene Ansichten über das Modell auf die Daten zu. Deshalb ist seine Verlässlichkeit besonders wichtig und Tests sind an dieser Stelle gut investierte Zeit.

Gemeinsam ist allen beteiligten Anwendungsteilen, dass sie nur die öffentlichen Funktionen des Modells nutzen, was auch dem Gedanken der Objektorientierung entspricht. Daher ist für die Anwendung das Innenleben des Modells unerheblich. Wichtig sind nur die Ergebnisse, die aus bestimmten Eingangsdaten resultieren. Darum ist es naheliegend, dass die Tests genau diesen Transformationsprozess im Ganzen prüfen. Da die Blackbox eine abgeschlossene Einheit oder ein Modul darstellt, heißt diese Art von Tests auch Unittest [2].

Testgeräte an Bord

Um solche Tests durchzuführen, setzen Programmierer die mit Qt ausgelieferte Klasse »QTest« ein, die sich im Modul »QTestLib« findet [3]. Die Klasse nimmt sowohl Algorithmen als auch grafische Oberflächen in Augenschein. Jede Testanwendung bildet eine Klasse des Typs »QObject« und darf beliebig viele Tests enthalten. Diese wiederum sind die privaten Slots des »QObject« . In der Regel erzeugen und prüfen die Funktionen eine Instanz des zu testenden Objekts. Das bedeutet, dass sich »QTestLib« vornehmlich für Modultests eignet.

Ein einfaches Beispiel zeigt Listing1. Das Objekt enthält nur einen Test, den die Funktion »addressRange()« implementiert. Ein spezielles Makro wandelt die simple Testklasse in ein ausführbares Programm um. Es führt nach dem Start automatisch alle privaten Slots nacheinander aus.

Neben den privaten Slots, die Testfunktionen beinhalten, dürfen Entwickler die vier Spezialslots »initTestCase()« , »cleanupTestCase()« , »init()« und »cleanup()« nutzen. Während Qt alle anderen Testfunktionen in der Reihenfolge ihrer Definition abarbeitet, durchläuft die Bibliothek diese Funktionen zu speziellen Zeitpunkten: Weil die erste die Testsammlung initialisiert, ruft Qt sie beim Start des Programms auf. Um aufzuräumen ruft Qt vor dem Testende »cleanupTestCase()« auf. Die beiden verbleibenden Methoden dienen dem gleichen Zweck, allerdings auf jede Testfunktion bezogen. Qt arbeitet sie daher vor und nach jeder einzelnen Testfunktion ab.

Eine weitere Ausprägung Spezialfunktionen ruft Testroutinen mit vielen Datenkombinationen auf. Die sind aber für das Beispiel nicht nötig [4]. Außerdem üben die Ergebnisse der Testfunktionen Einfluss auf den Ablauf der Testanwendung aus. Ein Fehlschlag von »initTestCase()« beendet bereits die gesamte Testanwendung. Der Fehlschlag von »init()« sorgt hingegen lediglich für das Überspringen der folgenden Testfunktion.

Abbildung 1: Die Testanwendung holt Daten für einen Chipentwurf aus einer XML-Datei und strukturiert sie.

Abbildung 1: Die Testanwendung holt Daten für einen Chipentwurf aus einer XML-Datei und strukturiert sie.

Navigation durch Testfälle

Für die Ablaufsteuerung und den Wertevergleich innerhalb der Testfunktionen stellt die Klasse »QTest« einige Makros bereit. Die wichtigsten von ihnen, nämlich »QCOMPARE(tatsächlich, erwartet)« und »QVERIFY(Bedingung)« , vergleichen Werte. Das Makro »QVERFIY« erwartet eine Bedingung wie bei einem If-Statement. Im Fehlerfall gibt die Testanwendung die Nummer der Programmzeile des Makros aus, das fehlgeschlug. »QCOMPARE« gibt zusätzlich noch den tatsächlichen und den erwarteten Wert aus. Das hilft dabei, Fehler schneller zu identifizieren, spart manche »QDebug« -Anweisung und macht sich besonders bei den datengetriebenen Tests bemerkbar. Dort prüft der Testablauf eine Testbedingung nämlich oft mit mehreren Wertepaaren, sodass dann »QCOMPARE« die bessere Wahl darstellt.

Listing 1

Header für die erste Testklasse

01 #include <QObject>
02
03 class TestMemoryMapModel : public QObject
04 {
05 Q_OBJECT
06 private slots:
07 void addressRange();
08 };

Da viele Tests für einen erfolgreichen Durchlauf Randbedingungen erfordern, bietet Qt das Makro »QSKIP(Beschreibung, Modus)« an. Innerhalb einer Testfunktion stoppt der Aufruf von »QSKIP« das Ausführen der Funktion und markiert sie im Protokoll als übersprungen. Der Modus ist nur in den speziellen Slots und bei datengetriebenen Tests von Bedeutung. Bei letzteren überspringt »QSKIP« im Modus »SkipSingle« den aktuellen Datensatz und setzt die Funktion mit dem folgenden fort. Im Modus »SkipAll« bricht »QSKIP« alle folgenden Datensätze ab und springt zur folgenden Testfunktion.

In den oben genannten Spezialfunktionen sind die Auswirkungen von »QSKIP« gleichbedeutend mit einem Fehlschlag der Funktionen. Generell sollten Entwickler das Makro »QSKIP« nur dazu nutzen, um Fehlschläge bei unzureichenden Testvoraussetzungen zu vermeiden. Darunter fällt zum Beispiel eine fehlende Datenbankverbindung, die eigentlich für den Test nötig wäre.

Kalkulierte Fehler

Um bewusste oder unvermeidliche Fehler in der Implementation zu berücksichtigen, setzen Programmierer besser »QEXPECT_FAIL« ein. Der Aufruf des Makros vor einer Prüfanweisung wie »QCOMPARE« negiert ihr Ergebnis. Ein Fehlschlag der Prüfanweisung wird daher ein Erfolg und umgekehrt. Zusätzlich fügt die Testanwendung dem Testprotokoll für einen erwarteten Fehlschlag die Ausgabe »XFAIL« und für einen unerwarteten Erfolg »XPASS« hinzu. Der Einsatz des Makros »QEXPECT_FAIL« hilft daher nicht nur sich an Implementierungsdefizite zu erinnern, sondern definiert durch die erhaltene Prüfanweisung bereits den Sollzustand.

Teil des Projekts

So vorbereitet setzen Entwickler konkrete Tests um. Damit sie die Übersichtlichkeit des Quellcode wahren, speichern sie die nötigen Dateien für die Tests in einen Unterordner »tests« ihres Projekts. Der Befehl »add_subdirectory(tests)« in der »CmakeLists.txt« des Projekts bindet den Unterordner ein. Die Testklasse für den ersten Test hat den Namen »TestMemoryMapmodel« und den bereits bekannten Header aus Listing1.

In der Implementierung sorgen der Befehl »#include testmemorymapmodel.moc« und die »qt4_automoc« -Funktion von Cmake dafür, dass der MOC-Compiler von Qt die Klasse bearbeitet und ihre Signale und Slots umwandelt. Die Prüfanweisungen für den Test befinden sich in der Funktion »addressRange()« (siehe Listing2). Für die Ausführung der Anweisungen müssen natürlich die Modellklasse und die Klasse »QTest« ebenfalls eingebunden sein.

Zunächst erzeugt der Test eine Instanz des Modells und ermittelt mit dem Code ab Zeile2 die Indizes für die zu prüfenden Objekte. Anhand ihres Namens aus der ersten Spalte des Modells prüft er ab Zeile 8, ob sie an der richtigen Stelle im Modellbaum stehen. Ist bis dahin kein Test fehlgeschlagen, prüft in den Zeilen 12 und 13 der Test die berechnete Speichergröße in der dritten Spalte des Modells. Das an die Konstante angehängte »ll« in der letzten Zeile teilt dem Compiler dabei mit, dass die Konstante, genau wie die Rückgabe des Modells, vom Typ »long long« ist. Ohne diese Angabe würde der Compiler Zahlenkonstanten als einfache Integers interpretieren und keine passende »QCOMPARE« -Variante finden.

Listing 2

Implementierung der ersten Testklasse

01 MemoryMapModel *model = new MemoryMapModel(domDocument, this);
02 QModelIndex area = model->index(0, 0);
03 QModelIndex sram = model->index(0, 2, area);
04 QModelIndex flash = model->index(1, 0, area);
05 QVERIFY(area.isValid());
06 QVERIFY(sram.isValid());
07 QVERIFY(flash.isValid());
08 QCOMPARE(model->data(model->index(area.row(), 0)).toString(), QString("area"));
09 QCOMPARE(model->data(model->index(sram.row(), 0, area)).toString(), QString("SRAM"));
10 QCOMPARE(model->data(flash, 0).toString(), QString("FLASH"));
11 QEXPECT_FAIL("", "Don't know how to handle untyped areas", Continue);
12 QCOMPARE(model->data(sram), QVariant());
13 QCOMPARE(model->data(model->index(flash.row(), 2, area)).toLongLong(), 1879048192ll);

Anwendungen mit Prüfausrüstung ausstatten

Die Anweisung in Zeile11 dient schließlich der Negation des Tests in Zeile 12. Dieser schlägt fehl, weil das Modell den Typ des Elements noch nicht berücksichtigt und daher eine Speichergröße bestimmt. Im fertigen Produkt sollte dies aber nicht mehr der Fall sein. Ihren Code schließen Entwickler mit dem Makro »QTEST_MAIN(TestMemoryMapModel)« ab, um Qt mitzuteilen, dass er eine Testklasse darstellt.

Die Zeilen in Listing3 aus der »CMakeLists.txt« im Verzeichnis »tests« binden den Test in das vorhandene Projekt ein. Daraufhin übersetzt ein »make« im Buildverzeichnis nach der Konfiguration durch »cmake ../« die Testanwendung. Nach dem Start mit »bin/testmemorymapmodel« sollte sie eine Ausgabe wie in Abbildung2 erzeugen. Darin ist der erwartete Fehlschlag zu erkennen, dass also die Testfunktion korrekt durchläuft. Wer nun »initTestCase()« und »cleanupTestCase()« hinzuzählt, erkennt die drei als bestanden gemeldeten Tests.

Nach diesem komplizierten ersten Test soll eine zweite Prüfung die Funktionstüchtigkeit des Modells sichern. Dazu bietet Nokia die Klasse »ModelTest« an, die nach häufigen Fehlern in Implementierungen des »QAbstractItemModel« -API Ausschau hält [5]. Um es zu nutzen, kopiert der Entwickler die Dateien »modeltest.h« und »modeltest.cpp« in das Verzeichnis »tests« . Die Testausführung findet in der hinzugefügten Funktion »void baseModelTest()« der Testklasse statt. In der Implementierung beginnt der Test mit:

MemoryMapModel *model = new MemoryMapModel(QDomDocument(), this);
ModelTest *modelTest = new ModelTest(model, this);

Der Code erzeugt eine Instanz des Modells und übergibt den Zeiger auf das Objekt an eine Instanz des Modeltests. Damit ist er an das »MemoryMapModel« angedockt und führt bei jeder Änderung des Modells einige Tests aus. Daher ist es nur noch nötig, einen Testdatensatz zu laden. Wenn der Entwickler »modeltest.cpp« zur Quellenliste hinzufügt und erneut »make« und »bin/testmemorymapmodel« ausführt, zeigt die Ausgabe der Testanwendung nunmehr vier bestandene Tests an.

Listing 3

Tests in das Cmake-Projekt einbinden

01 SET(QT_USE_QTTEST TRUE)
02 INCLUDE(${QT_USE_FILE})
03 INCLUDE_DIRECTORIES(
04 ${QT_INCLUDES}
05 ${CMAKE_CURRENT_BINARY_DIR}
06 ${CMAKE_CURRENT_BINARY_DIR}/../
07 ${CMAKE_CURRENT_SOURCE_DIR}/../
08 )
09 ADD_DEFINITIONS(${QT_DEFINITIONS})
10 SET(test_CPP
11 testmemorymapmodel.cpp
12 ../memorymapmodel.cpp
13 ../domitem.cpp
14 )
15 QT4_AUTOMOC(${test_CPP})
16 ADD_EXECUTABLE(testmemorymodel ${test_CPP})
17 TARGET_LINK_LIBRARIES(testmemorymodel${QT_LIBRARIES})

An der Oberfläche

Für die Ausführung von GUI-Tests stellt »QTest« weitere Funktionen bereit, die der Simulation von Maus- und Tastatureingaben dienen. Der Beispieltest verwendet die Funktionen »keyClick(*Widget, Key)« , »keyClicks(*Widget, Sequenz)« und »mouseClick(*Widget, Button, Modifier, Position)« . Gemeinsam ist ihnen, dass sie als erstes Argument einen Zeiger auf das Oberflächenobjekt erwarten, das auf den Prüfstand soll. Dabei interagiert die erste Funktion durch einen einzelnen Tastendruck mit dem Objekt. Als zweiten Parameter erwartet sie entweder einen Buchstaben als »char« oder einen Qt-Keycode.

Abbildung 2: Die Ausgabe des ersten Tests zeigt den erwarteten Fehlschlag und den daher bestandenen Test.

Abbildung 2: Die Ausgabe des ersten Tests zeigt den erwarteten Fehlschlag und den daher bestandenen Test.

Die zweite Funktion bietet die Möglichkeit, gleich eine ganze Reihe von Tastendrücken zu simulieren. Daher erwartet sie als zweites Argument einen »QString« mit der Zeichenkette. Zur Simulation eines Mausklicks dient die dritte Funktion. Sie erwartet als zweites Argument den zu simulierenden Mausknopf, der sich durch einen Modifier ergänzen lässt – beispielsweise die [Strg]-Taste.

Wichtiger ist allerdings das vierte Argument, da es den Ort des Mausklicks definiert. Ohne Angabe erfolgt er auf die Mitte des Objekts. Das reicht für Menüs und einfache Knöpfe aus. Der Koordinatenursprung für die Angabe des Ausführungsortes ist aber nicht die Mitte des Objekts, sondern, wie bei Qt üblich, die obere linke Ecke des Objekts.

Listing 4

Ein GUI-Test anhand einer Spinbox

01 #include "testhexspinbox.h"
02 #include "testhexspinbox.moc"
03 #include "hexspinbox.h"
04 #include <QTest>
05 #include <QPoint>
06
07 void TestHexSpinBox::testHexSpinBox()
08 {
09 HexSpinBox *spinBox = new HexSpinBox();
10 QCOMPARE(spinBox->value(), 0ll);
11 QTest::keyClicks(spinBox, "A9");
12 QCOMPARE(spinBox->value(), 169ll);
13 QTest::keyClick(spinBox, Qt::Key_Up);
14 QCOMPARE(spinBox->value(), 170ll);
15 QPoint clickPoint(spinBox->size().width() - 2, spinBox->size().height() - 2);
16 QTest::mouseClick(spinBox, Qt::LeftButton, 0, clickPoint);
17 QCOMPARE(spinBox->value(), 169ll);
18 delete spinBox;
19 }
20
21 QTEST_MAIN(TestHexSpinBox);

Ferngesteuert klicken

Den praktischen Einsatz dieser Funktionen zeigt Listing4. Es testet eine Implementierung von »QAbstractSpinBox« , um Hexadezimalzahlen einzugeben (Abbildung3). Die Methode instanziert in Zeile9 das UI-Element. Komplexere Anwendungen bestehen aus mehr als einem Widget. Damit die Testfunktionen auch auf private Kinder des Hauptwidget zugreifen dürfen, verwenden Tester die Methoden »findChild()« und »findChildren()« . Die Klasse »TestMainWindow« in den Beispielquellen nutzt diese Funktionen zum Test des Dateimenüs.

Die nachfolgenden Tests prüfen, ob der Anfangswert zunächst 0 und nach der Eingabe der Hex-Folge »A9« tatsächlich dem Dezimalwert 169 entspricht. Der Druck auf die Pfeil-nach-oben-Taste erhöht ihn weiter auf 170.

Abbildung 3: Das Spinbox-Widget wandelt interne Dezimalzahlen in das Hexadezimalsystem um.

Abbildung 3: Das Spinbox-Widget wandelt interne Dezimalzahlen in das Hexadezimalsystem um.

Für den Test der Pfeiltasten am rechten Rand der Box kommt schließlich die Funktion »mouseClick()« zum Einsatz, die hier auf die Taste mit dem Pfeil nach unten klickt. Da sich diese rechts unten in der Box befindet, lässt sich der Klickpunkt durch den Abzug weniger Pixel von der Breite und der Höhe berechnen. Nach dem Klick prüft Zeile17 wieder das Resultat. Das Listing schließt erneut mit dem Makro, das die Testklasse zur Anwendung wandelt.

Systematisches Vorgehen

Alle Tests mit diesem Makro sind übrigens grafische Anwendungen und benötigen X. Sollen sie automatisch ablaufen, empfiehlt es sich, sie in einem vorgetäuschten X-Server zu starten. Dafür eignet sich das Tool Xvfb. Der Aufruf »xvfb-run -a Anwendung« führt sie im simulierten X aus. Die Schreibweise gelingt aber nicht bei allen Distributionen [6]. Schließlich fügen die Zeilen in Listing5 den Test in das Cmake-Projekt ein.

Abbildung 4: Die Anwendung besteht aus den Klassen 1 und 2, deren Funktion die beiden Testklassen prüfen.

Abbildung 4: Die Anwendung besteht aus den Klassen 1 und 2, deren Funktion die beiden Testklassen prüfen.

Wenn der Programmierer den GUI-Test fertiggestellt hat, startet er zwei Programme und überwacht deren Ausgabe. Bei einer größeren Anwendung erhöht sich ihre Anzahl schnell und das Ausführen gestaltet sich zeitraubend und unübersichtlich.

Listing 5

GUI-Tests ins Cmake-Projekt einbinden

01 SET(test_CPP
02 testhexspinbox.cpp
03 ../hexspinbox.cpp
04 ../memorymapmodel.cpp
05 ../domitem.cpp
06 )
07 QT4_AUTOMOC(${test_CPP})
08 ADD_EXECUTABLE(testhexspinbox ${test_CPP})
09 TARGET_LINK_LIBRARIES(testhexspinbox ${QT_LIBRARIES})

Abhilfe schafft dann das Tool Ctest, da es die Testausführung stark vereinfacht. Wie Abbildung4 verdeutlicht, führt Ctest die einzelnen Testanwendungen nacheinander aus und sammelt deren Ausgaben. Damit ist der Funktionsumfang des Werkzeugs noch nicht erschöpft: Die Möglichkeit, Ctest auch ohne Cmake einzusetzen, macht es daher auch für solche Projekte interessant, die dieses Buildtool nicht einsetzen.

Listing 6

Tests einfach aktivieren und deaktivieren

01 IF(BUILD_TESTING)
02 ADD_SUBDIRECTORY(tests)
03 ENDIF(BUILD_TESTING)

Listing 7

Testanwendungen bekanntmachen

01 ADD_TEST(TestMemoryMapModel ${EXECUTABLE_OUTPUT_PATH}/testmemorymodel)
02 ADD_TEST(TestHexSpinBox ${EXECUTABLE_OUTPUT_PATH}/testhexspinbox)

Listing 8

Berechnung der Testabdeckung

01 IF(BUILD_DEBUG)
02 SET(CMAKE_CXX_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -fprofile-arcs -ftest-coverage")
03 SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fprofile-arcs -ftest-coverage")
04 ENDIF(BUILD_DEBUG)

Weil das Beispiel Cmake einsetzt, integriert sich Ctest besonders einfach. Die Anweisung »include(Ctest)« – in der Datei »CMakeLists.txt« im obersten Verzeichnis gleich unter »cmake_minimum_required« – bindet Ctest in das Projekt ein. Die damit verfügbaren Erweiterungen im Makefile zeigen sich bei Kdevelop sofort im »Projekte« -Reiter. Innerhalb von Cmake steht dadurch die Option »BUILD_TESTING« bereit, die sich anbietet, um Tests ein- und auszuschalten. Dabei zeigt sich der Vorteil, die Tests in ein separates Verzeichnis auszugliedern, womit die Anweisungen in Listing6 die Tests aktivieren und deaktivieren.

Geordnete Binaries

Schließlich benötigt Cmake noch den Hinweis, welche Anwendung ein Test ist und welche nicht. Diese Aufgabe löst die Anweisung in Listing7 aus der »CMakeLists.txt« im Verzeichnis »tests« . Damit Ctest die ausführbare Anwendung auch findet, enthält der Befehl neben der Anwendung selbst auch den Pfad. Dabei kommt die Variable »${EXECUTABLE_OUTPUT_PATH}« zum Einsatz, die das oberste »CMakeLists.txt« definiert. Sie sorgt dafür, dass sich bei Abschluss der Übersetzung alle ausführbaren Dateien in diesem Ordner befinden. Sinnvoll ist meist die Wahl »${CMAKE_BINARY_DIR}/bin« , wodurch Cmake sie in den Ordner »bin« im Buildverzeichnis verschiebt.

Nach erneutem Durchlauf der Konfiguration mit »cmake ../« im Buildverzeichnis genügt es, »make test« oder »Ctest« aufzurufen, um alle Tests zu starten. Dabei ruft der Makeprozess die Tests nacheinander auf und gibt ihre Ausgaben zusammengefasst aus. Den damit verbundenen Verlust von Details verhindert die Option »-V« , womit Ctest die Originalausgabe für jeden Test anzeigt. Da dies bei einer größeren Anzahl von Tests unübersichtlich ist, bietet sich die Option »-R Test« an. Sie weist Ctest an, nur diesen Test auszuführen. Will der Entwickler die Tests auch weiterhin in Xvfb ablaufen lassen, startet er Ctest damit.

Diese einfachere Testausführung schöpft Ctest nicht aus. Um von seinen Vorteilen zu profitieren, nutzt der Tester die so genannten Dashboard-Tests, die Daten sammeln und an eine zentrale Aufbereitungseinheit wie Cdash übermitteln [7]. Zudem bieten sie weitere Vorteile, weshalb sich der Einsatz der angebotenen Dashboard-Tests auch ohne Dashboard lohnt (Abbildung5). Für verschiedene Testkategorien bietet Ctest die drei Testszenarien Continuous, Nightly und Experimental an. Ersteres ist dafür gedacht, nach jeder eingecheckten Version die Tests zu durchlaufen und so Fehler und seine Verursacher sofort zu entdecken.

Abbildung 5: Cdash gliedert die Ctest-Ergebnisse nach Szenarien: Es fasst jeden Lauf mit Hostnamen und Plattform zusammen und ergänzt sie durch die Werte der Testabdeckung und Speichertests (Dynamic Analysis). Per Mausklick sind Details zu Compiler-Meldungen und zeilengenaue Ansicht der Testabdeckung erreichbar.

Abbildung 5: Cdash gliedert die Ctest-Ergebnisse nach Szenarien: Es fasst jeden Lauf mit Hostnamen und Plattform zusammen und ergänzt sie durch die Werte der Testabdeckung und Speichertests (Dynamic Analysis). Per Mausklick sind Details zu Compiler-Meldungen und zeilengenaue Ansicht der Testabdeckung erreichbar.

Zusatzchecks integrieren

Die Nightly-Tests dienen im Gegensatz dazu, die Anwendung in einem bestimmten Zustand, also einer Revision des Versionskontrollsystems, zu testen. Beide Testszenarien benötigen daher zwingend ein solches System. Die Experimental-Tests kommen hingegen auch ohne Versionskontrolle aus. Wichtig ist dabei, dass sich die Szenarien bei den durchgeführten Tests an sich nicht unterscheiden. Der Befehl »ctest -D Szenario« konfiguriert zunächst die Quellen, übersetzt anschließend den Code und führt die Testanwendungen aus. Mit diesem Ablauf erschließt sich auch bereits ein großer Vorteil dieser Testarten, da sie neben den Tests sowohl die Konfigurations- als auch die Buildphase überprüfen.

Die Erweiterung dieses Grundablaufs ist unkompliziert möglich. Beispielsweise aktivieren die Zeilen aus Listing8 der obersten »CMakeLists.txt« die Ermittlung der Testabdeckung. Sofern das Bauen mit einem GCC-kompatiblen Compiler erfolgt und die Anwendung Debugsymbole enthält, ermittelt Cmake die nötige Testausführung daraufhin automatisch. Ebenso einfach aktiviert der Tester Speichertests. Führt er nach der Installation von Valgrind [8] Ctest mit der zusätzlichen Option »-D ExperimentalMemCheck« aus, lässt das Werkzeug alle Checks ein weiteres Mal in Valgrind ablaufen und ermittelt so Speicherprobleme.

Überwachungszentrale

Die erstellten Tests bieten mit den mitgelieferten Ctest-Funktionen zusammen bereits ein großes Überwachungspotenzial. Ein weiterer Vorteil ist, dass neben der Anwendung auch die erstellten Tests samt Ctest plattformunabhängig sind. Die Erweiterung der Umgebung mit einem Dashboard wie Cdash verstärkt dies noch. Im Zusammenspiel erhalten Programmierer damit eine nahtlose Dokumentation, welche Plattformen mit welcher Anwendungsversion Probleme zeigen, bekommen oder verlieren.

Entwickler, die mit diesem Ansatz zufrieden sind, finden im Skriptingmodus von Ctest eine gute Ergänzung [9]. Cdash arbeitet die Vielfalt an Testergebnissen übersichtlich auf. Das zeigen auch Abbildung4 oder die Demo-Website des Projekts [10]. Die Mittel, die Qtestlib, Ctest und Cdash an die Hand geben, helfen Entwicklern ein ungeliebtes Thema besser zu schätzen: Wer seine Tests erst einmal im Griff und im Blick hat, findet dann auch wieder mehr Zeit und Ruhe zum Programmieren. (mg)

Der Autor

Joachim Langenbach ist einer der Gründer von Engsas – Engineering Solutions and Services Langenbach. Er berät Kunden zum Thema Datenhaltung und -verarbeitung in Ingenieurdisziplinien und entwickelt und bietet dazu noch weitere Dienstleistungen an.

DIESEN ARTIKEL ALS PDF KAUFEN
EXPRESS-KAUF ALS PDFUmfang: 6 HeftseitenPreis €0,99
(inkl. 19% MwSt.)
LINUX-MAGAZIN KAUFEN
EINZELNE AUSGABE Print-Ausgaben Digitale Ausgaben
ABONNEMENTS Print-Abos Digitales Abo
TABLET & SMARTPHONE APPS Readly Logo
E-Mail Benachrichtigung
Benachrichtige mich zu:
0 Kommentare
Älteste
Neuste Beste Bewertung
Nach oben