Auf dieser Seite

Qt Test Bewährte Vorgehensweisen

Wir empfehlen Ihnen, bei Fehlerbehebungen und neuen Funktionen Qt-Tests hinzuzufügen. Bevor Sie versuchen, einen Fehler zu beheben, sollten Sie einen Regressionstest (idealerweise automatisch) hinzufügen, der vor der Fehlerbehebung fehlschlägt, den Fehler demonstriert und nach der Fehlerbehebung erfolgreich ist. Fügen Sie während der Entwicklung neuer Funktionen Tests hinzu, um zu überprüfen, ob diese wie beabsichtigt funktionieren.

Die Einhaltung einer Reihe von Codierungsstandards erhöht die Wahrscheinlichkeit, dass Qt-Autotests in allen Umgebungen zuverlässig funktionieren. Beispielsweise müssen einige Tests Daten von der Festplatte lesen. Wenn keine Standards dafür festgelegt sind, wie dies geschehen soll, sind manche Tests nicht portierbar. Ein Test, der beispielsweise davon ausgeht, dass sich seine Testdatendateien im aktuellen Arbeitsverzeichnis befinden, funktioniert nur bei einem „In-Source“-Build. Bei einem „Shadow“-Build (außerhalb des Quellverzeichnisses) kann der Test seine Daten nicht finden und schlägt fehl.

Die folgenden Abschnitte enthalten Richtlinien zum Schreiben von Qt Tests:

Die Hinweise zu Sicherheitsaspekten sollten neben diesen Best Practices berücksichtigt werden.

Allgemeine Grundsätze

Die folgenden Abschnitte enthalten allgemeine Richtlinien zum Schreiben von Unit-Tests:

Tests verifizieren

Schreiben Sie Ihre Tests und committen Sie sie zusammen mit Ihrer Korrektur oder neuen Funktion in einen neuen Branch. Sobald Sie fertig sind, können Sie den Zweig, auf dem Ihre Arbeit basiert, auschecken und anschließend die Testdateien für Ihre neuen Tests in diesen Zweig einchecken. Auf diese Weise können Sie überprüfen, ob die Tests auf dem vorherigen Zweig tatsächlich fehlschlagen und somit einen Fehler erkennen oder eine neue Funktion testen.

Beispielsweise könnte der Arbeitsablauf zur Behebung eines Fehlers in der Klasse „ QDateTime “ wie folgt aussehen, wenn Sie das Versionskontrollsystem Git verwenden:

  1. Erstellen Sie einen Branch für Ihre Korrektur und den Test: git checkout -b fix-branch dev
  2. Schreiben Sie einen Test und beheben Sie den Fehler.
  3. Erstellen Sie einen Build und führen Sie Tests sowohl mit der Korrektur als auch mit dem neuen Test durch, um zu überprüfen, ob der neue Test mit der Korrektur erfolgreich durchläuft.
  4. Fügen Sie die Korrektur und den Test zu Ihrem Zweig hinzu: git add tests/auto/corelib/time/qdatetime/tst_qdatetime.cpp src/corelib/time/qdatetime.cpp
  5. Committen Sie die Korrektur und den Test in Ihren Zweig: git commit -m 'Fix bug in QDateTime'
  6. Um zu überprüfen, ob der Test tatsächlich etwas erkennt, wofür Sie den Fix benötigt haben, checken Sie den Zweig aus, auf dem Ihr eigener Zweig basiert: git checkout dev
  7. Checken Sie nur die Testdatei aus dem Fix-Zweig aus: git checkout fix-branch -- tests/auto/corelib/time/qdatetime/tst_qdatetime.cpp

    Der Rest des Quellcode-Baums verbleibt auf „dev“ – ohne den Fix, aber mit dem neuen Test, der dort ausprobiert werden kann.

  8. Erstellen Sie den Test und führen Sie ihn aus, um zu überprüfen, ob er auf „dev“ fehlschlägt und somit tatsächlich einen Fehler aufdeckt.
  9. Sie können nun zum „fix“-Zweig zurückkehren: git checkout fix-branch
  10. Alternativ können Sie Ihren Arbeitsbaum auf „dev“ in einen sauberen Zustand zurücksetzen: git checkout HEAD -- tests/auto/corelib/time/qdatetime/tst_qdatetime.cpp

Wenn Sie eine Änderung überprüfen, können Sie diesen Arbeitsablauf anpassen, um sicherzustellen, dass die Änderung tatsächlich einen Test für ein Problem enthält, das sie auch tatsächlich behebt.

Geben Sie Testfunktionen aussagekräftige Namen

