Qt Test Beste Praktiken
Wir empfehlen, dass Sie Qt Tests für Fehlerbehebungen und neue Funktionen hinzufügen. Bevor Sie versuchen, einen Fehler zu beheben, fügen Sie einen (idealerweise automatischen) Regressionstest hinzu, der vor der Fehlerbehebung fehlschlägt und nach der Fehlerbehebung erfolgreich ist. Fügen Sie bei der Entwicklung neuer Funktionen Tests hinzu, um zu überprüfen, ob diese wie vorgesehen funktionieren.
Die Einhaltung einer Reihe von Codierungsstandards erhöht die Wahrscheinlichkeit, dass Qt-Autotests in allen Umgebungen zuverlässig funktionieren. Zum Beispiel müssen einige Tests Daten von der Festplatte lesen. Wenn es keine Standards dafür gibt, wie dies zu geschehen hat, werden einige Tests nicht portabel sein. Ein Test, der davon ausgeht, dass sich die Testdaten im aktuellen Arbeitsverzeichnis befinden, funktioniert zum Beispiel nur bei einem In-Source-Build. In einem Shadow-Build (außerhalb des Quellverzeichnisses) wird der Test seine Daten nicht finden können.
Die folgenden Abschnitte enthalten Richtlinien für das Schreiben von Qt Tests:
- Allgemeine Grundsätze
- Verlässliche Tests schreiben
- Verbessern der Testausgabe
- Schreiben von testbarem Code
- Einrichten von Testmaschinen
Allgemeine Grundsätze
Die folgenden Abschnitte enthalten allgemeine Richtlinien für das Schreiben von Unit-Tests:
- Tests verifizieren
- Benennen Sie Testfunktionen mit aussagekräftigen Namen
- Schreiben Sie in sich geschlossene Testfunktionen
- Testen Sie den gesamten Stack
- Machen Sie Tests schnell vollständig
- Datengesteuertes Testen verwenden
- Abdeckungswerkzeuge verwenden
- Geeignete Mechanismen zum Ausschluss von Tests wählen
- Vermeiden Sie Q_ASSERT
Tests verifizieren
Schreiben und übergeben Sie Ihre Tests zusammen mit Ihrer Korrektur oder neuen Funktion in einem neuen Zweig. Sobald Sie fertig sind, können Sie den Zweig, auf dem Ihre Arbeit basiert, auschecken und dann die Testdateien für Ihre neuen Tests in diesen Zweig auschecken. So können Sie überprüfen, ob die Tests auf dem vorherigen Zweig fehlschlagen und somit tatsächlich einen Fehler abfangen oder eine neue Funktion testen.
Der Arbeitsablauf zur Behebung eines Fehlers in der Klasse QDateTime
könnte zum Beispiel wie folgt aussehen, wenn Sie das Versionskontrollsystem Git verwenden:
- Erstellen Sie einen Zweig für Ihre Korrektur und Ihren Test:
git checkout -b fix-branch 5.14
- Schreiben Sie einen Test und beheben Sie den Fehler.
- Erstellen und testen Sie sowohl die Korrektur als auch den neuen Test, um zu überprüfen, ob der neue Test mit der Korrektur funktioniert.
- 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
- Committen Sie die Korrektur und den Test in Ihren Zweig:
git commit -m 'Fix bug in QDateTime'
- Um zu überprüfen, ob der Test tatsächlich etwas abfängt, wofür Sie die Korrektur benötigten, checken Sie den Zweig aus, auf dem Ihr eigener Zweig basiert:
git checkout 5.14
- Checken Sie nur die Testdatei in den 5.14-Zweig aus:
git checkout fix-branch -- tests/auto/corelib/time/qdatetime/tst_qdatetime.cpp
Nur der Test befindet sich jetzt auf dem Fix-Zweig. Der Rest des Quellbaums ist immer noch auf 5.14.
- Erstellen Sie den Test und führen Sie ihn aus, um zu überprüfen, ob er auf 5.14 fehlschlägt und somit tatsächlich einen Fehler abfängt.
- Sie können nun zum Fix-Zweig zurückkehren:
git checkout fix-branch
- Alternativ dazu können Sie Ihren Arbeitsbaum auf einen sauberen Zustand auf 5.14 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 zu prüfen, ob die Änderung tatsächlich einen Test für ein Problem enthält, das sie 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. Die Namen geben den Lesern des Berichts einen ersten Hinweis darauf, was schief gelaufen ist.
Die Namen der Testfunktionen sollten deutlich machen, was die Funktion testen soll. Verwenden Sie nicht einfach den Bezeichner des Bug-Trackers, da die Bezeichner veraltet werden, wenn der Bug-Tracker ersetzt wird. Außerdem sind einige Bug-Tracker 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.
Auch beim Schreiben von datengesteuerten Tests sollten Sie den Testfällen beschreibende Namen geben, die angeben, auf welchen Aspekt der Funktionalität sie sich konzentrieren. Nummerieren Sie die Testfälle nicht einfach und verwenden Sie auch keine Bezeichnungen für die Fehlerverfolgung. Jemand, der die Testausgabe liest, wird keine Ahnung haben, was die Nummern oder Bezeichner bedeuten. Sie können einen Kommentar zu der Testreihe hinzufügen, der den Bug-Tracking-Bezeichner erwähnt, wenn er relevant ist. Es ist am besten, Leerzeichen und Zeichen zu vermeiden, die für die Kommandozeilen-Shells, auf denen Sie die Tests ausführen wollen, von Bedeutung sein könnten. Dadurch wird es einfacher, den Test und das Tag auf der Befehlszeile Ihres Testprogramms anzugeben - zum Beispiel, um einen Testlauf auf einen einzigen Testfall zu beschränken.
Schreiben Sie in sich geschlossene Testfunktionen
Innerhalb eines Testprogramms sollten die 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 mit tst_foo testname
selbständig ausführen.
Verwenden Sie Instanzen der zu testenden Klasse nicht in mehreren Tests wieder. Testinstanzen (z. B. Widgets) sollten keine Mitgliedsvariablen der Tests sein, sondern vorzugsweise auf dem Stack instanziiert werden, um eine ordnungsgemäße Bereinigung zu gewährleisten, auch wenn ein Test fehlschlägt, so dass sich die Tests nicht gegenseitig stören.
Testen Sie den gesamten Stack
Wenn eine API in Form von steckbaren oder plattformspezifischen Backends implementiert ist, die die Hauptarbeit leisten, sollten Sie Tests schreiben, die die Codepfade bis hinunter zu den Backends abdecken. Das Testen der API-Teile der oberen Schicht unter Verwendung eines Mock-Backends ist eine gute Möglichkeit, um Fehler in der API-Schicht von den Backends zu isolieren, ergänzt jedoch Tests, die die tatsächliche Implementierung mit realen Daten ausführen.
Schnelles Abschließen von Tests
Tests sollten keine Zeit verschwenden, indem sie sich unnötig wiederholen, unangemessen große Mengen an Testdaten verwenden oder unnötige Leerlaufzeiten einführen.
Dies gilt insbesondere für Unit-Tests, bei denen jede Sekunde zusätzlicher Ausführungszeit für Unit-Tests dazu führt, dass die CI-Tests eines Zweigs über mehrere Ziele hinweg länger dauern. Denken Sie daran, dass Unit-Tests nicht mit Last- und Zuverlässigkeitstests zu verwechseln sind, bei denen größere Mengen an Testdaten und längere Testläufe zu erwarten sind.
Benchmark-Tests, bei denen derselbe Test in der Regel mehrfach ausgeführt wird, sollten in einem separaten Verzeichnis tests/benchmarks
untergebracht 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 gefunden werden.
Die Verwendung eines datengesteuerten Tests, anstatt mehrere Elemente nacheinander in einem Test zu testen, erspart die Wiederholung von sehr ähnlichem Code und stellt sicher, dass spätere Fälle getestet werden, auch wenn frühere fehlschlagen. Außerdem werden systematische und einheitliche Tests gefördert, da auf jede Datenprobe die gleichen Tests angewendet werden.
Wenn ein Test datengesteuert ist, können Sie das Daten-Tag zusammen mit dem Namen der Testfunktion ( function:tag
) in der Befehlszeile des Tests angeben, um den Test nur für einen bestimmten Testfall und nicht für alle Testfälle der Funktion auszuführen. Dies kann entweder für ein globales Daten-Tag oder ein lokales Tag verwendet werden, das eine Zeile aus den eigenen Daten der Funktion identifiziert; Sie können diese sogar kombinieren ( function:global:local
).
Coverage-Tools verwenden
Verwenden Sie ein Abdeckungswerkzeug 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 ist es, spätere Regressionen zu erkennen, wenn der Code überarbeitet wird.
Geeignete Mechanismen zum Ausschluss von Tests auswählen
Es ist wichtig, einen geeigneten Mechanismus zu wählen, um nicht anwendbare Tests auszuschließen.
Verwenden Sie QSKIP(), um Fälle zu behandeln, in denen sich eine ganze Testfunktion zur Laufzeit als in der aktuellen Testumgebung unanwendbar erweist. Wenn nur ein Teil einer Testfunktion übersprungen werden soll, kann eine bedingte Anweisung verwendet werden, optional mit einem qDebug()
-Aufruf, 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, wenn dies möglich ist. Es verifiziert auch, dass das Problem immer noch existiert, und lässt den Betreuer des Codes wissen, wenn er es unwissentlich behebt, ein Vorteil, der auch bei Verwendung des Abort Flags gewonnen wird.
Testfunktionen oder Datenzeilen eines datengesteuerten Tests können auf bestimmte Plattformen beschränkt werden, oder auf bestimmte Funktionen, die mit #if
aktiviert werden. Achten Sie jedoch auf die moc-Beschränkungen, wenn Sie #if
zum Überspringen von Testfunktionen verwenden. Der moc
Präprozessor hat keinen Zugriff auf alle builtin
Makros des Compilers, die oft für die Feature-Erkennung des Compilers verwendet werden. Daher kann moc
ein anderes Ergebnis für eine Präprozessor-Bedingung erhalten, als der Rest Ihres Codes sieht. Dies kann dazu führen, dass moc
Metadaten für einen Test-Slot generiert, die der eigentliche Compiler überspringt, oder dass die Metadaten für einen Test-Slot, der tatsächlich in die Klasse kompiliert wird, weggelassen werden. Im ersten Fall wird der Test versuchen, einen Slot auszuführen, der nicht implementiert ist. Im zweiten Fall wird der Test nicht versuchen, einen Test-Slot auszuführen, obwohl er es sollte.
Wenn ein ganzes Testprogramm für eine bestimmte Plattform nicht anwendbar ist oder wenn eine bestimmte Funktion nicht aktiviert ist, ist es am besten, die Build-Konfiguration des übergeordneten Verzeichnisses zu verwenden, um die Erstellung des Tests zu vermeiden. Wenn zum Beispiel der Test tests/auto/gui/someclass
nicht für macOS gültig ist, verpacken Sie seine Aufnahme als Unterverzeichnis in tests/auto/gui/CMakeLists.txt
in eine Plattformprüfung:
if(NOT APPLE) add_subdirectory(someclass) endif
oder, wenn Sie qmake
verwenden, fügen Sie die folgende Zeile zu tests/auto/gui.pro
hinzu:
mac*: SUBDIRS -= someclass
Siehe auch Überspringen von Tests mit QSKIP.
Q_ASSERT vermeiden
Das Q_ASSERT Makro bewirkt, dass ein Programm abbricht, wenn die geltend gemachte Bedingung false
ist, aber nur, wenn die Software im Debug-Modus erstellt wurde. Sowohl in Release- als auch in Debug-and-Release-Builds bewirkt Q_ASSERT
nichts.
Q_ASSERT
sollte vermieden werden, weil sich Tests unterschiedlich verhalten, je nachdem, ob ein Debug-Build getestet wird, und weil ein Test sofort abbricht, alle verbleibenden Testfunktionen überspringt und unvollständige oder fehlerhafte Testergebnisse zurückgibt.
Es überspringt auch alle Abbruch- oder Aufräumarbeiten, die am Ende des Tests stattfinden sollten, und kann daher den Arbeitsbereich in einem unaufgeräumten Zustand hinterlassen, was zu Komplikationen bei weiteren Tests führen kann.
Anstelle von Q_ASSERT
sollten die Makrovarianten QCOMPARE() oder QVERIFY() verwendet werden. Sie führen dazu, dass der aktuelle Test einen Fehler meldet und abbricht, erlauben aber die Ausführung der übrigen Testfunktionen und die normale Beendigung des gesamten Testprogramms. QVERIFY2 Mit () kann sogar eine beschreibende Fehlermeldung in das Testprotokoll aufgenommen werden.
Verlässliche Tests schreiben
Die folgenden Abschnitte enthalten Richtlinien für das Schreiben zuverlässiger Tests:
- Vermeiden Sie Nebeneffekte in Verifikationsschritten
- Vermeiden Sie feste Zeitüberschreitungen
- Hüten Sie sich vor zeitabhängigem Verhalten
- Vermeiden Sie Bitmap-Erfassung und -Vergleich
Vermeiden von Nebeneffekten in Verifikationsschritten
Bei der Durchführung von Verifikationsschritten in einem Autotest mit QCOMPARE(), QVERIFY() usw. sollten Nebeneffekte vermieden werden. Nebeneffekte in Verifikationsschritten können einen Test schwer verständlich machen. Außerdem können sie einen Test auf eine Art und Weise unterbrechen, die schwer zu diagnostizieren ist, wenn der Test geändert wird, um QTRY_VERIFY(), QTRY_COMPARE() oder QBENCHMARK() zu verwenden. Diese können den übergebenen Ausdruck mehrfach ausführen und damit alle Nebeneffekte wiederholen.
Wenn Seiteneffekte unvermeidlich sind, muss sichergestellt werden, dass der vorherige Zustand am Ende der Testfunktion wiederhergestellt wird, auch wenn der Test fehlschlägt. Dies erfordert in der Regel die Verwendung einer RAII-Klasse (Resource Acquisition is Initialization), die den Zustand wiederherstellt, wenn die Funktion zurückkehrt, oder eine Methode cleanup()
. Fügen Sie den Wiederherstellungscode nicht einfach an das Ende des Tests. Wenn ein Teil des Tests fehlschlägt, wird dieser Code übersprungen und der vorherige Zustand wird nicht wiederhergestellt.
Vermeiden Sie feste Zeitüberschreitungen
Vermeiden Sie fest kodierte Timeouts, wie QTest::qWait(), um zu warten, bis bestimmte Bedingungen erfüllt sind. Erwägen Sie die Verwendung der Klasse QSignalSpy, der Makros QTRY_VERIFY() oder QTRY_COMPARE() oder der Klasse QSignalSpy
in Verbindung mit den Makrovarianten QTRY_
.
Die Funktion qWait()
kann verwendet werden, um eine Verzögerung für einen festen Zeitraum zwischen der Durchführung einer Aktion und dem Warten auf ein durch diese Aktion ausgelöstes asynchrones Verhalten festzulegen. Beispielsweise kann man den Zustand eines Widgets ändern und dann warten, bis das Widget neu gezeichnet wird. Solche Zeitüberschreitungen führen jedoch häufig zu Fehlern, wenn ein auf einem Arbeitsplatzrechner geschriebener Test auf einem Gerät ausgeführt wird, auf dem das erwartete Verhalten möglicherweise länger dauert, bis es abgeschlossen ist. Das Erhöhen des festen Timeouts auf einen Wert, der um ein Vielfaches größer ist als der, der auf der langsamsten Testplattform benötigt wird, ist keine gute Lösung, da es den Testlauf auf allen Plattformen verlangsamt, insbesondere bei tabellengesteuerten Tests.
Wenn der zu testende Code bei Abschluss des asynchronen Verhaltens Qt-Signale ausgibt, ist es besser, die Klasse QSignalSpy zu verwenden, um der Testfunktion mitzuteilen, dass der Verifizierungsschritt nun ausgeführt werden kann.
Wenn es keine Qt-Signale gibt, verwenden Sie die Makros QTRY_COMPARE()
und QTRY_VERIFY()
, die periodisch eine bestimmte Bedingung testen, bis sie wahr wird oder eine maximale Zeitüberschreitung erreicht ist. Diese Makros verhindern, dass der Test länger als nötig dauert, und vermeiden gleichzeitig Fehler, wenn die Tests auf Workstations geschrieben und später auf eingebetteten Plattformen ausgeführt werden.
Wenn es keine Qt-Signale gibt und Sie den Test als Teil der Entwicklung einer neuen API schreiben, sollten Sie überlegen, ob die API von einem zusätzlichen Signal profitieren könnte, das die Beendigung des asynchronen Verhaltens meldet.
Vorsicht vor zeitabhängigem Verhalten
Einige Teststrategien sind anfällig für das zeitabhängige Verhalten bestimmter Klassen, was zu Tests führen kann, die nur auf bestimmten Plattformen fehlschlagen oder keine konsistenten Ergebnisse liefern.
Ein Beispiel hierfür sind Texteingabe-Widgets, die oft einen blinkenden Cursor haben, der Vergleiche von erfassten Bitmaps erfolgreich oder nicht erfolgreich machen kann, je nach dem Zustand des Cursors bei der Erfassung der Bitmap. Dies wiederum kann von der Geschwindigkeit des Rechners abhängen, der den Test ausführt.
Beim Testen von Klassen, die ihren Zustand auf der Grundlage von Timer-Ereignissen ändern, muss das Timer-basierte Verhalten bei der Durchführung von Verifikationsschritten berücksichtigt werden. Aufgrund der Vielfalt des zeitabhängigen Verhaltens gibt es keine allgemeine Lösung für dieses Testproblem.
Für Texteingabe-Widgets sind mögliche Lösungen, das Blinken des Cursors zu deaktivieren (wenn die API diese Funktion bietet), zu warten, bis der Cursor in einem bekannten Zustand ist, bevor eine Bitmap erfasst wird (z. B. indem ein entsprechendes Signal abonniert wird, wenn die API eines bietet), oder den Bereich mit dem Cursor vom Bitmap-Vergleich auszuschließen.
Vermeiden von Bitmap-Erfassung und -Vergleich
Die Überprüfung von Testergebnissen durch Erfassen und Vergleichen von Bitmaps ist zwar manchmal notwendig, kann aber recht anfällig und arbeitsintensiv sein.
Zum Beispiel kann ein bestimmtes Widget auf verschiedenen Plattformen oder mit verschiedenen Widgetstilen unterschiedlich aussehen, so dass Referenz-Bitmaps möglicherweise mehrfach erstellt und dann in der Zukunft gepflegt werden müssen, wenn sich die von Qt unterstützten Plattformen weiterentwickeln. Ä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 Plattform erfordern würde.
Bitmap-Vergleiche können auch von Faktoren wie der Bildschirmauflösung des Testrechners, der Bittiefe, dem aktiven Thema, dem Farbschema, dem Widget-Stil, dem aktiven Gebietsschema (Währungssymbole, Textrichtung usw.), der Schriftgröße, den Transparenzeffekten und der Wahl des Fenstermanagers beeinflusst werden.
Verwenden Sie, wenn möglich, programmatische Mittel, wie z. B. die Überprüfung der Eigenschaften von Objekten und Variablen, anstatt Bitmaps zu erfassen und zu vergleichen.
Verbessern der Testausgabe
Die folgenden Abschnitte enthalten Richtlinien für die Erstellung lesbarer und hilfreicher Testausgaben:
- Testen Sie auf Warnungen
- Vermeiden Sie das Drucken von Debug-Meldungen aus Autotests
- Schreiben Sie gut strukturierten diagnostischen Code
Testen Sie auf Warnungen
Genau wie bei der Entwicklung Ihrer Software wird es Ihnen schwer fallen, eine Warnung zu bemerken, die ein Hinweis auf das Auftreten eines Fehlers ist, wenn die Testausgabe mit Warnungen überladen ist. Es ist daher ratsam, Ihre Testprotokolle regelmäßig auf Warnungen und andere ungewollte Ausgaben zu überprüfen und die Ursachen zu untersuchen. Wenn es sich um Anzeichen für einen Fehler handelt, können Sie dafür sorgen, dass die Warnungen einen Testfehler auslösen.
Wenn der zu testende Code Meldungen erzeugen soll, wie z.B. Warnungen über eine falsche Verwendung, ist es auch wichtig zu testen, dass er diese Meldungen auch erzeugt, wenn er verwendet wird. Sie können auf erwartete Meldungen des zu testenden Codes testen, die von qWarning(), qDebug(), qInfo() und anderen Programmen erzeugt werden, indem Sie QTest::ignoreMessage() verwenden. Dadurch wird überprüft, ob die Nachricht erzeugt wird, und sie wird aus der Ausgabe des Testlaufs herausgefiltert. Wenn die Nachricht nicht erzeugt wird, schlägt der Test fehl.
Wenn eine erwartete Nachricht nur ausgegeben wird, wenn Qt im Debug-Modus gebaut wurde, verwenden Sie QLibraryInfo::isDebugBuild(), um festzustellen, ob die Qt-Bibliotheken im Debug-Modus gebaut wurden. Die Verwendung von #ifdef QT_DEBUG
reicht nicht aus, da es Ihnen nur sagt, ob der Test im Debug-Modus gebaut wurde, und das garantiert nicht, dass die Qt-Bibliotheken auch im Debug-Modus gebaut wurden.
Ihre Tests können (seit Qt 6.3) überprüfen, dass sie keine Aufrufe von qWarning() auslösen, indem sie QTest::failOnWarning() aufrufen. Dies nimmt die Warnmeldung, auf die getestet werden soll, oder eine QRegularExpression, die mit Warnungen abgeglichen wird; wenn eine übereinstimmende Warnung erzeugt wird, wird sie gemeldet und führt zum Fehlschlagen des Tests. Ein Test, der überhaupt keine Warnungen erzeugen sollte, kann zum Beispiel QTest::failOnWarning(QRegularExpression(u".*"_s))
aufrufen, was auf jede beliebige Warnung zutrifft.
Sie können auch die Umgebungsvariable QT_FATAL_WARNINGS
setzen, damit Warnungen als schwerwiegende Fehler behandelt werden. Siehe qWarning() für Details; dies ist nicht spezifisch für Autotests. Wenn Warnungen sonst in den umfangreichen Testprotokollen verloren gehen würden, kann ein gelegentlicher Lauf mit dieser Umgebungsvariablen Ihnen helfen, auftretende Warnungen zu finden und zu beseitigen.
Vermeiden Sie das Drucken von Debug-Meldungen aus Autotests
Autotests sollten keine unbehandelten Warnungen oder Debug-Meldungen erzeugen. Dies ermöglicht es dem CI Gate, neue Warn- oder Debug-Meldungen als Testfehler zu behandeln.
Das Hinzufügen von Debug-Meldungen während der Entwicklung ist in Ordnung, aber diese sollten entweder deaktiviert oder entfernt werden, bevor ein Test eingecheckt wird.
Schreiben Sie gut strukturierten Diagnosecode
Jegliche diagnostische Ausgabe, die nützlich wäre, wenn ein Test fehlschlägt, sollte Teil der regulären Testausgabe sein und nicht auskommentiert, durch Präprozessor-Direktiven deaktiviert oder nur in Debug-Builds aktiviert werden. Wenn ein Test während der kontinuierlichen Integration fehlschlägt, kann die Verfügbarkeit aller relevanten Diagnoseausgaben in den CI-Protokollen viel Zeit sparen, verglichen mit der Aktivierung des Diagnosecodes und dem erneuten Testen. Vor allem, wenn der Fehler auf einer Plattform auftrat, die Sie nicht auf Ihrem Desktop haben.
Diagnosemeldungen in Tests sollten die Ausgabemechanismen von Qt verwenden, wie qDebug()
und qWarning()
, und nicht die Ausgabemechanismen stdio.h
oder iostream.h
. Letztere umgehen die Nachrichtenbehandlung von Qt und verhindern, dass die Kommandozeilenoption -silent
die Diagnosemeldungen unterdrückt. Dies könnte dazu führen, dass wichtige Fehlermeldungen in einer großen Menge von Debugging-Ausgaben versteckt werden.
Schreiben von testbarem Code
Die folgenden Abschnitte enthalten Richtlinien für das Schreiben von Code, der leicht zu testen ist:
Abhängigkeiten aufbrechen
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 verwenden, die die Objekterstellung von der Objektverwendung trennt. Eine Fabrik ist für den Aufbau von Objektbäumen verantwortlich. Andere Objekte manipulieren diese Objekte über abstrakte Schnittstellen.
Diese Technik eignet sich gut für datengesteuerte Anwendungen. Für GUI-Anwendungen kann dieser Ansatz schwierig sein, da Objekte häufig erstellt und zerstört werden. Um das korrekte Verhalten von Klassen, die von abstrakten Schnittstellen abhängen, zu überprüfen, kann Mocking eingesetzt werden. Siehe zum Beispiel 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. Dies 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 zur Erstellung einer statischen Bibliothek aufzulisten. Dann wird die Funktion main()
gegen die statische Bibliothek gelinkt, um die ausführbare Datei zu erstellen, und die Tests werden gegen die statischen Bibliotheken gelinkt.
Bei Projekten, in denen dieselben Quelldateien zur Erstellung mehrerer Programme verwendet werden, kann es sinnvoller sein, die gemeinsam genutzten Klassen in einer dynamisch gelinkten (oder gemeinsam genutzten Objekt-) Bibliothek zu erstellen, die jedes Programm, einschließlich der Testprogramme, zur Laufzeit laden kann. Auch hier hilft die Zusammenfassung des kompilierten Codes in einer Bibliothek, Doppelarbeit bei der Beschreibung der Komponenten zu vermeiden, die zur Erstellung der verschiedenen Programme kombiniert werden müssen.
Einrichten von Testmaschinen
In den folgenden Abschnitten werden häufig auftretende Probleme bei der Einrichtung von Testmaschinen behandelt:
Alle 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 unzuverlässige Testergebnisse verursachen. Bildschirmschoner sollten deaktiviert werden, um konsistente und zuverlässige Testergebnisse zu gewährleisten.
System-Dialoge
Unerwartet vom Betriebssystem oder anderen laufenden Anwendungen angezeigte Dialoge können den Eingabefokus von Widgets, die an einem Autotest beteiligt sind, stehlen, was zu nicht reproduzierbaren Fehlern führt.
Beispiele für typische Probleme sind Online-Update-Benachrichtigungsdialoge unter macOS, Fehlalarme von Virenscannern, geplante Aufgaben wie Virensignatur-Updates, Software-Updates, die an Workstations verteilt werden, und Chat-Programme, die Fenster über dem Stapel auftauchen lassen.
Display-Nutzung
Einige Tests verwenden den Bildschirm, die Maus und die Tastatur des Testrechners und können daher fehlschlagen, wenn der Rechner gleichzeitig für etwas anderes verwendet wird oder wenn mehrere Tests parallel laufen.
Das CI-System verwendet 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 laufen lassen. Führen Sie zum Beispiel die folgenden Befehle aus, um die gesamte Testreihe auf Xephyr auszuführen:
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 nicht in der Lage ist, GLX-Erweiterungen bereitzustellen. Das Erzwingen von Mesa libGL könnte helfen:
export LD_PRELOAD=/usr/lib/mesa-diverted/x86_64-linux-gnu/libGL.so.1
Wenn jedoch Tests auf Xephyr und dem echten X-Server mit unterschiedlichen libGL-Versionen ausgeführt werden, kann der QML-Plattencache die Tests zum Absturz bringen. Um dies zu vermeiden, verwenden Sie QML_DISABLE_DISK_CACHE=1
.
Alternativ können Sie auch das Offscreen-Plugin verwenden:
TESTARGS="-platform offscreen" make check -k -j1
Fenster-Manager
Unter Unix erfordern mindestens zwei Autotests (tst_examples
und tst_gestures
) die Ausführung eines Fenstermanagers. Wenn Sie diese Tests unter einem verschachtelten X-Server ausführen, müssen Sie daher auch einen Fenstermanager in diesem X-Server laufen lassen.
Ihr Fenstermanager muss so konfiguriert sein, dass er alle Fenster automatisch auf dem Bildschirm positioniert. Einige Fenstermanager, wie z. B. der Tab Window Manager (twm), verfügen über einen Modus zur manuellen Positionierung neuer Fenster, wodurch die Testsuite nicht ohne Benutzerinteraktion ausgeführt werden kann.
Hinweis: Der Tab Window Manager eignet sich nicht für die Durchführung der vollständigen Qt-Autotests, da der tst_gestures
Autotest dazu führt, dass er seine Konfiguration vergisst und zur manuellen Fensterplatzierung zurückkehrt.
© 2025 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.