Viktigt
Översättning är en gemenskapsinsats du kan gå med i. Den här sidan är för närvarande översatt till 100.00%.
6. Enhetstestning
Från och med november 2007 kräver vi att alla nya funktioner som går in i master ska åtföljas av ett enhetstest. Inledningsvis har vi begränsat detta krav till qgis_core, och vi kommer att utöka detta krav till andra delar av kodbasen när folk är bekanta med de procedurer för enhetstestning som beskrivs i de avsnitt som följer.
6.1. QGIS testramverk - en översikt
Enhetstestning utförs med hjälp av en kombination av QTestLib (Qt-testbiblioteket) och CTest (ett ramverk för att sammanställa och köra tester som en del av CMake-byggprocessen). Låt oss ta en överblick över processen innan vi går in på detaljerna:
Det finns en kod som du vill testa, t.ex. en klass eller funktion. Förespråkare för extrem programmering föreslår att koden inte ens ska vara skriven ännu när du börjar bygga dina tester, och sedan när du implementerar din kod kan du omedelbart validera varje ny funktionell del som du lägger till med ditt test. I praktiken kommer du förmodligen att behöva skriva tester för redan existerande kod i QGIS eftersom vi börjar med ett testramverk långt efter att mycket av applikationslogiken redan har implementerats.
Du skapar ett enhetstest. Detta sker under
<QGIS Source Dir>/tests/src/core
i fallet med core lib. Testet är i grunden en klient som skapar en instans av en klass och anropar några metoder på den klassen. Det kommer att kontrollera avkastningen från varje metod för att se till att den matchar det förväntade värdet. Om något av anropen misslyckas kommer enheten att misslyckas.Du inkluderar QtTestLib-makron i din testklass. Detta makro bearbetas av Qt meta object compiler (moc) och utökar din testklass till en körbar applikation.
Du lägger till ett avsnitt i CMakeLists.txt i din testkatalog som kommer att bygga ditt test.
Du måste se till att du har aktiverat
ENABLE_TESTING
i ccmake / cmakesetup. Detta kommer att säkerställa att dina tester faktiskt kompileras när du skriver make.Du kan lägga till testdata i<QGIS Source Dir>/tests/testdata om ditt test är datadrivet (t.ex. om du behöver ladda en shapefile). Dessa testdata ska vara så små som möjligt och när det är möjligt ska du använda de befintliga dataset som redan finns där. Dina tester ska aldrig modifiera dessa data på plats, utan snarare göra en tillfällig kopia någonstans om det behövs.
Du kompilerar dina källor och installerar. Detta gör du med den vanliga proceduren
make && (sudo) make install
.Du kör dina tester. Detta görs normalt genom att helt enkelt göra
make test
eftermake install
, men vi kommer att förklara andra tillvägagångssätt som ger mer finkornig kontroll över testkörningen.
Med den översikten i åtanke kommer vi att gå in lite mer i detalj. Vi har redan gjort mycket av konfigurationen åt dig i CMake och på andra ställen i källträdet, så allt du behöver göra är de enkla bitarna - att skriva enhetstester!
6.2. Skapa ett enhetstest
Att skapa ett enhetstest är enkelt - vanligtvis gör du detta genom att bara skapa en enda .cpp
-fil (ingen .h
-fil används) och implementera alla dina testmetoder som privata metoder som returnerar void. Vi kommer att använda en enkel testklass för QgsRasterLayer
i hela avsnittet som följer för att illustrera. Enligt konvention kommer vi att namnge vårt test med samma namn som klassen de testar men prefix med ’Test’. Så vår testimplementering går i en fil som heter testqgsrasterlayer.cpp
och själva klassen kommer att vara TestQgsRasterLayer
. Först lägger vi till vår standard copyright banner:
/***************************************************************************
testqgsvectorfilewriter.cpp
--------------------------------------
Date : Friday, Jan 27, 2015
Copyright: (C) 2015 by Tim Sutton
Email: [email protected]
***************************************************************************
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
***************************************************************************/
Därefter börjar vi med de inkluderingar som behövs för de tester vi planerar att köra. Det finns en speciell inkludering som alla tester bör ha:
#include <QtTest/QtTest>
Utöver det fortsätter du bara att implementera din klass som vanligt och drar in de rubriker du behöver:
//Qt includes...
#include <QObject>
#include <QString>
#include <QObject>
#include <QApplication>
#include <QFileInfo>
#include <QDir>
//qgis includes...
#include <qgsrasterlayer.h>
#include <qgsrasterbandstats.h>
#include <qgsapplication.h>
Eftersom vi kombinerar både klassdeklaration och implementation i en enda fil kommer klassdeklarationen härnäst. Vi börjar med vår doxygen-dokumentation. Varje testfall bör vara ordentligt dokumenterat. Vi använder doxygen ingroup-direktivet så att alla UnitTests visas som en modul i den genererade Doxygen-dokumentationen. Efter det kommer en kort beskrivning av enhetstestet och klassen måste ärva från QObject och innehålla Q_OBJECT-makrot.
/** \ingroup UnitTests
* This is a unit test for the QgsRasterLayer class.
*/
class TestQgsRasterLayer: public QObject
{
Q_OBJECT
Alla våra testmetoder är implementerade som privata slots. QtTest-ramverket kommer sekventiellt att anropa varje privat slot-metod i testklassen. Det finns fyra ”speciella” metoder som om de implementeras kommer att anropas i början av enhetstestet (initTestCase
), i slutet av enhetstestet (cleanupTestCase
). Innan varje testmetod anropas kommer metoden init()
att anropas och efter att varje testmetod har anropats anropas metoden cleanup()
. Dessa metoder är praktiska eftersom de gör att du kan allokera och rensa resurser innan du kör varje test och testenheten som helhet.
private slots:
// will be called before the first testfunction is executed.
void initTestCase();
// will be called after the last testfunction was executed.
void cleanupTestCase(){};
// will be called before each testfunction is executed.
void init(){};
// will be called after every testfunction.
void cleanup();
Sedan kommer dina testmetoder, som alla inte ska ta några parametrar och ska returnera void. Metoderna kommer att anropas i deklarationsordning. Här implementerar vi två metoder som illustrerar två olika typer av testning.
I det första fallet vill vi i allmänhet testa om de olika delarna av klassen fungerar, Vi kan använda en funktionell testmetod. Återigen skulle extrema programmerare förespråka att skriva dessa tester innan klassen implementeras. När du sedan arbetar dig igenom din klassimplementering kör du iterativt dina enhetstester. Fler och fler testfunktioner bör slutföras framgångsrikt när ditt klassimplementeringsarbete fortskrider, och när hela enhetstestet passerar är din nya klass klar och är nu komplett med ett repeterbart sätt att validera den.
Vanligtvis täcker dina enhetstester bara klassens publika API, och du behöver normalt inte skriva tester för accessorer och mutatorer. Om det skulle hända att en accessor eller mutator inte fungerar som förväntat implementerar man normalt ett regressionstest för att kontrollera detta.
//
// Functional Testing
//
/** Check if a raster is valid. */
void isValid();
// more functional tests here ...
6.2.1. Implementering av ett regressionstest
Därefter implementerar vi våra regressionstester. Regressionstester bör implementeras för att replikera förhållandena för en viss bugg. Till exempel:
Vi fick en rapport via e-post om att cellantalet per raster var felaktigt med 1, vilket påverkade all statistik för rasterbanden.
Vi har öppnat en felrapport (ticket #832)
Vi skapade ett regressionstest som replikerade felet med hjälp av en liten testdataset (en 10x10 raster).
Vi körde testet och kontrollerade att det verkligen misslyckades (cellantalet var 99 i stället för 100).
Sedan gick vi för att fixa buggen och göra om enhetstestet och regressionstestet godkändes. Vi begick regressionstestet tillsammans med buggfixen. Om någon bryter mot detta i källkoden igen i framtiden kan vi nu omedelbart identifiera att koden har regresserat.
Ännu bättre: innan vi gör några ändringar i framtiden kan vi genom att köra våra tester se till att ändringarna inte får oväntade bieffekter - som att befintlig funktionalitet bryts.
Det finns ytterligare en fördel med regressionstester - de kan spara tid. Om du någonsin har åtgärdat en bugg som innebar att du gjorde ändringar i källan och sedan körde applikationen och utförde en rad invecklade steg för att replikera problemet, kommer det att vara omedelbart uppenbart att om du helt enkelt implementerar ditt regressionstest innan du åtgärdar buggen kan du automatisera testningen för bugglösning på ett effektivt sätt.
För att genomföra ditt regressionstest bör du följa namnkonventionen regression<TicketID> för dina testfunktioner. Om det inte finns någon biljett för regressionen bör du först skapa en. Genom att använda detta tillvägagångssätt kan den person som kör ett misslyckat regressionstest enkelt gå och ta reda på mer information.
//
// Regression Testing
//
/** This is our second test case...to check if a raster
* reports its dimensions properly. It is a regression test
* for ticket #832 which was fixed with change r7650.
*/
void regression832();
// more regression tests go here ...
Slutligen kan du i din testklassdeklaration privat deklarera alla datamedlemmar och hjälpmetoder som ditt enhetstest kan behöva. I vårt fall kommer vi att deklarera ett QgsRasterLayer *
som kan användas av någon av våra testmetoder. Rasterlagret skapas i funktionen initTestCase()
som körs före alla andra tester, och förstörs sedan med cleanupTestCase()
som körs efter alla tester. Genom att deklarera hjälpmetoder (som kan anropas av olika testfunktioner) privat kan du se till att de inte körs automatiskt av den QTest-körbara fil som skapas när vi kompilerar vårt test.
private:
// Here we have any data structures that may need to
// be used in many test cases.
QgsRasterLayer * mpLayer;
};
Det avslutar vår klassdeklaration. Implementationen är helt enkelt inlined i samma fil längre ner. Först våra init- och cleanup-funktioner:
void TestQgsRasterLayer::initTestCase()
{
// init QGIS's paths - true means that all paths will be inited from prefix
QString qgisPath = QCoreApplication::applicationDirPath ();
QgsApplication::setPrefixPath(qgisPath, TRUE);
#ifdef Q_OS_LINUX
QgsApplication::setPkgDataPath(qgisPath + "/../share/qgis");
#endif
//create some objects that will be used in all tests...
std::cout << "PrefixPATH: " << QgsApplication::prefixPath().toLocal8Bit().data() << std::endl;
std::cout << "PluginPATH: " << QgsApplication::pluginPath().toLocal8Bit().data() << std::endl;
std::cout << "PkgData PATH: " << QgsApplication::pkgDataPath().toLocal8Bit().data() << std::endl;
std::cout << "User DB PATH: " << QgsApplication::qgisUserDbFilePath().toLocal8Bit().data() << std::endl;
//create a raster layer that will be used in all tests...
QString myFileName (TEST_DATA_DIR); //defined in CmakeLists.txt
myFileName = myFileName + QDir::separator() + "tenbytenraster.asc";
QFileInfo myRasterFileInfo ( myFileName );
mpLayer = new QgsRasterLayer ( myRasterFileInfo.filePath(),
myRasterFileInfo.completeBaseName() );
}
void TestQgsRasterLayer::cleanupTestCase()
{
delete mpLayer;
}
Ovanstående init-funktion illustrerar ett par intressanta saker.
Vi behövde manuellt ställa in QGIS-applikationens datasökväg så att resurser som
srs.db
kan hittas på rätt sätt.För det andra är detta ett datadrivet test så vi behövde tillhandahålla ett sätt att generiskt lokalisera filen
tenbytenraster.asc
. Detta uppnåddes genom att använda kompilatorns definitionTEST_DATA_PATH
. Definitionen skapas i konfigurationsfilenCMakeLists.txt
under<QGIS Source Root>/tests/CMakeLists.txt
och är tillgänglig för alla QGIS-enhetstester. Om du behöver testdata för ditt test ska du överföra dem till<QGIS Source Root>/tests/testdata
. Du bör bara lägga in mycket små dataset här. Om ditt test behöver modifiera testdata bör det först göra en kopia av det.
Qt tillhandahåller också några andra intressanta mekanismer för datadriven testning, så om du är intresserad av att veta mer om ämnet kan du läsa Qt-dokumentationen.
Låt oss sedan titta på vårt funktionella test. Testet isValid()
kontrollerar helt enkelt om rasterlagret laddades korrekt i initTestCase. QVERIFY är ett Qt-makro som du kan använda för att utvärdera ett testvillkor. Det finns några andra makron som Qt tillhandahåller för användning i dina tester, t.ex:
QCOMPARE ( actual, expected )
QEXPECT_FAIL ( dataIndex, comment, mode )
QFAIL ( message )
QFETCH ( type, name )
QSKIP ( description, mode )
QTEST ( actual, testElement )
QTEST_APPLESS_MAIN ( TestClass )
QTEST_MAIN ( TestClass )
QTEST_NOOP_MAIN ()
QVERIFY2 ( condition, message )
QVERIFY ( condition )
QWARN ( message )
Vissa av dessa makron är endast användbara när du använder Qt-ramverket för datadriven testning (se Qt-dokumenten för mer information).
void TestQgsRasterLayer::isValid()
{
QVERIFY ( mpLayer->isValid() );
}
Normalt bör dina funktionella tester täcka alla funktioner i dina klassers offentliga API där det är möjligt. Med våra funktionella tester ur vägen kan vi titta på vårt regressionstest exempel.
Eftersom problemet i bugg #832 är ett felaktigt rapporterat cellantal, är det bara att använda QVERIFY för att kontrollera att cellantalet motsvarar det förväntade värdet när vi skriver vårt test:
void TestQgsRasterLayer::regression832()
{
QVERIFY ( mpLayer->getRasterXDim() == 10 );
QVERIFY ( mpLayer->getRasterYDim() == 10 );
// regression check for ticket #832
// note getRasterBandStats call is base 1
QVERIFY ( mpLayer->getRasterBandStats(1).elementCountInt == 100 );
}
När alla testfunktioner är implementerade finns det en sista sak vi måste lägga till i vår testklass:
QTEST_MAIN(TestQgsRasterLayer)
#include "testqgsrasterlayer.moc"
Syftet med dessa två rader är att signalera till Qt’s moc att detta är ett QtTest (det kommer att generera en main-metod som i sin tur anropar varje testfunktion. Den sista raden är include för de källor som genereras av MOC. Du bör ersätta testqgsrasterlayer
med namnet på din klass i gemener.
6.3. Jämförelse av bilder för renderingstester
Rendering av bilder i olika miljöer kan ge subtila skillnader på grund av plattformsspecifika implementeringar (t.ex. olika algoritmer för rendering av teckensnitt och antialiasing), vilka teckensnitt som finns tillgängliga i systemet och andra oklara orsaker.
När ett renderingstest körs på Travis och misslyckas ska du leta efter dash-länken längst ner i Travis-loggen. Den här länken tar dig till en cdash-sida där du kan se de renderade bilderna jämfört med de förväntade bilderna, tillsammans med en ”skillnadsbild” som i rött markerar alla pixlar som inte matchade referensbilden.
QGIS enhetstestsystem har stöd för att lägga till ”mask”-bilder, som används för att ange när en renderad bild kan skilja sig från referensbilden. En maskbild är en bild (med samma namn som referensbilden, men med suffixet _mask.png) som ska ha samma dimensioner som referensbilden. I en maskbild anger pixelvärdena hur mycket den enskilda pixeln kan skilja sig från referensbilden, så en svart pixel anger att pixeln i den renderade bilden exakt måste matcha samma pixel i referensbilden. En pixel med RGB 2, 2, 2 betyder att den återgivna bilden kan variera med upp till 2 i sina RGB-värden från referensbilden, och en helt vit pixel (255, 255, 255) betyder att pixeln i praktiken ignoreras när man jämför den förväntade och den återgivna bilden.
Ett verktygsskript för att generera maskbilder finns tillgängligt som scripts/generate_test_mask_image.py
. Det här skriptet används genom att du skickar sökvägen till en referensbild (t.ex. tests/testdata/control_images/annotations/expected_annotation_fillstyle/expected_annotation_fillstyle.png
) och sökvägen till din renderade bild.
T.ex.
scripts/generate_test_mask_image.py tests/testdata/control_images/annotations/expected_annotation_fillstyle/expected_annotation_fillstyle.png /tmp/path_to_rendered_image.png
Du kan förkorta sökvägen till referensbilden genom att skicka en del av testnamnet istället, t.ex.
scripts/generate_test_mask_image.py annotation_fillstyle /tmp/path_to_rendered_image.png
(Den här genvägen fungerar bara om en enda matchande referensbild hittas. Om flera matchningar hittas måste du ange den fullständiga sökvägen till referensbilden)
Skriptet accepterar också http-url:er för den renderade bilden, så att du direkt kan kopiera en url för den renderade bilden från cdash-résultatsidan och skicka den till skriptet.
Var försiktig när du genererar maskbilder - du bör alltid visa den genererade maskbilden och granska eventuella vita områden i bilden. Eftersom dessa pixlar ignoreras bör du se till att dessa vita områden inte täcker några viktiga delar av referensbilden - annars blir ditt enhetstest meningslöst!
På samma sätt kan du manuellt ”sudda ut” delar av masken om du medvetet vill utesluta dem från testet. Detta kan vara användbart t.ex. för tester som blandar symbol- och textrendering (t.ex. legendtester), där enhetstestet inte är utformat för att testa den renderade texten och du inte vill att testet ska påverkas av skillnader i textrendering mellan olika plattformar.
För att jämföra bilder i QGIS enhetstester bör du använda klassen QgsMultiRenderChecker
eller en av dess underklasser.
Här är några tips för att förbättra testernas robusthet:
Inaktivera antialiasing om du kan, eftersom detta minimerar skillnaderna i rendering mellan olika plattformar.
Se till att dina referensbilder är ”tjocka”… dvs. inte har 1 px breda linjer eller andra små detaljer, och använd stora, feta teckensnitt (14 punkter eller mer rekommenderas).
Ibland genererar tester bilder med något olika storlek (t.ex. test för rendering av teckenförklaringar, där bildstorleken är beroende av teckensnittets renderingsstorlek - som kan variera mellan olika plattformar). För att ta hänsyn till detta kan du använda
QgsMultiRenderChecker::setSizeTolerance()
och ange det maximala antalet pixlar som den renderade bildens bredd och höjd får skilja sig från referensbilden.Använd inte transparenta bakgrunder i referensbilder (CDash stöder inte sådana). Använd istället
QgsMultiRenderChecker::drawBackground()
för att rita ett rutmönster för referensbildens bakgrund.När teckensnitt krävs ska du använda det teckensnitt som anges i
QgsFontUtils::standardTestFontFamily()
(”QGIS Vera Sans”).
Om travis rapporterar fel för nya bilder (t.ex. på grund av skillnader i antialiasing eller teckensnitt) kan skriptet parse_dash_results.py hjälpa dig när du uppdaterar de lokala testmaskerna.
6.4. Lägga till ditt enhetstest i CMakeLists.txt
Att lägga till ditt enhetstest till byggsystemet är helt enkelt en fråga om att redigera :filen:`CMakeLists.txt` i testkatalogen, klona ett av de befintliga testblocken och sedan ersätta ditt testklassnamn i det. Till exempel:
# QgsRasterLayer test
ADD_QGIS_TEST(rasterlayertest testqgsrasterlayer.cpp)
6.4.1. Makrot ADD_QGIS_TEST förklarat
Vi ska kort gå igenom dessa rader för att förklara vad de gör, men om du inte är intresserad kan du bara göra det steg som förklaras i avsnittet ovan.
MACRO (ADD_QGIS_TEST testname testsrc)
SET(qgis_${testname}_SRCS ${testsrc} ${util_SRCS})
SET(qgis_${testname}_MOC_CPPS ${testsrc})
QT4_WRAP_CPP(qgis_${testname}_MOC_SRCS ${qgis_${testname}_MOC_CPPS})
ADD_CUSTOM_TARGET(qgis_${testname}moc ALL DEPENDS ${qgis_${testname}_MOC_SRCS})
ADD_EXECUTABLE(qgis_${testname} ${qgis_${testname}_SRCS})
ADD_DEPENDENCIES(qgis_${testname} qgis_${testname}moc)
TARGET_LINK_LIBRARIES(qgis_${testname} ${QT_LIBRARIES} qgis_core)
SET_TARGET_PROPERTIES(qgis_${testname}
PROPERTIES
# skip the full RPATH for the build tree
SKIP_BUILD_RPATHTRUE
# when building, use the install RPATH already
# (so it doesn't need to relink when installing)
BUILD_WITH_INSTALL_RPATH TRUE
# the RPATH to be used when installing
INSTALL_RPATH ${QGIS_LIB_DIR}
# add the automatically determined parts of the RPATH
# which point to directories outside the build tree to the install RPATH
INSTALL_RPATH_USE_LINK_PATH true)
IF (APPLE)
# For macOS, the executable must be at the root of the bundle's executable folder
INSTALL(TARGETS qgis_${testname} RUNTIME DESTINATION ${CMAKE_INSTALL_PREFIX})
ADD_TEST(qgis_${testname} ${CMAKE_INSTALL_PREFIX}/qgis_${testname})
ELSE (APPLE)
INSTALL(TARGETS qgis_${testname} RUNTIME DESTINATION ${CMAKE_INSTALL_PREFIX}/bin)
ADD_TEST(qgis_${testname} ${CMAKE_INSTALL_PREFIX}/bin/qgis_${testname})
ENDIF (APPLE)
ENDMACRO (ADD_QGIS_TEST)
Låt oss titta lite mer i detalj på de enskilda raderna. Först definierar vi listan med källor för vårt test. Eftersom vi bara har en källfil (enligt den metodik som beskrivs ovan där klassdeklaration och definition finns i samma fil) är det ett enkelt uttalande:
SET(qgis_${testname}_SRCS ${testsrc} ${util_SRCS})
Eftersom vår testklass måste köras genom Qt:s metaobjektkompilator (moc) måste vi också lägga till ett par rader för att det ska ske:
SET(qgis_${testname}_MOC_CPPS ${testsrc})
QT4_WRAP_CPP(qgis_${testname}_MOC_SRCS ${qgis_${testname}_MOC_CPPS})
ADD_CUSTOM_TARGET(qgis_${testname}moc ALL DEPENDS ${qgis_${testname}_MOC_SRCS})
Därefter talar vi om för cmake att den måste skapa en körbar fil av testklassen. Kom ihåg att vi i föregående avsnitt på den sista raden i klassimplementeringen inkluderade moc-utgångarna direkt i vår testklass, så det kommer att ge den (bland annat) en main-metod så att klassen kan kompileras som en körbar fil:
ADD_EXECUTABLE(qgis_${testname} ${qgis_${testname}_SRCS})
ADD_DEPENDENCIES(qgis_${testname} qgis_${testname}moc)
Därefter måste vi specificera eventuella biblioteksberoenden. För närvarande har klasser implementerats med ett övergripande QT_LIBRARIES-beroende, men vi kommer att arbeta för att ersätta det med de specifika Qt-bibliotek som varje klass bara behöver. Naturligtvis måste du också länka till de relevanta qgis-biblioteken som krävs av ditt enhetstest.
TARGET_LINK_LIBRARIES(qgis_${testname} ${QT_LIBRARIES} qgis_core)
Därefter säger vi till cmake att installera testerna på samma plats som qgis-binärfilerna själva. Detta är något vi planerar att ta bort i framtiden så att testerna kan köras direkt inifrån källträdet.
SET_TARGET_PROPERTIES(qgis_${testname}
PROPERTIES
# skip the full RPATH for the build tree
SKIP_BUILD_RPATHTRUE
# when building, use the install RPATH already
# (so it doesn't need to relink when installing)
BUILD_WITH_INSTALL_RPATH TRUE
# the RPATH to be used when installing
INSTALL_RPATH ${QGIS_LIB_DIR}
# add the automatically determined parts of the RPATH
# which point to directories outside the build tree to the install RPATH
INSTALL_RPATH_USE_LINK_PATH true)
IF (APPLE)
# For macOS, the executable must be at the root of the bundle's executable folder
INSTALL(TARGETS qgis_${testname} RUNTIME DESTINATION ${CMAKE_INSTALL_PREFIX})
ADD_TEST(qgis_${testname} ${CMAKE_INSTALL_PREFIX}/qgis_${testname})
ELSE (APPLE)
INSTALL(TARGETS qgis_${testname} RUNTIME DESTINATION ${CMAKE_INSTALL_PREFIX}/bin)
ADD_TEST(qgis_${testname} ${CMAKE_INSTALL_PREFIX}/bin/qgis_${testname})
ENDIF (APPLE)
Slutligen använder ovanstående ADD_TEST
för att registrera testet med cmake / ctest. Det är här den bästa magin händer - vi registrerar klassen med ctest. Om du minns i den översikt vi gav i början av detta avsnitt använder vi både QtTest och CTest tillsammans. För att sammanfatta, QtTest lägger till en huvudmetod till din testenhet och hanterar anrop av dina testmetoder inom klassen. Det ger också några makron som QVERIFY
som du kan använda för att testa om testerna misslyckas med hjälp av villkor. Utdata från ett QtTest-enhetstest är en körbar fil som du kan köra från kommandoraden. Men när du har en svit med tester och du vill köra varje körbar i tur och ordning, och ännu bättre integrera körtester i byggprocessen, är CTest det vi använder.
6.5. Bygga ditt enhetstest
För att bygga enhetstestet behöver du bara se till att ENABLE_TESTS=true
i cmake-konfigurationen. Det finns två sätt att göra detta på:
Kör
ccmake ..
( ellercmakesetup ..
under Windows) och sätt interaktivt flagganENABLE_TESTS
tillON
.Lägg till en kommandoradsflagga till cmake, t.ex.
cmake -DENABLE_TESTS=true ..`
Utöver detta är det bara att bygga QGIS som vanligt och testerna bör också byggas.
6.6. Kör dina tester
Det enklaste sättet att köra testerna är att göra det som en del av den normala byggprocessen:
make && make install && make test
Kommandot make test
kommer att anropa CTest som kommer att köra varje test som registrerades med hjälp av ADD_TEST CMake-direktivet som beskrivs ovan. Typisk utdata från make test
kommer att se ut så här:
Running tests...
Start processing tests
Test project /Users/tim/dev/cpp/qgis/build
## 13 Testing qgis_applicationtest***Exception: Other
## 23 Testing qgis_filewritertest *** Passed
## 33 Testing qgis_rasterlayertest*** Passed
## 0 tests passed, 3 tests failed out of 3
The following tests FAILED:
## 1- qgis_applicationtest (OTHER_FAULT)
Errors while running CTest
make: *** [test] Error 8
Om ett test misslyckas kan du använda kommandot ctest för att undersöka närmare varför det misslyckades. Använd alternativet -R
för att ange en regex för vilka tester du vill köra och -V
för att få utförliga utdata:
$ ctest -R appl -V
Start processing tests
Test project /Users/tim/dev/cpp/qgis/build
Constructing a list of tests
Done constructing a list of tests
Changing directory into /Users/tim/dev/cpp/qgis/build/tests/src/core
## 13 Testing qgis_applicationtest
Test command: /Users/tim/dev/cpp/qgis/build/tests/src/core/qgis_applicationtest
********* Start testing of TestQgsApplication *********
Config: Using QTest library 4.3.0, Qt 4.3.0
PASS : TestQgsApplication::initTestCase()
PrefixPATH: /Users/tim/dev/cpp/qgis/build/tests/src/core/../
PluginPATH: /Users/tim/dev/cpp/qgis/build/tests/src/core/..//lib/qgis
PkgData PATH: /Users/tim/dev/cpp/qgis/build/tests/src/core/..//share/qgis
User DB PATH: /Users/tim/.qgis/qgis.db
PASS : TestQgsApplication::getPaths()
PrefixPATH: /Users/tim/dev/cpp/qgis/build/tests/src/core/../
PluginPATH: /Users/tim/dev/cpp/qgis/build/tests/src/core/..//lib/qgis
PkgData PATH: /Users/tim/dev/cpp/qgis/build/tests/src/core/..//share/qgis
User DB PATH: /Users/tim/.qgis/qgis.db
QDEBUG : TestQgsApplication::checkTheme() Checking if a theme icon exists:
QDEBUG : TestQgsApplication::checkTheme()
/Users/tim/dev/cpp/qgis/build/tests/src/core/..//share/qgis/themes/default//mIconProjectionDisabled.png
FAIL!: TestQgsApplication::checkTheme() '!myPixmap.isNull()' returned FALSE. ()
Loc: [/Users/tim/dev/cpp/qgis/tests/src/core/testqgsapplication.cpp(59)]
PASS : TestQgsApplication::cleanupTestCase()
Totals: 3 passed, 1 failed, 0 skipped
********* Finished testing of TestQgsApplication *********
-- Process completed
***Failed
## 0 tests passed, 1 tests failed out of 1
The following tests FAILED:
## 1- qgis_applicationtest (Failed)
Errors while running CTest
6.6.1. Körning av enskilda tester
C++-tester är vanliga applikationer. Du kan köra dem från build-mappen som vilken körbar fil som helst.
$ ./output/bin/qgis_dxfexporttest
********* Start testing of TestQgsDxfExport *********
Config: Using QtTest library 5.12.5, Qt 5.12.5 (x86_64-little_endian-lp64 shared (dynamic) release build; by GCC 9.2.1 20190827 (Red Hat 9.2.1-1))
PASS : TestQgsDxfExport::initTestCase()
PASS : TestQgsDxfExport::testPoints()
PASS : TestQgsDxfExport::testLines()
...
Totals: 19 passed, 4 failed, 0 skipped, 0 blacklisted, 612ms
********* Finished testing of TestQgsDxfExport *********
Dessa tester tar också kommandoradsargument. Detta gör det möjligt att köra en specifik delmängd av testerna:
$ ./output/bin/qgis_dxfexporttest testPoints
********* Start testing of TestQgsDxfExport *********
Config: Using QtTest library 5.12.5, Qt 5.12.5 (x86_64-little_endian-lp64 shared (dynamic) release build; by GCC 9.2.1 20190827 (Red Hat 9.2.1-1))
PASS : TestQgsDxfExport::initTestCase()
PASS : TestQgsDxfExport::testPoints()
PASS : TestQgsDxfExport::cleanupTestCase()
Totals: 3 passed, 0 failed, 0 skipped, 0 blacklisted, 272ms
********* Finished testing of TestQgsDxfExport *********
6.6.2. Testkörning med sandlåda
Som standard skrivs alla temporära testfiler till systemets temp-katalog (t.ex. /tmp/
på Linux-system eller C:\temp
på Windows). Många filer kan skapas i den här katalogen under testkörningen.
Om du inte vill blanda dig med systemets vanliga temp-katalog (t.ex. på en server med flera användare eller vid behörighetsproblem) kan du skapa din egen temp-katalog och ange den för ctest
genom att ange din nya katalog i miljövariabeln TMPDIR
.
På Linux kan du göra det med:
$ mkdir ~/my_qgis_temp
$ TMPDIR=~/my_qgis_temp ctest
6.6.3. Felsökning av enhetstester
C++-test
För C++-enhetstester lägger QtCreator automatiskt till körmål så att du kan starta dem från felsökaren.
Om du går till Projects och där till fliken Build & Run –> Desktop Run kan du också ange kommandoradsparametrar som gör att en delmängd av testerna kan köras i en .cpp-fil i debuggern.
Python-test
Det är också möjligt att starta Python-enhetstester från QtCreator med GDB. För detta måste du gå till Projects och välja Run under Build & Run. Lägg sedan till en ny Run configuration
med den körbara /usr/bin/python3
och kommandoradsargumenten inställda på sökvägen till enhetstestets pythonfil, t.ex. /home/user/dev/qgis/QGIS/tests/src/python/test_qgsattributeformeditorwidget.py
.
Ändra nu också Run Environment
och lägg till 3 nya variabler:
Variable |
Värde |
PYTHONPATH |
[build]/output/python/:[build]/output/python/plugins:[source]/tests/src/python |
QGIS_PREFIX_PATH |
[build]/output |
LD_LIBRARY_PATH |
[build]/output/lib |
Ersätt [build]
med din byggkatalog och [source]
med din källkatalog.
6.6.4. Ha kul
Det avslutar detta avsnitt om att skriva enhetstester i QGIS. Vi hoppas att du kommer att ta för vana att skriva tester för att testa ny funktionalitet och för att kontrollera regressioner. Vissa aspekter av testsystemet (i synnerhet CMakeLists.txt
-delarna) håller fortfarande på att bearbetas så att testramverket fungerar på ett verkligt plattformsoberoende sätt.