Die Benennung von Testfällen ist wichtig. Der Testname erscheint im Fehlerbericht eines Testlaufs. Bei datengesteuerten Tests erscheint auch der Name der Datenzeile im Fehlerbericht. Gute Namen können den Lesern des Berichts einen ersten Hinweis darauf geben, was schiefgelaufen ist.

Die Namen von Testfunktionen sollten deutlich machen, was die Funktion zu testen versucht. Verwenden Sie nicht einfach die Kennung aus dem Bug-Tracker, da diese Kennungen veralten, wenn der Bug-Tracker ersetzt wird. Außerdem haben Entwickler, die offline arbeiten, keinen Zugriff auf Ihren Bug-Tracker, und manche Bug-Tracker sind möglicherweise nicht für alle Benutzer zugänglich. Wenn der Fehlerbericht für spätere Leser des Testcodes von Interesse sein könnte, können Sie ihn in einem Kommentar neben einem relevanten Teil des Tests erwähnen.

Ebenso sollten Sie beim Schreiben datengesteuerter Tests den Testfällen beschreibende Namen geben, die angeben, auf welchen Aspekt der Funktionalität sich der jeweilige Testfall konzentriert. Nummerieren Sie den Testfall nicht einfach nur und verwenden Sie keine Bug-Tracking-Identifikatoren. Wer die Testausgabe liest, hat keine Ahnung, was die Zahlen oder Identifikatoren bedeuten. Sie können in der Testzeile einen Kommentar hinzufügen, der den Bug-Tracking-Identifikator erwähnt, sofern dies relevant ist. Am besten vermeiden Sie Leerzeichen und Zeichen, die für Befehlszeilen-Shells, auf denen Sie Tests ausführen möchten, von Bedeutung sein könnten. Dies erleichtert die Angabe des Tests und des Tags in der Befehlszeile Ihres Testprogramms – beispielsweise, um einen Testlauf auf nur einen Testfall zu beschränken.

Schreiben Sie in sich geschlossene Testfunktionen

Innerhalb eines Testprogramms sollten Testfunktionen voneinander unabhängig sein und nicht davon abhängen, dass vorherige Testfunktionen bereits ausgeführt wurden. Sie können dies überprüfen, indem Sie die Testfunktion eigenständig mit ` tst_foo testname` ausführen. Vermeiden Sie bei datengesteuerten Tests ebenfalls Abhängigkeiten zwischen den Zeilen der Testdatentabelle, damit mit ` tst_foo function:tag ` eine einzelne Zeile isoliert ausgeführt werden kann (zum Beispiel, um zu untersuchen, warum sie fehlschlägt).

Verwenden Sie Instanzen der zu testenden Klasse nicht in mehreren Tests wieder. Testinstanzen (z. B. Widgets) sollten keine Membervariablen der Tests sein, sondern vorzugsweise auf dem Stack instanziiert werden, um eine ordnungsgemäße Bereinigung auch bei einem fehlgeschlagenen Test zu gewährleisten, sodass sich die Tests nicht gegenseitig beeinträchtigen.

Wenn Ihr Test globale Änderungen beinhaltet, achten Sie darauf, dass der vorherige Zustand am Ende des Tests wiederhergestellt wird, unabhängig davon, ob er bestanden oder fehlgeschlagen ist. Da ein Fehlschlag die Ausführung von Code verhindert, der nach der fehlgeschlagenen Prüfung folgt, funktioniert die Wiederherstellung am Ende des Tests nicht, wenn der Test fehlschlägt. Die robuste Methode zur Wiederherstellung auch bei einem Fehlschlag besteht darin, ein RAII-Objekt (Resource Acquisition Is Initialization) zu instanziieren, dessen Destruktor den vorherigen Zustand wiederherstellt. Dies lässt sich oft bequem mit ` qScopeGuard()` bewerkstelligen, zum Beispiel

const auto restoreDefaultLocale = qScopeGuard([prior = QLocale()]() {
    QLocale::setDefault(prior);
});

vor dem ersten Aufruf von ` QLocale::setDefault()` in einem Test, der die vom zu testenden Code verwendete Locale steuern muss.

Vermeiden Sie externe Abhängigkeiten

Testfunktionen sollten ebenfalls jegliche Abhängigkeit von externen Ressourcen vermeiden. Eine solche Abhängigkeit kann leicht dazu führen, dass der Test fehlschlägt, wenn diese Ressource vorübergehend nicht verfügbar ist. Selbst wenn sie verfügbar ist, kann die Ressource den Zugriff durch das Testsystem blockieren – wenn Tests beispielsweise häufig ausgeführt werden, könnte die Ressource den Test als unerwünschte Belastung ihres Dienstes interpretieren.

