Entwicklung/CPP Unit Tests
TDF LibreOffice Document Liberation Project Community Blogs Weblate Nextcloud Redmine Ask LibreOffice Spende
CppUnit
LibreOffice verwendet das CppUnit Framework für seine C++ Unit Tests- CppUnit ist ein C++ port des JUnit Frameworks und wurde mehrfach verzweigt. LibreOffice verwendet seinen eigenen CppUnit-Zweig. Dieser ist aktuell der letzte aktive Zweig.
Xisco Faulis Präsentation von der FOSDEM 2021 LibreOffice QA - how to write your first test geht durch den Prozess der Testerstellung.
UnitTest Typen imLibreOffice Code
Es gibt viele UnitTest Typen in LibreOffice. Bei Unsicherheiten in der Verwendung bietet die LibreOffice Development Mailing List Hilfe, genauso wie der IRC - siehe "Sprich mit Entwicklern" auf https://de.libreoffice.org/community/developers/.
Eine Funktion / Methode testen
Nimm die Funktion / Methode und füttere sie mit verschiedenen Daten. Danach Überprüfe die Ergebnisse auf Richtigkeit.
Beispiel: test::oustring::StringConcat::checkConcat() in https://cgit.freedesktop.org/libreoffice/core/tree/sal/qa/rtl/strings/test_oustring_concat.cxx#n57
Import Test
Für einen Import Test braucht man zwei Dinge:
- ein Test Dokument - eine Mini-Version eines Dokumentes, das den Bug an erster Stelle hat. Am besten wird das Dokument aus einem frischen Dokument erzeugt, um vertrauliche Daten zu vermeiden. Prüfe ähnliche Tests in der Umgebung, in der das Dokument gespeichert wurde, üblicherweise in einem 'data/'-Unterverzeichnis.
- den Test selbst.
Der Import Test lädt als erstes das Dokument und testet dann das was gefixt wurde.
Beispiel: testFdo50665 in https://cgit.freedesktop.org/libreoffice/core/tree/sw/qa/extras/rtfimport/rtfimport.cxx#n606
Export Test
Ein Export Test erweitert den Import Test durch einen zweiten Re-Import:
- Beim ersten Mal wird das Dokument gelesen und dann mit dem Filter, den wir testen wollen, gespeichert.
- Beim zweiten Mal wird das gespeicherte Dokument wieder geladen, um testen zu können, ob das Speichern korrekt funktioniert hat.
Die Überprüfung des gespeicherten Ergebnisses kann genauso wie beim Import Test (siehe oben) passieren. Oder über die assertXPath Methode (siehe unten).
Beispiel: testDMLSolidfillAlpha in https://cgit.freedesktop.org/libreoffice/core/tree/sw/qa/extras/ooxmlexport/ooxmlexport6.cxx#n115
assertXPath Test
In vielen Fälle arbeiten wir mit XML:
- ODF und OOXML sind XML in einem Container.
- Wir dumpen Daten als XML (unterschiedlicher Debugging Output - wie ein dump im Writer Layout, EMF/WMF Structure, XShapes etc.).
Der Trick ist, die Daten als DOM zu laden und einige Attribute über einen XPath Ausdruck zu ergänzen.
Beispiel: testBehinddoc in https://cgit.freedesktop.org/libreoffice/core/tree/sw/qa/extras/ooxmlexport/ooxmlexport6.cxx#n610
EMF/WMF Beispiele: https://cgit.freedesktop.org/libreoffice/core/tree/emfio/qa/cppunit/wmf/wmfimporttest.cxx
Unit Tests, die ein Bitmap dumpen und das Ergebnis auswerten
EMF+ wird in EMF Kommentaren gespeichert und später aufbereitet. Um es testen zu können, muss es also als Bitmap gedumpt werden. Dann müssen die einzelnen Pixel auf korrekte Werte geprüft werden.
Um das Bitmap zuerst zu dumpen (um zu sehen, was genau überprüft werden soll), wird CPPCANVAS_DEBUG_EMFPLUS_DUMP_TO=/tmp/file.png exportiert und dann unter /tmp/file.png gespeichert, wenn der Unit Test ausgeführt wird.
Beispiel: https://cgit.freedesktop.org/libreoffice/core/tree/cppcanvas/qa/extras/emfplus/emfplus.cxx
Layout Dump Test
Das ist spezifisch Writer. Manchmal muss man testen, wie die Layout Maschine ein Dokument aufbereitet. - z. Bsp. soll ein Text auf der dritten Seite erscheinen, der zuvor auf der zweiten Seite war, oder so ähnlich.
Dieser Test importiert die Datei und schreibt dann den Dump als XML.
Beispiel: testFdo52052 in https://cgit.freedesktop.org/libreoffice/core/tree/sw/qa/extras/rtfimport/rtfimport.cxx#n755 .
XShape Test
Das kann recht einfach in chart2 oder Impress geschehen. Die Struktur der Ansicht (repräsentiert in einer XShapes Hirarchie) wird als XML gedumpt und kann entweder gegen einen Referenz-Dump abgeglichen werden (weiniger verlässlich - da die Dump Methode in Zukunft geändert werden kann) oder gegen den assert XPath geprüft werden (die modernere Methode).
Beispiel (Referenz): Chart2XShapeTest::testFdo75075() in https://cgit.freedesktop.org/libreoffice/core/tree/chart2/qa/extras/xshape/chart2xshape.cxx#n80
Beiepiel (assertXPath): Chart2XShapeTest::testTdf76649TrendLineBug() in https://cgit.freedesktop.org/libreoffice/core/tree/chart2/qa/extras/xshape/chart2xshape.cxx#n144
Test eines komplexen Szenarios
Es kann passieren, dass ein Fehler mehrere Schritte zur Reproduktion benötigt. Sogar dann ist er testbar, wenn die Prozedur verlässlich ist.
In so einem Fall ist es normalerweise ein Import-Test mit zusätzlich erforderlichen Schritten - wie einen Text auswählen, eine Aktion darauf anstossen etc.
Beispiel: testFdo37606Copy in https://cgit.freedesktop.org/libreoffice/core/tree/sw/qa/extras/odfimport/odfimport.cxx#n738
LibreOfficeKit (LOK) Test
Manche Funktionalität (wie ein echter Tastendruck) ist in der LibreOfficeKit API, aber nicht über UNO oder der internen API realisierbar (weil z. Bsp. in Writer die SwEditWin keine public Klasse vom Standpunkt der Sichtbarkeit aus gesehen ist).
Um sie zu verwenden, wird eine Referenz auf das UNO Objekt eines Dokumentes verwendet. Hier können alle Member Funktionen direkt aufgerufen werden.
Beispiel: testTdf89954 in https://cgit.freedesktop.org/libreoffice/core/tree/sw/qa/extras/uiwriter/uiwriter7.cxx#n2371
Performanz Tests
Diese Tests werden als Teil eines Performanztests ausgeführt. Sie erfordern das valgrind-dev packagage. Log-Files werden in workdir/CppunitTest/[testname]/.. geschrieben. Diese enthalten die valgrind logs.
Sie können ausgeführt werden, um eine zusammenfassende CSV-Datei unter Verwendung von bin/parse-perfcheck.py zu erzeugen.
Beispiel: sc/qa/perf/scperfobj.cxx
Neuen Text hinzufügen
Manchmal ist die Infrastruktur für die Unit-Tests noch nicht vorhanden. Dann hilft ein Blick auf eine Umgebung mit vielen Unit Tests zur Inspiration. Dann kann auch ein Unit Test wie folgt erzeugt werden.
eine Teststrategie entwerfen
LibreOffice kann in einem Debugger laufen. Dabei wird ein Breakpoint an einer interessanten Stelle bei der Änderung gesetzt werden. Dann wird in LibreOffice eingegeben, was den Fehler verursacht hat und kontrolliert, was die Ablaufverfolgung im Debugger anbietet. Schlußendlich wird im CPPUnit Test die Funktion angestossen analog zu dem, wie sie in der Ablaufverfolgung angestossen wurde.
Test Klassen definieren
Die Tests sollten in einem Unterverzeichnis von qa/
in einer Datei namens test_xxx.cxx
abgelegt werden. Zunächst wird die Klasse angelegt. Hier folgt ein Basis-Test-Skelett:
// copy here a header from the file TEMPLATE.SOURCECODE.HEADER in the root of the source tree.
#include "sal/config.h"
#include "sal/precppunit.hxx"
#include "cppunit/TestAssert.h"
#include "cppunit/TestFixture.h"
#include "cppunit/extensions/HelperMacros.h"
#include "cppunit/plugin/TestPlugIn.h"
namespace
{
/**
* Perform tests on MyClass.
*/
class MyClassTest : public CppUnit::TestFixture
{
private:
/**
* Tests capability Foo of the class.
*/
void testFoo();
// Adds code needed to register the test suite
CPPUNIT_TEST_SUITE(MyClassTest);
// Declares the method as a test to call
CPPUNIT_TEST(testFoo);
// End of test suite definition
CPPUNIT_TEST_SUITE_END();
};
void MyClassTest::testFoo()
{
CPPUNIT_ASSERT(true); // do the tests here
}
// Put the test suite in the registry
CPPUNIT_TEST_SUITE_REGISTRATION(MyClassTest);
} // namespace
CPPUNIT_PLUGIN_IMPLEMENT();
Die Klasse wird am besten für ein oder mehrere Funktionen und verschiedene Resourcen genutzt. Siehe das CppUnit Tutorial für weitere Einzelheiten zum Test mitt CPPUnit.
Testen während des Compile-Laufes
Jetzt ist die Test-Klasse fertig für die Übersetzung und den Start. Das heißt, die Datei CppunitTest_module_test.mk
(Maodul- und Test-Name sind an die aktuellen Gegebenheiten anzupassen) im Toplevel-(Modul-)Verzeichnis zu erzeugen, falls es nicht bereits existiert:
# copy here a header from the file TEMPLATE.SOURCECODE.HEADER in the root of the source tree.
$(eval $(call gb_CppunitTest_CppunitTest,module_test))
$(eval $(call gb_CppunitTest_add_exception_objects,module_test, \
module/qa/cppunit/test_xxx \
))
# add a list of all needed libraries here
$(eval $(call gb_CppunitTest_add_linked_libs,module_test, \
sal \
$(gb_STDLIBS) \
))
$(eval $(call gb_CppunitTest_set_include,module_test,\
$$(INCLUDE) \
-I$(OUTDIR)/inc \
))
Außerdem wird Module_module.mk
angepasst und folgendes ergänzt (oder zur Liste ergänzt, falls sie bereits existiert):
$(eval $(call gb_Module_add_check_targets,module,\
CppunitTest_module_test \
))
Mit make check
werden die Tests zusammengestellt und gestartet. Wenn die Zusammenstellung erfolgreich ist, startet der Test automatisch.
Sobald die Tests laufen brauchen nur noch die Fehler bereinigt werden, die sie hervorbringen.
Nie mehr als einen Test gleichzeitig laufen lassen
CPPUNIT_TEST_NAME ist die Umgebungsvariable, die gesetzt werden muss und den Namen der Tests enthält. Die Testnamen müssen voll qualifiziert sein, um erkannt zu werden.
Beispiel:
CPPUNIT_TEST_NAME="testOutlineODS testPrintRangeODS" make CppunitTest_sc_subsequent_filters_test2
Printing to stderr is hidden if the test passes and displayed if it fails.
If you use a single string as a value of CPPUNIT_TEST_NAME, that has to be a class name. If that class has multiple tests, then all tests inside that class will be ran. The CPPUNIT_TEST_FIXTURE macro always creates a class with a single test function.
You can discover the make targets by looking at the *.mk files in modules.