Das Überspringen des Tests bei Unzugänglichkeit könnte ein Problem verbergen, das bei Ausführung des Tests aufgedeckt worden wäre, was diese Vorgehensweise zu einer ungeeigneten Lösung für diese Probleme macht. Es ist besser, eine lokale Nachbildung der Ressource zu erstellen, die für die Zwecke des Tests über ausreichende Eigenschaften verfügt.

Externe Abhängigkeiten stellen auch ein Problem für Entwickler dar, die offline arbeiten, sowie für Tests in einer Sandbox – wie beispielsweise einer isolierten virtuellen Maschine –, wenn Codeänderungen aus nicht vertrauenswürdigen Quellen bewertet werden. Siehe „Qt Test: Security Considerations“ für weitere Informationen zu diesem Thema.

Testen Sie den gesamten Stack

Wenn eine API in Form von pluggbaren oder plattformspezifischen Backends implementiert ist, die die Hauptarbeit übernehmen, sollten Sie darauf achten, Tests zu schreiben, die die Codepfade bis hinunter in die Backends abdecken. Das Testen der API-Teile der oberen Schicht mithilfe eines Mock-Backends ist eine gute Möglichkeit, Fehler in der API-Schicht von den Backends zu isolieren, stellt jedoch nur eine Ergänzung zu Tests dar, bei denen die tatsächliche Implementierung mit Daten ausgeführt wird, die reale Bedingungen originalgetreu widerspiegeln.

Sorgen Sie für schnelle Testdurchläufe

Tests sollten keine Zeit verschwenden, indem sie unnötig wiederholt werden, unangemessen große Mengen an Testdaten verwenden oder unnötige Leerlaufzeiten verursachen.

Dies gilt insbesondere für Unit-Tests, bei denen jede zusätzliche Sekunde Ausführungszeit dazu führt, dass das CI-Testing eines Branches über mehrere Ziele hinweg länger dauert. Denken Sie daran, dass Unit-Tests von Last- und Zuverlässigkeitstests zu unterscheiden sind, bei denen größere Testdatenmengen und längere Testläufe zu erwarten sind.

Benchmark-Tests, bei denen in der Regel derselbe Test mehrfach ausgeführt wird, sollten in einem separaten Verzeichnis „ tests/benchmarks “ abgelegt und nicht mit funktionalen Unit-Tests vermischt werden.

Verwenden Sie datengesteuerte Tests

Datengesteuerte Tests erleichtern das Hinzufügen neuer Tests für Randbedingungen, die in späteren Fehlerberichten festgestellt werden.

Die Verwendung eines datengesteuerten Tests anstelle der sequenziellen Prüfung mehrerer Elemente in einem Test erspart die Wiederholung sehr ähnlicher Codezeilen und stellt sicher, dass spätere Fälle auch dann getestet werden, wenn frühere fehlschlagen. Außerdem fördert dies systematisches und einheitliches Testen, da auf jede Datenstichprobe dieselben Tests angewendet werden.

Wenn ein Test datengesteuert ist, können Sie sein Daten-Tag zusammen mit dem Namen der Testfunktion als „ function:tag “ in der Befehlszeile des Tests angeben, um den Test nur für einen bestimmten Testfall auszuführen, anstatt für alle Testfälle der Funktion. Dies kann sowohl für ein globales Daten-Tag als auch für ein lokales Tag verwendet werden, das eine Zeile aus den eigenen Daten der Funktion identifiziert; Sie können diese sogar kombinieren, z. B. als „ function:global:local “.

Verwenden Sie Abdeckungs-Tools

Verwenden Sie ein Abdeckungs-Tool wie Coco oder gcov, um Tests zu schreiben, die so viele Anweisungen, Verzweigungen und Bedingungen wie möglich in der zu testenden Funktion oder Klasse abdecken. Je früher dies im Entwicklungszyklus einer neuen Funktion geschieht, desto einfacher lässt sich später bei der Refaktorisierung des Codes Regressionen erkennen.

Wählen Sie geeignete Mechanismen zum Ausschluss von Tests

Es ist wichtig, den geeigneten Mechanismus zum Ausschluss nicht anwendbarer Tests auszuwählen.

Verwenden Sie ` QSKIP()` für Fälle, in denen eine gesamte Testfunktion zur Laufzeit als in der aktuellen Testumgebung nicht anwendbar erkannt wird. Wenn nur ein Teil einer Testfunktion übersprungen werden soll, kann eine bedingte Anweisung verwendet werden, optional mit einem Aufruf von ` qDebug() `, um den Grund für das Überspringen des nicht anwendbaren Teils zu melden.

Bei bekannten Testfehlern, die letztendlich behoben werden sollten, wird „ QEXPECT_FAIL “ empfohlen, da es die Ausführung des restlichen Tests unterstützt, sofern dies möglich ist. Außerdem wird dadurch überprüft, ob das Problem weiterhin besteht, und der Code-Betreuer wird informiert, falls er es unbeabsichtigt behebt – ein Vorteil, der auch bei Verwendung des Flags „ Abort “ zum Tragen kommt.

Testfunktionen oder Datenzeilen eines datengesteuerten Tests können mithilfe von ` #if` auf bestimmte Plattformen oder auf bestimmte aktivierte Funktionen beschränkt werden. Beachten Sie jedoch die Einschränkungen von `moc`, wenn Sie ` #if ` verwenden, um Testfunktionen zu überspringen. Der Präprozessor ` moc ` hat keinen Zugriff auf alle ` builtin `-Makros des Compilers, die häufig zur Erkennung von Compiler-Funktionen verwendet werden. Daher kann es vorkommen, dass „ moc “ für eine Präprozessorbedingung ein anderes Ergebnis liefert als der Rest Ihres Codes. Dies kann dazu führen, dass „ moc “ Metadaten für einen Test-Slot generiert, den der eigentliche Compiler überspringt, oder dass die Metadaten für einen Test-Slot weggelassen werden, der tatsächlich in die Klasse kompiliert wird. Im ersten Fall versucht der Test, einen Slot auszuführen, der nicht implementiert ist. Im zweiten Fall versucht der Test nicht, einen Test-Slot auszuführen, obwohl er dies eigentlich sollte.

Wenn ein gesamtes Testprogramm für eine bestimmte Plattform nicht anwendbar ist oder eine bestimmte Funktion nicht aktiviert ist, empfiehlt es sich, die Build-Konfiguration des übergeordneten Verzeichnisses zu nutzen, um den Build des Tests zu vermeiden. Wenn beispielsweise der Test „ tests/auto/gui/someclass “ für macOS ungültig ist, umschließen Sie dessen Einbindung als Unterverzeichnis in „ tests/auto/gui/CMakeLists.txt “ mit einer Plattformprüfung:

if(NOT APPLE)
    add_subdirectory(someclass)
endif

oder, falls „ qmake “ verwendet wird, fügen Sie die folgende Zeile zu „ tests/auto/gui.pro “ hinzu:

mac*: SUBDIRS -= someclass

Siehe auch „Tests mit QSKIP überspringen“.

Vermeiden Sie Q_ASSERT

Das Makro „ Q_ASSERT “ führt dazu, dass ein Programm abgebrochen wird, sobald die überprüfte Bedingung „ false “ erfüllt ist – allerdings nur, wenn die Software im Debug-Modus erstellt wurde. Sowohl in Release- als auch in Debug- und Release-Builds hat „ Q_ASSERT “ keine Auswirkung.

Q_ASSERT sollte vermieden werden, da es dazu führt, dass sich Tests je nachdem, ob ein Debug-Build getestet wird, unterschiedlich verhalten, und weil es dazu führt, dass ein Test sofort abgebrochen wird, wodurch alle verbleibenden Testfunktionen übersprungen werden und unvollständige oder fehlerhafte Testergebnisse zurückgegeben werden.

Außerdem werden alle Aufräum- oder Bereinigungsschritte übersprungen, die am Ende des Tests erfolgen sollten, wodurch der Arbeitsbereich möglicherweise in einem unaufgeräumten Zustand zurückbleibt, was zu Komplikationen bei weiteren Tests führen kann.

Anstelle von ` Q_ASSERT` sollten die Makrovarianten ` QCOMPARE()` oder ` QVERIFY()` verwendet werden. Sie bewirken, dass der aktuelle Test einen Fehler meldet und beendet wird, ermöglichen jedoch die Ausführung der verbleibenden Testfunktionen und den normalen Abschluss des gesamten Testprogramms. ` QVERIFY2()` ermöglicht sogar die Aufzeichnung einer aussagekräftigen Fehlermeldung im Testprotokoll.

Verfassen zuverlässiger Tests

Die folgenden Abschnitte enthalten Richtlinien zum Schreiben zuverlässiger Tests:

Vermeiden Sie Nebenwirkungen in Verifikationsschritten

Bei der Durchführung von Verifikationsschritten in einem Autotest mit den Funktionen „ QCOMPARE()“, „ QVERIFY()“ usw. sollten Nebenwirkungen vermieden werden. Nebenwirkungen in Verifikationsschritten können einen Test schwer verständlich machen. Außerdem können sie einen Test leicht auf eine Weise zum Scheitern bringen, die schwer zu diagnostizieren ist, wenn der Test so geändert wird, dass „ QTRY_VERIFY()“, „ QTRY_COMPARE()“ oder „ QBENCHMARK “ verwendet werden. Diese können den übergebenen Ausdruck wiederholt ausführen und somit etwaige Nebenwirkungen wiederholen.

Wenn Nebenwirkungen unvermeidbar sind, stellen Sie sicher, dass der vorherige Zustand am Ende der Testfunktion wiederhergestellt wird, selbst wenn der Test fehlschlägt. Dies erfordert in der Regel die Verwendung einer RAII-Klasse (siehe „Schreiben Sie in sich geschlossene Testfunktionen“ oben) oder einer „ cleanup() “-Methode. Platzieren Sie den Wiederherstellungscode nicht einfach am Ende des Tests. Wenn ein Teil des Tests fehlschlägt, wird dieser Code übersprungen und der vorherige Zustand nicht wiederhergestellt.

Vermeiden Sie feste Timeouts

Vermeiden Sie fest codierte Timeouts wie z. B. ` QTest::qWait()`, um darauf zu warten, dass bestimmte Bedingungen wahr werden. Ziehen Sie stattdessen die Verwendung der Klasse ` QSignalSpy `, der Makros ` QTRY_VERIFY()` oder ` QTRY_COMPARE()` oder der Klasse ` QSignalSpy ` in Verbindung mit den Makrovarianten ` QTRY_ ` in Betracht.

Mit der Funktion ` qWait() ` lässt sich eine feste Verzögerung zwischen der Ausführung einer Aktion und dem Warten auf den Abschluss eines durch diese Aktion ausgelösten asynchronen Vorgangs festlegen. Ein Beispiel hierfür ist das Ändern des Zustands eines Widgets und das anschließende Warten auf dessen Neuzeichnung. Solche Timeouts führen jedoch häufig zu Fehlern, wenn ein auf einer Workstation geschriebener Test auf einem Gerät ausgeführt wird, auf dem die Ausführung des erwarteten Verhaltens möglicherweise länger dauert. Die Erhöhung des festen Timeouts auf einen Wert, der um ein Vielfaches größer ist als auf der langsamsten Testplattform erforderlich, ist keine gute Lösung, da dies den Testlauf auf allen Plattformen verlangsamt, insbesondere bei tabellengesteuerten Tests.

Wenn der zu testende Code nach Abschluss des asynchronen Vorgangs Qt-Signale ausgibt, ist es besser, die Klasse ` QSignalSpy ` zu verwenden, um der Testfunktion mitzuteilen, dass der Verifizierungsschritt nun durchgeführt werden kann.

Wenn keine Qt-Signale vorhanden sind, verwenden Sie die Makros „ QTRY_COMPARE() “ und „ QTRY_VERIFY() “, die in regelmäßigen Abständen eine angegebene Bedingung prüfen, bis diese wahr wird oder ein maximales Timeout erreicht ist. Diese Makros verhindern, dass der Test länger dauert als nötig, und vermeiden gleichzeitig Fehler, wenn Tests auf schnelleren Systemen entwickelt und später auf langsameren ausgeführt werden.

Wenn keine Qt-Signale vorhanden sind und Sie den Test im Rahmen der Entwicklung einer neuen API schreiben, sollten Sie überlegen, ob die API von der Hinzufügung eines Signals profitieren könnte, das den Abschluss des asynchronen Verhaltens meldet. Wenn dies Ihre Tests vereinfachen würde, könnte es auch für die Aufrufer Ihrer API nützlich sein.

Vorsicht vor zeitabhängigem Verhalten

Einige Teststrategien sind anfällig für zeitabhängiges Verhalten bestimmter Klassen, was dazu führen kann, dass Tests nur auf bestimmten Plattformen fehlschlagen oder keine konsistenten Ergebnisse liefern.

Ein Beispiel hierfür sind Widgets zur Texteingabe, die oft über einen blinkenden Cursor verfügen, der dazu führen kann, dass Vergleiche von erfassten Bitmaps je nach Zustand des Cursors zum Zeitpunkt der Erfassung erfolgreich sind oder fehlschlagen. Dies kann wiederum von der Geschwindigkeit des Rechners abhängen, auf dem der Test ausgeführt wird.

Beim Testen von Klassen, die ihren Zustand basierend auf Timer-Ereignissen ändern, muss das timerbasierte Verhalten bei der Durchführung von Verifizierungsschritten berücksichtigt werden. Aufgrund der Vielfalt zeitabhängiger Verhaltensweisen gibt es keine einheitliche, generische Lösung für dieses Testproblem.

Für Texteingabe-Widgets umfassen mögliche Lösungen das Deaktivieren des Cursor-Blinkverhaltens (sofern die API diese Funktion bereitstellt), das Abwarten, bis sich der Cursor in einem bekannten Zustand befindet, bevor eine Bitmap erfasst wird (beispielsweise durch Abonnieren eines entsprechenden Signals, sofern die API eines bereitstellt) oder das Ausschließen des Bereichs, in dem sich der Cursor befindet, aus dem Bitmap-Vergleich.

Vermeiden Sie die Erfassung und den Vergleich von Bitmaps

Zwar ist die Überprüfung von Testergebnissen durch das Erfassen und Vergleichen von Bitmaps manchmal notwendig, doch kann dies recht anfällig und arbeitsintensiv sein.

Beispielsweise kann ein bestimmtes Widget auf verschiedenen Plattformen oder mit unterschiedlichen Widget-Stilen unterschiedlich aussehen, sodass Referenz-Bitmaps möglicherweise mehrfach erstellt und später, wenn sich die von Qt unterstützten Plattformen weiterentwickeln, gepflegt werden müssen. Änderungen, die sich auf die Bitmap auswirken, bedeuten daher, dass die erwarteten Bitmaps auf jeder unterstützten Plattform neu erstellt werden müssen, was den Zugriff auf jede einzelne Plattform erfordern würde.

Bitmap-Vergleiche können zudem durch Faktoren wie die Bildschirmauflösung des Testrechners, die Bittiefe, das aktive Design, das Farbschema, den Widget-Stil, die aktive Ländereinstellung (Währungssymbole, Textrichtung usw.), die Schriftgröße, Transparenzeffekte und die Wahl des Fenstermanagers beeinflusst werden.

Verwenden Sie nach Möglichkeit programmatische Mittel, wie z. B. die Überprüfung von Eigenschaften von Objekten und Variablen, anstatt Bitmaps zu erfassen und zu vergleichen.

Verbesserung der Testausgabe

Die folgenden Abschnitte enthalten Richtlinien zur Erstellung lesbarer und hilfreicher Testausgaben:

Auf Warnungen prüfen

Genau wie bei der Entwicklung Ihrer Software gilt auch hier: Wenn die Testausgabe mit Warnungen überladen ist, fällt es Ihnen schwerer, eine Warnung zu erkennen, die tatsächlich einen Hinweis auf das Auftreten eines Fehlers darstellt. Es ist daher ratsam, Ihre Testprotokolle regelmäßig auf Warnungen und andere irrelevante Ausgaben zu überprüfen und die Ursachen zu untersuchen. Wenn es sich um Anzeichen für einen Fehler handelt, können Sie festlegen, dass Warnungen zum Fehlschlagen des Tests führen.

Wenn der zu testende Code Meldungen ausgeben soll – beispielsweise Warnungen bei falscher Verwendung –, ist es ebenfalls wichtig zu testen, ob er diese bei entsprechender Nutzung tatsächlich ausgibt. Sie können mit ` QTest::ignoreMessage()` auf erwartete Meldungen des zu testenden Codes prüfen, die von ` qWarning()`, ` qDebug()`, ` qInfo()` und ähnlichen Funktionen erzeugt werden. Dadurch wird überprüft, ob die Meldung erzeugt wird, und sie wird aus der Ausgabe des Testlaufs herausgefiltert. Wird die Meldung nicht erzeugt, schlägt der Test fehl.

Wenn eine erwartete Meldung nur ausgegeben wird, wenn Qt im Debug-Modus kompiliert wurde, verwenden Sie QLibraryInfo::isDebugBuild(), um festzustellen, ob die Qt-Bibliotheken im Debug-Modus kompiliert wurden. Die Verwendung von #ifdef QT_DEBUG reicht nicht aus, da diese Funktion lediglich angibt, ob der Test im Debug-Modus kompiliert wurde – was jedoch nicht garantiert, dass auch die Qt-Bibliotheken im Debug-Modus kompiliert wurden.

Ihre Tests können überprüfen, ob sie keine Aufrufe von ` qWarning()` auslösen, indem sie ` QTest::failOnWarning()` aufrufen. Ohne Parameter (seit Qt 6.8) führt dies dazu, dass der Test fehlschlägt, wenn eine Warnung ausgegeben wird, und die Warnung wird angezeigt. Optional können Sie an ` failOnWarning() ` entweder eine zu prüfende Warnmeldung oder einen Mustertext (` QRegularExpression `) übergeben, um dieses Verhalten auf übereinstimmende Warnungen zu beschränken. (Diese gefilterten Versionen wurden in Qt 6.3 eingeführt.)

Sie können außerdem die Umgebungsvariable ` QT_FATAL_WARNINGS ` setzen, um Warnungen als schwerwiegende Fehler zu behandeln. Weitere Informationen finden Sie unter qWarning(); dies gilt nicht speziell für Autotests. Wenn Warnungen andernfalls in umfangreichen Testprotokollen untergehen würden, kann ein gelegentlicher Testlauf mit gesetzter Umgebungsvariable Ihnen helfen, auftretende Warnungen zu finden und zu beseitigen.

Vermeiden Sie die Ausgabe von Debug-Meldungen durch Autotests

Autotests, die erfolgreich abgeschlossen werden, sollten keine unbehandelten Warnungen oder Debug-Meldungen erzeugen. Dadurch kann das CI-Gate neue Warnungen oder Debug-Meldungen als Testfehler behandeln. Ähnlich wie bei Warnungen von Compilern beim Erstellen Ihres Codes sind Warnungen, wenn sie selten auftreten, nützliche Hinweise auf Probleme; treten sie jedoch regelmäßig auf, können sie für Entwickler, die ihre Änderungen testen, wichtige Probleme verschleiern.

Das Hinzufügen von Debug-Meldungen während der Entwicklung ist in Ordnung, diese sollten jedoch entweder deaktiviert oder entfernt werden, bevor ein Test eingecheckt wird.

Schreiben Sie gut strukturierten Diagnosecode

Jede Diagnoseausgabe, die bei einem Testfehler nützlich wäre, sollte Teil der regulären Testausgabe sein, anstatt auskommentiert, durch Präprozessor-Direktiven deaktiviert oder nur in Debug-Builds aktiviert zu werden. Wenn ein Test während der kontinuierlichen Integration fehlschlägt, kann es Ihnen viel Zeit sparen, wenn alle relevanten Diagnoseausgaben in den CI-Protokollen enthalten sind, anstatt den Diagnosecode erst zu aktivieren und den Test erneut auszuführen. Dies gilt insbesondere dann, wenn der Fehler auf einer Plattform auftrat, die Sie nicht auf Ihrem Desktop zur Verfügung haben.

Diagnosemeldungen in Tests sollten die Ausgabemechanismen von Qt verwenden, wie z. B. qDebug() und qWarning(), anstatt die Ausgabemechanismen stdio.h oder iostream.h. Letztere umgehen die Nachrichtenverarbeitung von Qt und verhindern, dass die Befehlszeilenoption „ -silent “ die Diagnosemeldungen unterdrückt. Dies könnte dazu führen, dass wichtige Fehlermeldungen in einer großen Menge an Debugging-Ausgaben verborgen bleiben.

Schreiben von testbarem Code

Die folgenden Abschnitte enthalten Richtlinien zum Schreiben von Code, der sich leicht testen lässt:

Abhängigkeiten auflösen

Die Idee von Unit-Tests besteht darin, jede Klasse isoliert zu verwenden. Da viele Klassen andere Klassen instanziieren, ist es nicht möglich, eine Klasse separat zu instanziieren. Daher sollten Sie eine Technik namens „Dependency Injection“ (Abhängigkeitsinjektion) verwenden, die die Erstellung von Objekten von deren Verwendung trennt. Eine Factory ist für den Aufbau von Objektbäumen zuständig. Andere Objekte manipulieren diese Objekte über abstrakte Schnittstellen.

Diese Technik eignet sich gut für datengesteuerte Anwendungen. Bei GUI-Anwendungen kann dieser Ansatz schwierig sein, da Objekte häufig erstellt und wieder freigegeben werden. Um das korrekte Verhalten von Klassen zu überprüfen, die von abstrakten Schnittstellen abhängen, kann Mocking eingesetzt werden. Siehe beispielsweise das Googletest Mocking (gMock)-Framework.

Alle Klassen in Bibliotheken kompilieren

In kleinen bis mittelgroßen Projekten listet ein Build-Skript in der Regel alle Quelldateien auf und kompiliert dann die ausführbare Datei in einem Durchgang. Das bedeutet, dass die Build-Skripte für die Tests die benötigten Quelldateien erneut auflisten müssen.

Es ist einfacher, die Quelldateien und die Header nur einmal in einem Skript aufzulisten, um eine statische Bibliothek zu erstellen. Anschließend wird die Funktion „ main() “ mit der statischen Bibliothek verknüpft, um die ausführbare Datei zu erstellen, und die Tests werden mit den statischen Bibliotheken verknüpft.

Bei Projekten, bei denen dieselben Quelldateien zum Erstellen mehrerer Programme verwendet werden, kann es sinnvoller sein, die gemeinsam genutzten Klassen in eine dynamisch verknüpfte (oder Shared-Object-)Bibliothek zu integrieren, die jedes Programm – einschließlich der Testprogramme – zur Laufzeit laden kann. Auch hier hilft die Unterbringung des kompilierten Codes in einer Bibliothek dabei, Doppelarbeit bei der Beschreibung zu vermeiden, welche Komponenten zur Erstellung der verschiedenen Programme kombiniert werden müssen.

Einrichtung von Testrechnern

In den folgenden Abschnitten werden häufige Probleme behandelt, die durch die Einrichtung von Testrechnern verursacht werden:

All diese Probleme lassen sich in der Regel durch den sinnvollen Einsatz von Virtualisierung lösen.

Bildschirmschoner

Bildschirmschoner können einige der Tests für GUI-Klassen stören und zu unzuverlässigen Testergebnissen führen. Bildschirmschoner sollten deaktiviert werden, um sicherzustellen, dass die Testergebnisse konsistent und zuverlässig sind.

Systemdialoge

Dialoge, die unerwartet vom Betriebssystem oder anderen laufenden Anwendungen angezeigt werden, können den Eingabefokus von Widgets, die an einem Autotest beteiligt sind, wegnehmen und so nicht reproduzierbare Fehler verursachen.

Beispiele für typische Probleme sind Dialoge mit Online-Update-Benachrichtigungen unter macOS, Fehlalarme von Virenscannern, geplante Aufgaben wie Updates von Virensignaturen, Software-Updates, die auf Workstations verteilt werden, sowie Chat-Programme, die Fenster ganz oben im Stapel anzeigen.

Bildschirmnutzung

Einige Tests nutzen den Bildschirm, die Maus und die Tastatur des Testrechners und können daher fehlschlagen, wenn der Rechner gleichzeitig für andere Zwecke genutzt wird oder wenn mehrere Tests parallel ausgeführt werden.

Das CI-System nutzt dedizierte Testrechner, um dieses Problem zu vermeiden. Wenn Sie jedoch keinen dedizierten Testrechner haben, können Sie dieses Problem möglicherweise lösen, indem Sie die Tests auf einem zweiten Bildschirm ausführen.

Unter Unix kann man die Tests auch auf einem verschachtelten oder virtuellen X-Server wie Xephyr ausführen. Um beispielsweise den gesamten Testsatz auf Xephyr auszuführen, führen Sie die folgenden Befehle aus:

Xephyr :1 -ac -screen 1920x1200 >/dev/null 2>&1 &
sleep 5
DISPLAY=:1 icewm >/dev/null 2>&1 &
cd tests/auto
make
DISPLAY=:1 make -k -j1 check

Benutzer von NVIDIA-Binärtreibern sollten beachten, dass Xephyr möglicherweise keine GLX-Erweiterungen bereitstellen kann. Das Erzwingen von Mesa libGL könnte Abhilfe schaffen:

export LD_PRELOAD=/usr/lib/mesa-diverted/x86_64-linux-gnu/libGL.so.1

Wenn die Tests jedoch auf Xephyr und dem echten X-Server mit unterschiedlichen libGL-Versionen ausgeführt werden, kann der QML-Festplatten-Cache zum Absturz der Tests führen. Um dies zu vermeiden, verwenden Sie QML_DISABLE_DISK_CACHE=1.

Alternativ können Sie das Offscreen-Plugin verwenden:

TESTARGS="-platform offscreen" make check -k -j1

Fenstermanager

Unter Unix erfordern mindestens zwei Autotests (tst_examples und tst_gestures) einen laufenden Fenstermanager. Wenn Sie diese Tests also unter einem verschachtelten X-Server ausführen, müssen Sie in diesem X-Server ebenfalls einen Fenstermanager ausführen.

Ihr Fenstermanager muss so konfiguriert sein, dass alle Fenster automatisch auf dem Bildschirm positioniert werden. Einige Fenstermanager, wie beispielsweise der Tab Window Manager (twm), verfügen über einen Modus zur manuellen Positionierung neuer Fenster, wodurch die Testsuite nicht ohne Benutzereingriff ausgeführt werden kann.

Hinweis: Der Tab Window Manager eignet sich nicht für die Ausführung der vollständigen Suite von Qt-Autotests, da der Autotest „ tst_gestures “ dazu führt, dass er seine Konfiguration vergisst und zur manuellen Fensterplatzierung zurückkehrt.

© 2026 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.