Skip to content

Commit fca8d39

Browse files
author
timlinux
committedNov 25, 2007
Completed my tutorial on unit testing as I promised I would at the last QGIS developer meeting...
git-svn-id: http://svn.osgeo.org/qgis/trunk/qgis@7656 c8812cc2-4d05-0410-92ff-de0c093fc19c

File tree

2 files changed

+1096
-58
lines changed

2 files changed

+1096
-58
lines changed
 

‎CODING

Lines changed: 572 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,13 @@
4646
2.6.5. Due Diligence
4747
2.7. Obtaining SVN Write Access
4848
2.7.1. Procedure once you have access
49-
3. Authors
49+
3. Unit Testing
50+
3.1. The QGIS testing framework - an overview
51+
3.2. Creating a unit test
52+
3.3. Adding your unit test to CMakeLists.txt
53+
3.4. Building your unit test
54+
3.5. Run your tests
55+
4. Authors
5056

5157

5258
------------------------------------------------------------------------
@@ -78,21 +84,21 @@ Class in QGIS begin with Qgs and are formed using mixed case.
7884
1.1.2. Members
7985
==============
8086

81-
Class member names begin with a lower case ''m'' and are formed using mixed case.
87+
Class member names begin with a lower case m and are formed using mixed case.
8288

8389

8490
mMapCanvas
8591
mCurrentExtent
8692

8793

8894
All class members should be private.
89-
'''Public class members are STRONGLY discouraged'''
95+
Public class members are STRONGLY discouraged
9096

9197

9298
1.1.3. Accessor Functions
9399
=========================
94100

95-
Class member values should be obtained through accesssor functions. The function should be named without a ''get'' prefix. Accessor functions for the two private members above would be:
101+
Class member values should be obtained through accesssor functions. The function should be named without a get prefix. Accessor functions for the two private members above would be:
96102

97103

98104
mapCanvas()
@@ -118,7 +124,7 @@ Function names begin with a lowercase letter and are formed using mixed case. Th
118124
1.2.1. Generated Classes
119125
========================
120126

121-
QGIS classes that are generated from Qt Designer (ui) files should have a ''Base'' suffix. This identifies the class as a generated base class.
127+
QGIS classes that are generated from Qt Designer (ui) files should have a Base suffix. This identifies the class as a generated base class.
122128

123129

124130
Examples:
@@ -131,8 +137,8 @@ QGIS classes that are generated from Qt Designer (ui) files should have a ''Base
131137

132138
All dialogs should implement the following:
133139
* Tooltip help for all toolbar icons and other relevant widgets
134-
* WhatsThis help for '''all''' widgets on the dialog
135-
* An optional (though highly recommended) context sensitive ''Help'' button that directs the user to the appropriate help page by launching their web browser
140+
* WhatsThis help for all widgets on the dialog
141+
* An optional (though highly recommended) context sensitive Help button that directs the user to the appropriate help page by launching their web browser
136142

137143

138144
1.3. C++ Files
@@ -343,10 +349,10 @@ So, prefer this:
343349
1.6.6. Book recommendations
344350
===========================
345351

346-
* http://www.awprofessional.com/title/0321334876 Effective (C++), Scott Meyers
347-
* http://www.awprofessional.com/bookstore/product.asp?isbn=020163371X&rl=1 More Effective (C++), Scott Meyers
348-
* http://www.awprofessional.com/title/0201749629 Effective (STL), Scott Meyers
349-
* http://www.awprofessional.com/title/0201634988 Design (Patterns), GoF
352+
* Effective C++ (http://www.awprofessional.com/title/0321334876), Scott Meyers
353+
* More Effective C++ (http://www.awprofessional.com/bookstore/product.asp?isbn=020163371X&rl=1), Scott Meyers
354+
* Effective STL (http://www.awprofessional.com/title/0201749629), Scott Meyers
355+
* Design Patterns (http://www.awprofessional.com/title/0201634988), GoF
350356

351357

352358
2. SVN Access
@@ -386,10 +392,10 @@ To check out SVN stable trunk:
386392
svn co https://svn.qgis.org/repos/qgis/trunk/qgis qgis_unstable
387393

388394

389-
/!\ '''Note:''' If you are behind a proxy server, edit your ~/subversion/servers file to specify
395+
/!\ Note: If you are behind a proxy server, edit your ~/subversion/servers file to specify
390396
your proxy settings first!
391397

392-
/!\ '''Note:''' In QGIS we keep our most stable code in trunk. Periodically we will tag a release
398+
/!\ Note: In QGIS we keep our most stable code in trunk. Periodically we will tag a release
393399
off trunk, and then continue stabilisation and selective incorporation of new features into trunk.
394400

395401
See the INSTALL file in the source tree for specific instructions on building development versions.
@@ -514,7 +520,7 @@ deal with the patches that are sent to use easily.
514520
========================
515521

516522
If the patch is a fix for a specific bug, please name the file with the bug number in it e.g.
517-
'''bug777fix.diff''', and attach it to the original bug report in trac (https://svn.qgis.org/trac).
523+
bug777fix.diff, and attach it to the original bug report in trac (https://svn.qgis.org/trac).
518524

519525
If the bug is an enhancement or new feature, its usually a good idea to create a ticket in
520526
trac (https://svn.qgis.org/trac) first and then attach you
@@ -633,7 +639,558 @@ Save and close in your editor. The first time you do this, you should be prompte
633639
put in your username and password. Just use the same ones as your trac account.
634640

635641

636-
3. Authors
642+
3. Unit Testing
643+
===============
644+
645+
As of November 2007 we require all new features going into trunk to be accompanied with
646+
a unit test. Initially we have limited this requirement to qgis_core, and we will extend
647+
this requirement to other parts of the code base once people are familiar with the
648+
procedures for unit testing explained in the sections that follow.
649+
650+
651+
3.1. The QGIS testing framework - an overview
652+
==============================================
653+
654+
Unit testing is carried out using a combination of QTestLib (the Qt testing library) and
655+
CTest (a framework for compiling and running tests as part of the CMake build process).
656+
Lets take an overview of the process before I delve into the details:
657+
658+
* There is some code you want to test, e.g. a class or function. Extreme programming
659+
advocates suggest that the code should not even be written yet when you start
660+
building your tests, and then as you implement your code you can immediately validate
661+
each new functional part you add with your test. In practive you will probably
662+
need to write tests for pre-existing code in QGIS since we are starting with a testing
663+
framework well after much application logic has already been implemented.
664+
665+
* You create a unit test. This happens under <QGIS Source Dir>/tests/src/core
666+
in the case of the core lib. The test is basically a client that creates an instance
667+
of a class and calls some methods on that class. It will check the return from each
668+
method to make sure it matches the expected value. If any one of the calls fails,
669+
the unit will fail.
670+
671+
* You include QtTestLib macros in your test class. This macro is processed by
672+
the Qt meta object compiler (moc) and expands your test class into a runnable application.
673+
674+
* You add a section to the CMakeLists.txt in your tests directory that will
675+
build your test.
676+
677+
* You ensure you have ENABLE_TESTING enabled in ccmake / cmakesetup. This
678+
will ensure your tests actually get compiled when you type make.
679+
680+
* You optionally add test data to <QGIS Source Dir>/tests/testdata if your
681+
test is data driven (e.g. needs to load a shapefile). These test data should be
682+
as small as possible and wherever possible you should use the existing datasets
683+
already there. Your tests should never modify this data in situ, but rather
684+
may a temporary copy somewhere if needed.
685+
686+
* You compile your sources and install. Do this using normal make && (sudo)
687+
make install procedure.
688+
689+
* You run your tests. This is normally done simply by doing make test
690+
after the make install step, though I will explain other aproaches that offer more
691+
fine grained control over running tests.
692+
693+
Right with that overview in mind, I will delve into a bit of detail. I've already
694+
done much of the configuration for you in CMake and other places in the source tree
695+
so all you need to do are the easy bits - writing unit tests!
696+
697+
698+
3.2. Creating a unit test
699+
=========================
700+
701+
Creating a unit test is easy - typically you will do this by just creating a
702+
single .cpp file (not .h file is used) and implement all your test methods as
703+
public methods that return void. I'll use a simple test class for QgsRasterLayer
704+
throughout the section that follows to illustrate. By convention we will name our
705+
test with the same name as the class they are testing but prefixed with 'Test'.
706+
So our test implementation goes in a file called testqgsrasterlayer.cpp and
707+
the class itself will be TestQgsRasterLayer. First we add our standard copyright
708+
banner:
709+
710+
711+
/***************************************************************************
712+
testqgsvectorfilewriter.cpp
713+
--------------------------------------
714+
Date : Frida Nov 23 2007
715+
Copyright : (C) 2007 by Tim Sutton
716+
Email : tim@linfiniti.com
717+
***************************************************************************
718+
* *
719+
* This program is free software; you can redistribute it and/or modify *
720+
* it under the terms of the GNU General Public License as published by *
721+
* the Free Software Foundation; either version 2 of the License, or *
722+
* (at your option) any later version. *
723+
* *
724+
***************************************************************************/
725+
726+
727+
Next we use start our includes needed for the tests we plan to run. There is
728+
one special include all tests should have:
729+
730+
731+
#include <QtTest>
732+
733+
734+
Beyond that you just continue implementing your class as per normal, pulling
735+
in whatever headers you may need:
736+
737+
738+
//Qt includes...
739+
#include <QObject>
740+
#include <QString>
741+
#include <QObject>
742+
#include <QApplication>
743+
#include <QFileInfo>
744+
#include <QDir>
745+
746+
//qgis includes...
747+
#include <qgsrasterlayer.h>
748+
#include <qgsrasterbandstats.h>
749+
#include <qgsapplication.h>
750+
751+
752+
Since we are combining both class declaration and implementation in a single
753+
file the class declaration comes next. We start with our doxygen documentation.
754+
Every test case should be properly documented. We use the doxygen ingroup
755+
directive so that all the UnitTests appear as a module in the generated
756+
Doxygen documentation. After that comes a short description of the unit test:
757+
758+
759+
/** \ingroup UnitTests
760+
* This is a unit test for the QgsRasterLayer class.
761+
*/
762+
763+
764+
The class must inherit from QObject and include the Q_OBJECT macro.
765+
766+
767+
class TestQgsRasterLayer: public QObject
768+
{
769+
Q_OBJECT;
770+
771+
772+
All our test methods are implemented as private slots. The QtTest framework
773+
will sequentially call each private slot method in the test class. There are
774+
four 'special' methods which if implemented will be called at the start of
775+
the unit test (initTestCase), at the end of the unit test (cleanupTestCase).
776+
Before each test method is called, the init() method will be called and
777+
after each test method is called the cleanup() method is called. These
778+
methods are handy in that they allow you to allocate and cleanup resources
779+
prior to running each test, and the test unit as a whole.
780+
781+
782+
private slots:
783+
// will be called before the first testfunction is executed.
784+
void initTestCase();
785+
// will be called after the last testfunction was executed.
786+
void cleanupTestCase(){};
787+
// will be called before each testfunction is executed.
788+
void init(){};
789+
// will be called after every testfunction.
790+
void cleanup();
791+
792+
793+
Then come your test methods, all of which should take no parameters and
794+
should return void. The methods will be called in order of declaration.
795+
I am implementing two methods here which illustrates to types of testing. In
796+
the first case I want to generally test the various parts of the class are
797+
working, I can use a functional testing approach. Once again, extreme
798+
programmers would advocate writing these tests before implementing the
799+
class. Then as you work your way through your class implementation you
800+
iteratively run your unit tests. More and more test functions should complete
801+
sucessfully as your class implementation work progresses, and when the whole
802+
unit test passes, your new class is done and is now complete with a repeatable
803+
way to validate it.
804+
805+
Typically your unit tests would only cover the public API of your
806+
class, and normally you do not need to write tests for accessors and mutators.
807+
If it should happen that an acccessor or mutator is not working as expected
808+
you would normally implement a regression test to check for this (see
809+
lower down).
810+
811+
812+
//
813+
// Functional Testing
814+
//
815+
816+
/** Check if a raster is valid. */
817+
void isValid();
818+
819+
// more functional tests here ...
820+
821+
822+
Next we implement our regression tests. Regression tests should be
823+
implemented to replicate the conditions of a particular bug. For example
824+
I recently received a report by email that the cell count by rasters was
825+
off by 1, throwing off all the statistics for the raster bands. I opened
826+
a bug (ticket #832) and then created a regression test that replicated
827+
the bug using a small test dataset (a 10x10 raster). Then I ran the test
828+
and ran it, verifying that it did indeed fail (the cell count was 99
829+
instead of 100). Then I went to fix the bug and reran the unit test and
830+
the regression test passed. I committed the regression test along with
831+
the bug fix. Now if anybody breakes this in the source code again in the
832+
future, we can immediatly identify that the code has regressed. Better
833+
yet before committing any changes in the future, running our tests will
834+
ensure our changes dont have unexpected side effects - like breaking
835+
existing functionality.
836+
837+
There is one more benifit to regression tests - they can save you time.
838+
If you ever fixed a bug that involved making changes to the source,
839+
and then running the application and performing a series of convoluted
840+
steps to replicate the issue, it will be immediately apparent that
841+
simply implementing your regression test before fixing the bug
842+
will let you automate the testing for bug resolution in an efficient
843+
manner.
844+
845+
To implement your regression test, you should follow the naming
846+
convention of regression<TicketID> for your test functions. If no
847+
trac ticket exists for the regression, you should create one first.
848+
Using this approach allows the person running a failed regression
849+
test easily go and find out more information.
850+
851+
852+
//
853+
// Regression Testing
854+
//
855+
856+
/** This is our second test case...to check if a raster
857+
reports its dimensions properly. It is a regression test
858+
for ticket #832 which was fixed with change r7650.
859+
*/
860+
void regression832();
861+
862+
// more regression tests go here ...
863+
864+
865+
Finally in our test class declaration you can declare privately
866+
any data members and helper methods your unit test may need. In our
867+
case I will declare a QgsRasterLayer * which can be used by any
868+
of our test methods. The raster layer will be created in the
869+
initTestCase() function which is run before any other tests, and then
870+
destroyed using cleanupTestCase() which is run after all tests. By
871+
declaring helper methods (which may be called by various test
872+
functions) privately, you can ensure that they wont be automatically
873+
run by the QTest executeable that is created when we compile our test.
874+
875+
876+
private:
877+
// Here we have any data structures that may need to
878+
// be used in many test cases.
879+
QgsRasterLayer * mpLayer;
880+
};
881+
882+
883+
884+
That ends our class declaration. The implementation is simply
885+
inlined in the same file lower down. First our init and cleanup functions:
886+
887+
888+
void TestQgsRasterLayer::initTestCase()
889+
{
890+
// init QGIS's paths - true means that all path will be inited from prefix
891+
QString qgisPath = QCoreApplication::applicationDirPath ();
892+
QgsApplication::setPrefixPath(qgisPath, TRUE);
893+
#ifdef Q_OS_LINUX
894+
QgsApplication::setPkgDataPath(qgisPath + "/../share/qgis");
895+
#endif
896+
//create some objects that will be used in all tests...
897+
898+
std::cout << "Prefix PATH: " << QgsApplication::prefixPath().toLocal8Bit().data() << std::endl;
899+
std::cout << "Plugin PATH: " << QgsApplication::pluginPath().toLocal8Bit().data() << std::endl;
900+
std::cout << "PkgData PATH: " << QgsApplication::pkgDataPath().toLocal8Bit().data() << std::endl;
901+
std::cout << "User DB PATH: " << QgsApplication::qgisUserDbFilePath().toLocal8Bit().data() << std::endl;
902+
903+
//create a raster layer that will be used in all tests...
904+
QString myFileName (TEST_DATA_DIR); //defined in CmakeLists.txt
905+
myFileName = myFileName + QDir::separator() + "tenbytenraster.asc";
906+
QFileInfo myRasterFileInfo ( myFileName );
907+
mpLayer = new QgsRasterLayer ( myRasterFileInfo.filePath(),
908+
myRasterFileInfo.completeBaseName() );
909+
}
910+
911+
void TestQgsRasterLayer::cleanupTestCase()
912+
{
913+
delete mpLayer;
914+
}
915+
916+
917+
918+
The above init function illustrates a couple of interesting things.
919+
920+
1. I needed to manually set the QGIS application data path so that
921+
resources such as srs.db can be found properly.
922+
2. Secondly, this is a data driven test so we needed to provide a
923+
way to generically locate the 'tenbytenraster.asc file. This was
924+
achieved by using the compiler define TEST_DATA_PATH. The
925+
define is created in the CMakeLists.txt configuration file under
926+
<QGIS Source Root>/tests/CMakeLists.txt and is available to all
927+
QGIS unit tests. If you need test data for your test, commit it
928+
under <QGIS Source Root>/tests/testdata. You should only commit
929+
very small datasets here. If your test needs to modify the test
930+
data, it should make a copy of if first.
931+
932+
Qt also provides some other interesting mechanisms for data driven
933+
testing, so if you are interested to know more on the topic, consult
934+
the Qt documentation.
935+
936+
Next lets look at our functional test. The isValid() test simply
937+
checks the raster layer was correctly loaded in the initTestCase.
938+
QVERIFY is a Qt macro that you can use to evaluate a test condition.
939+
There are a few other use macros Qt provide for use in your tests
940+
including:
941+
942+
943+
QCOMPARE ( actual, expected )
944+
QEXPECT_FAIL ( dataIndex, comment, mode )
945+
QFAIL ( message )
946+
QFETCH ( type, name )
947+
QSKIP ( description, mode )
948+
QTEST ( actual, testElement )
949+
QTEST_APPLESS_MAIN ( TestClass )
950+
QTEST_MAIN ( TestClass )
951+
QTEST_NOOP_MAIN ()
952+
QVERIFY2 ( condition, message )
953+
QVERIFY ( condition )
954+
QWARN ( message )
955+
956+
957+
Some of these macros are useful only when using the Qt framework
958+
for data driven testing (see the Qt docs for more detail).
959+
960+
961+
void TestQgsRasterLayer::isValid()
962+
{
963+
QVERIFY ( mpLayer->isValid() );
964+
}
965+
966+
967+
Normally your functional tests would cover all the range of
968+
functionality of your classes public API where feasible. With our
969+
functional tests out the way, we can look at our regression test example.
970+
971+
Since the issue in bug #832 is a misreported cell count, writing
972+
our test if simply a matter of using QVERIFY to check that the
973+
cell count meets the expected value:
974+
975+
976+
void TestQgsRasterLayer::regression832()
977+
{
978+
QVERIFY ( mpLayer->getRasterXDim() == 10 );
979+
QVERIFY ( mpLayer->getRasterYDim() == 10 );
980+
// regression check for ticket #832
981+
// note getRasterBandStats call is base 1
982+
QVERIFY ( mpLayer->getRasterBandStats(1).elementCountInt == 100 );
983+
}
984+
985+
986+
With all the unit test functions implemented, there one final thing we
987+
need to add to our test class:
988+
989+
990+
QTEST_MAIN(TestQgsRasterLayer)
991+
#include "moc_testqgsrasterlayer.cxx"
992+
993+
994+
The purpose of these two lines is to signal to Qt's moc that his is a
995+
QtTest (it will generate a main method that in turn calls each test funtion.
996+
The last line is the include for the MOC generated sources. You should
997+
replace 'testqgsrasterlayer' with the name of your class in lower case.
998+
999+
1000+
3.3. Adding your unit test to CMakeLists.txt
1001+
============================================
1002+
1003+
Adding your unit test to the build system is simply a matter of editing
1004+
the CMakeLists.txt in the test directory, cloning one of the existing
1005+
test blocks, and then search and replacing your test class name into it.
1006+
For example:
1007+
1008+
1009+
#
1010+
# QgsRasterLayer test
1011+
#
1012+
SET(qgis_rasterlayertest_SRCS testqgsrasterlayer.cpp)
1013+
SET(qgis_rasterlayertest_MOC_CPPS testqgsrasterlayer.cpp)
1014+
QT4_WRAP_CPP(qgis_rasterlayertest_MOC_SRCS ${qgis_rasterlayertest_MOC_CPPS})
1015+
ADD_CUSTOM_TARGET(qgis_rasterlayertestmoc ALL DEPENDS ${qgis_rasterlayertest_MOC_SRCS})
1016+
ADD_EXECUTABLE(qgis_rasterlayertest ${qgis_rasterlayertest_SRCS})
1017+
ADD_DEPENDENCIES(qgis_rasterlayertest qgis_rasterlayertestmoc)
1018+
TARGET_LINK_LIBRARIES(qgis_rasterlayertest ${QT_LIBRARIES} qgis_core)
1019+
INSTALL(TARGETS qgis_rasterlayertest RUNTIME DESTINATION ${QGIS_BIN_DIR})
1020+
ADD_TEST(qgis_rasterlayertest ${QGIS_BIN_DIR}/qgis_rasterlayertest)
1021+
1022+
1023+
I'll run through these lines briefly to explain what they do, but if
1024+
you are not interested, just clone the block, search and replace e.g.
1025+
1026+
1027+
:'<,'>s/rasterlayer/mynewtest/g
1028+
1029+
1030+
Lets look a little more in detail at the individual lines. First we
1031+
define the list of sources for our test. Since we have only one source file
1032+
(following the methodology I described above where class declaration and
1033+
definition are in the same file) its a simple statement:
1034+
1035+
1036+
SET(qgis_rasterlayertest_SRCS testqgsrasterlayer.cpp)
1037+
1038+
1039+
Since our test class needs to be run through the Qt meta object compiler (moc)
1040+
we need to provide a couple of lines to make that happen too:
1041+
1042+
1043+
SET(qgis_rasterlayertest_MOC_CPPS testqgsrasterlayer.cpp)
1044+
QT4_WRAP_CPP(qgis_rasterlayertest_MOC_SRCS ${qgis_rasterlayertest_MOC_CPPS})
1045+
ADD_CUSTOM_TARGET(qgis_rasterlayertestmoc ALL DEPENDS ${qgis_rasterlayertest_MOC_SRCS})
1046+
1047+
1048+
Next we tell cmake that it must make an executeable from the test class.
1049+
Remember in the previous section on the last line of the class implementation
1050+
I included the moc outputs directly into our test class, so that will
1051+
give it (among other things) a main method so the class can be
1052+
compiled as an executeable:
1053+
1054+
1055+
ADD_EXECUTABLE(qgis_rasterlayertest ${qgis_rasterlayertest_SRCS})
1056+
ADD_DEPENDENCIES(qgis_rasterlayertest qgis_rasterlayertestmoc)
1057+
1058+
1059+
Next we need to specify any library dependencies. At the moment classes
1060+
have been implemented with a catch-all QT_LIBRARIES dependency, but I will
1061+
be working to replace that with the specific Qt libraries that each class
1062+
needs only. Of course you also need to link to the relevant qgis
1063+
libraries as required by your unit test.
1064+
1065+
1066+
TARGET_LINK_LIBRARIES(qgis_rasterlayertest ${QT_LIBRARIES} qgis_core)
1067+
1068+
1069+
Next I tell cmake to the same place as the qgis binaries itself. This
1070+
is something I plan to remove in the future so that the tests can
1071+
run directly from inside the source tree.
1072+
1073+
1074+
INSTALL(TARGETS qgis_rasterlayertest RUNTIME DESTINATION ${QGIS_BIN_DIR})
1075+
1076+
1077+
Finally here is where the best magic happens - we register the class with
1078+
ctest. If you recall in the overview I gave in the beginning of this
1079+
section we are using both QtTest and CTest together. To recap, QtTest adds a
1080+
main method to your test unit and handles calling your test methods within
1081+
the class. It also provides some macros like QVERIFY that you can use as
1082+
to test for failure of the tests using conditions. The output from
1083+
a QtTest unit test is an executeable which you can run from the command line.
1084+
However when you have a suite of tests and you want to run each executeable
1085+
in turn, and better yet integrate running tests into the build process,
1086+
the CTest is what we use. The next line registers the unit test with
1087+
CMake / CTest.
1088+
1089+
1090+
ADD_TEST(qgis_rasterlayertest ${QGIS_BIN_DIR}/qgis_rasterlayertest)
1091+
1092+
1093+
The last thing I should add is that if your test requires optional
1094+
parts of the build process (e.g. Postgresql support, GSL libs, GRASS etc.),
1095+
you should take care to enclose you test block inside a IF () block
1096+
in the CMakeLists.txt file.
1097+
1098+
1099+
3.4. Building your unit test
1100+
============================
1101+
1102+
To build the unit test you need only to make sure that ENABLE_TESTS=true
1103+
in the cmake configuration. There are two ways to do this:
1104+
1105+
1. Run ccmake .. (cmakesetup .. under windows) and interactively set
1106+
the ENABLE_TESTS flag to ON.
1107+
1. Add a command line flag to cmake e.g. cmake -DENABLE_TESTS=true ..
1108+
1109+
Other than that, just build QGIS as per normal and the tests should build
1110+
too.
1111+
1112+
1113+
3.5. Run your tests
1114+
===================
1115+
1116+
The simplest way to run the tests is as part of your normal build process:
1117+
1118+
1119+
make && make install && make test
1120+
1121+
1122+
The make test command will invoke CTest which will run each test that
1123+
was registered using the ADD_TEST CMake directive described above. Typical
1124+
output from make test will look like this:
1125+
1126+
1127+
Running tests...
1128+
Start processing tests
1129+
Test project /Users/tim/dev/cpp/qgis/build
1130+
1/ 3 Testing qgis_applicationtest ***Exception: Other
1131+
2/ 3 Testing qgis_filewritertest *** Passed
1132+
3/ 3 Testing qgis_rasterlayertest *** Passed
1133+
1134+
0% tests passed, 3 tests failed out of 3
1135+
1136+
The following tests FAILED:
1137+
1 - qgis_applicationtest (OTHER_FAULT)
1138+
Errors while running CTest
1139+
make: *** [test] Error 8
1140+
1141+
1142+
If a test fails, you can use the ctest command to examine more
1143+
closely why it failed. User the -R option to specify a regex for
1144+
which tests you want to run and -V to get verbose output:
1145+
1146+
1147+
[build] ctest -R appl -V
1148+
Start processing tests
1149+
Test project /Users/tim/dev/cpp/qgis/build
1150+
Constructing a list of tests
1151+
Done constructing a list of tests
1152+
Changing directory into /Users/tim/dev/cpp/qgis/build/tests/src/core
1153+
1/ 3 Testing qgis_applicationtest
1154+
Test command: /Users/tim/dev/cpp/qgis/build/tests/src/core/qgis_applicationtest
1155+
********* Start testing of TestQgsApplication *********
1156+
Config: Using QTest library 4.3.0, Qt 4.3.0
1157+
PASS : TestQgsApplication::initTestCase()
1158+
Prefix PATH: /Users/tim/dev/cpp/qgis/build/tests/src/core/../
1159+
Plugin PATH: /Users/tim/dev/cpp/qgis/build/tests/src/core/..//lib/qgis
1160+
PkgData PATH: /Users/tim/dev/cpp/qgis/build/tests/src/core/..//share/qgis
1161+
User DB PATH: /Users/tim/.qgis/qgis.db
1162+
PASS : TestQgsApplication::getPaths()
1163+
Prefix PATH: /Users/tim/dev/cpp/qgis/build/tests/src/core/../
1164+
Plugin PATH: /Users/tim/dev/cpp/qgis/build/tests/src/core/..//lib/qgis
1165+
PkgData PATH: /Users/tim/dev/cpp/qgis/build/tests/src/core/..//share/qgis
1166+
User DB PATH: /Users/tim/.qgis/qgis.db
1167+
QDEBUG : TestQgsApplication::checkTheme() Checking if a theme icon exists:
1168+
QDEBUG : TestQgsApplication::checkTheme()
1169+
/Users/tim/dev/cpp/qgis/build/tests/src/core/..//share/qgis/themes/default//mIconProjectionDisabled.png
1170+
FAIL! : TestQgsApplication::checkTheme() '!myPixmap.isNull()' returned FALSE. ()
1171+
Loc: [/Users/tim/dev/cpp/qgis/tests/src/core/testqgsapplication.cpp(59)]
1172+
PASS : TestQgsApplication::cleanupTestCase()
1173+
Totals: 3 passed, 1 failed, 0 skipped
1174+
********* Finished testing of TestQgsApplication *********
1175+
-- Process completed
1176+
***Failed
1177+
1178+
0% tests passed, 1 tests failed out of 1
1179+
1180+
The following tests FAILED:
1181+
1 - qgis_applicationtest (Failed)
1182+
Errors while running CTest
1183+
1184+
1185+
1186+
Well that concludes this section on writing unit tests in QGIS. We hope you
1187+
will get into the habit of writing test to test new functionality and to
1188+
check for regressions. Some aspects of the test system (in particular the
1189+
CMakeLists.txt parts) are still being worked on so that the testing framework
1190+
works in a truly platform way. I will update this document as things progress.
1191+
1192+
1193+
4. Authors
6371194
==========
6381195

6391196
* Tim Sutton (author and editor)

‎CODING.t2t

Lines changed: 524 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,33 @@
1-
%!encoding: iso-8859-1
2-
3-
4-
% These are comments and will not be generated in any output
5-
% -------------------
6-
7-
%This document is in text2tags format. You can generate html, plain text and
8-
%moinmoin formatted documentation by running txt2tags on this document. See the
9-
%txt2tags home page for more details. Please insert manual line breaks in this
10-
%document as it makes diffing for changes much easier. To do this in vim
11-
%automatically, select a section then issue (gq) command. Please dont
12-
%apply vim formatting to the whole document as it screws up some formatting
13-
%rather apply it selectively to paragraphs where needed.
14-
15-
% To generate the text version of this document:
16-
% txt2tags -t txt --toc --enum-title -o CODING CODING.t2t
17-
% To generate the moinmoin version of this document
18-
% txt2tags -t moin --toc --enum-title -o CODING.moin CODING.t2t
19-
20-
% End of comments
1+
%!encoding: iso-8859-1
2+
3+
4+
% These are comments and will not be generated in any output
5+
% -------------------
6+
7+
%This document is in text2tags format. You can generate html, plain text and
8+
%moinmoin formatted documentation by running txt2tags on this document. See the
9+
%txt2tags home page for more details. Please insert manual line breaks in this
10+
%document as it makes diffing for changes much easier. To do this in vim
11+
%automatically, select a section then issue (gq) command. Please dont
12+
%apply vim formatting to the whole document as it screws up some formatting
13+
%rather apply it selectively to paragraphs where needed.
14+
15+
% To generate the text version of this document:
16+
% txt2tags -t txt --toc --enum-title -o CODING CODING.t2t
17+
% To generate the moinmoin version of this document
18+
% txt2tags -t moin --toc --enum-title -o CODING.moin CODING.t2t
19+
20+
% End of comments
2121
% -------------------
2222

2323

2424
%-----------------------------------------------------------------
2525
% Insert the following preamble on moinmoin generated output
2626
%-----------------------------------------------------------------
2727

28-
%/!\ '''Note:''' Please do not edit this document directly.
28+
%/!\ **Note:** Please do not edit this document directly.
2929
%
30-
%/!\ '''Note:''' Please do not remove this notice.
30+
%/!\ **Note:** Please do not remove this notice.
3131
%
3232
%(!) This document was generated using text2tags from INSTALL.t2t in the QGIS sources. Make your
3333
% edits to that file and use t2t to regenerate in moinmoin %format, then paste the procedure in below.
@@ -58,16 +58,16 @@ Examples:
5858
```
5959

6060
=== Members ===
61-
Class member names begin with a lower case ''m'' and are formed using mixed case.
61+
Class member names begin with a lower case //m// and are formed using mixed case.
6262
```
6363
mMapCanvas
6464
mCurrentExtent
6565
```
6666

6767
All class members should be private.
68-
'''Public class members are STRONGLY discouraged'''
68+
**Public class members are STRONGLY discouraged**
6969
=== Accessor Functions ===
70-
Class member values should be obtained through accesssor functions. The function should be named without a ''get'' prefix. Accessor functions for the two private members above would be:
70+
Class member values should be obtained through accesssor functions. The function should be named without a //get// prefix. Accessor functions for the two private members above would be:
7171
```
7272
mapCanvas()
7373
currentExtent()
@@ -82,7 +82,7 @@ Function names begin with a lowercase letter and are formed using mixed case. Th
8282

8383
== Qt Designer ==
8484
=== Generated Classes ===
85-
QGIS classes that are generated from Qt Designer (ui) files should have a ''Base'' suffix. This identifies the class as a generated base class.
85+
QGIS classes that are generated from Qt Designer (ui) files should have a //Base// suffix. This identifies the class as a generated base class.
8686
```
8787
Examples:
8888
QgsPluginMangerBase
@@ -91,8 +91,8 @@ Examples:
9191
=== Dialogs ===
9292
All dialogs should implement the following:
9393
* Tooltip help for all toolbar icons and other relevant widgets
94-
* WhatsThis help for '''all''' widgets on the dialog
95-
* An optional (though highly recommended) context sensitive ''Help'' button that directs the user to the appropriate help page by launching their web browser
94+
* WhatsThis help for **all** widgets on the dialog
95+
* An optional (though highly recommended) context sensitive //Help// button that directs the user to the appropriate help page by launching their web browser
9696
== C++ Files ==
9797
=== Names ===
9898
C++ implementation and header files should be have a .cpp and .h extension respectively.
@@ -261,10 +261,10 @@ So, prefer this:
261261

262262
=== Book recommendations ===
263263

264-
* [http://www.awprofessional.com/title/0321334876 Effective C++], Scott Meyers
265-
* [http://www.awprofessional.com/bookstore/product.asp?isbn=020163371X&rl=1 More Effective C++], Scott Meyers
266-
* [http://www.awprofessional.com/title/0201749629 Effective STL], Scott Meyers
267-
* [http://www.awprofessional.com/title/0201634988 Design Patterns], GoF
264+
* [Effective C++ http://www.awprofessional.com/title/0321334876], Scott Meyers
265+
* [More Effective C++ http://www.awprofessional.com/bookstore/product.asp?isbn=020163371X&rl=1], Scott Meyers
266+
* [Effective STL http://www.awprofessional.com/title/0201749629], Scott Meyers
267+
* [Design Patterns http://www.awprofessional.com/title/0201634988], GoF
268268

269269

270270

@@ -298,10 +298,10 @@ To check out SVN stable trunk:
298298
svn co https://svn.qgis.org/repos/qgis/trunk/qgis qgis_unstable
299299
```
300300

301-
/!\ '''Note:''' If you are behind a proxy server, edit your ~/subversion/servers file to specify
301+
/!\ **Note:** If you are behind a proxy server, edit your ~/subversion/servers file to specify
302302
your proxy settings first!
303303

304-
/!\ '''Note:''' In QGIS we keep our most stable code in trunk. Periodically we will tag a release
304+
/!\ **Note:** In QGIS we keep our most stable code in trunk. Periodically we will tag a release
305305
off trunk, and then continue stabilisation and selective incorporation of new features into trunk.
306306

307307
See the INSTALL file in the source tree for specific instructions on building development versions.
@@ -409,7 +409,7 @@ deal with the patches that are sent to use easily.
409409
=== Patch file naming ===
410410

411411
If the patch is a fix for a specific bug, please name the file with the bug number in it e.g.
412-
'''bug777fix.diff''', and attach it to the original bug report in trac (https://svn.qgis.org/trac).
412+
**bug777fix.diff**, and attach it to the original bug report in trac (https://svn.qgis.org/trac).
413413

414414
If the bug is an enhancement or new feature, its usually a good idea to create a ticket in
415415
trac (https://svn.qgis.org/trac) first and then attach you
@@ -536,38 +536,38 @@ Unit testing is carried out using a combination of QTestLib (the Qt testing libr
536536
CTest (a framework for compiling and running tests as part of the CMake build process).
537537
Lets take an overview of the process before I delve into the details:
538538

539-
* '''There is some code you want to test''', e.g. a class or function. Extreme programming
539+
* **There is some code you want to test**, e.g. a class or function. Extreme programming
540540
advocates suggest that the code should not even be written yet when you start
541541
building your tests, and then as you implement your code you can immediately validate
542542
each new functional part you add with your test. In practive you will probably
543543
need to write tests for pre-existing code in QGIS since we are starting with a testing
544544
framework well after much application logic has already been implemented.
545545

546-
* '''You create a unit test.''' This happens under <QGIS Source Dir>/tests/src/core
546+
* **You create a unit test.** This happens under <QGIS Source Dir>/tests/src/core
547547
in the case of the core lib. The test is basically a client that creates an instance
548548
of a class and calls some methods on that class. It will check the return from each
549549
method to make sure it matches the expected value. If any one of the calls fails,
550550
the unit will fail.
551551

552-
* '''You include QtTestLib macros in your test class.''' This macro is processed by
552+
* **You include QtTestLib macros in your test class.** This macro is processed by
553553
the Qt meta object compiler (moc) and expands your test class into a runnable application.
554554

555-
* '''You add a section to the CMakeLists.txt''' in your tests directory that will
555+
* **You add a section to the CMakeLists.txt** in your tests directory that will
556556
build your test.
557557

558-
* '''You ensure you have ENABLE_TESTING enabled in ccmake / cmakesetup.''' This
558+
* **You ensure you have ENABLE_TESTING enabled in ccmake / cmakesetup.** This
559559
will ensure your tests actually get compiled when you type make.
560560

561-
* '''You optionally add test data to <QGIS Source Dir>/tests/testdata''' if your
561+
* **You optionally add test data to <QGIS Source Dir>/tests/testdata** if your
562562
test is data driven (e.g. needs to load a shapefile). These test data should be
563563
as small as possible and wherever possible you should use the existing datasets
564564
already there. Your tests should never modify this data in situ, but rather
565565
may a temporary copy somewhere if needed.
566566

567-
* '''You compile your sources and install.''' Do this using normal make && (sudo)
567+
* **You compile your sources and install.** Do this using normal make && (sudo)
568568
make install procedure.
569569

570-
* '''You run your tests.''' This is normally done simply by doing '''make test'''
570+
* **You run your tests.** This is normally done simply by doing **make test**
571571
after the make install step, though I will explain other aproaches that offer more
572572
fine grained control over running tests.
573573

@@ -577,12 +577,493 @@ so all you need to do are the easy bits - writing unit tests!
577577

578578
== Creating a unit test ==
579579

580+
Creating a unit test is easy - typically you will do this by just creating a
581+
single .cpp file (not .h file is used) and implement all your test methods as
582+
public methods that return void. I'll use a simple test class for QgsRasterLayer
583+
throughout the section that follows to illustrate. By convention we will name our
584+
test with the same name as the class they are testing but prefixed with 'Test'.
585+
So our test implementation goes in a file called testqgsrasterlayer.cpp and
586+
the class itself will be TestQgsRasterLayer. First we add our standard copyright
587+
banner:
588+
589+
```
590+
/***************************************************************************
591+
testqgsvectorfilewriter.cpp
592+
--------------------------------------
593+
Date : Frida Nov 23 2007
594+
Copyright : (C) 2007 by Tim Sutton
595+
Email : tim@linfiniti.com
596+
***************************************************************************
597+
* *
598+
* This program is free software; you can redistribute it and/or modify *
599+
* it under the terms of the GNU General Public License as published by *
600+
* the Free Software Foundation; either version 2 of the License, or *
601+
* (at your option) any later version. *
602+
* *
603+
***************************************************************************/
604+
```
605+
606+
Next we use start our includes needed for the tests we plan to run. There is
607+
one special include all tests should have:
608+
609+
```
610+
#include <QtTest>
611+
```
612+
613+
Beyond that you just continue implementing your class as per normal, pulling
614+
in whatever headers you may need:
615+
616+
```
617+
//Qt includes...
618+
#include <QObject>
619+
#include <QString>
620+
#include <QObject>
621+
#include <QApplication>
622+
#include <QFileInfo>
623+
#include <QDir>
624+
625+
//qgis includes...
626+
#include <qgsrasterlayer.h>
627+
#include <qgsrasterbandstats.h>
628+
#include <qgsapplication.h>
629+
```
630+
631+
Since we are combining both class declaration and implementation in a single
632+
file the class declaration comes next. We start with our doxygen documentation.
633+
Every test case should be properly documented. We use the doxygen **ingroup**
634+
directive so that all the UnitTests appear as a module in the generated
635+
Doxygen documentation. After that comes a short description of the unit test:
636+
637+
```
638+
/** \ingroup UnitTests
639+
* This is a unit test for the QgsRasterLayer class.
640+
*/
641+
```
642+
643+
The class **must** inherit from QObject and include the Q_OBJECT macro.
644+
645+
```
646+
class TestQgsRasterLayer: public QObject
647+
{
648+
Q_OBJECT;
649+
```
650+
651+
All our test methods are implemented as **private slots**. The QtTest framework
652+
will sequentially call each private slot method in the test class. There are
653+
four 'special' methods which if implemented will be called at the start of
654+
the unit test (**initTestCase**), at the end of the unit test (**cleanupTestCase**).
655+
Before each test method is called, the **init()** method will be called and
656+
after each test method is called the **cleanup()** method is called. These
657+
methods are handy in that they allow you to allocate and cleanup resources
658+
prior to running each test, and the test unit as a whole.
659+
660+
661+
```
662+
private slots:
663+
// will be called before the first testfunction is executed.
664+
void initTestCase();
665+
// will be called after the last testfunction was executed.
666+
void cleanupTestCase(){};
667+
// will be called before each testfunction is executed.
668+
void init(){};
669+
// will be called after every testfunction.
670+
void cleanup();
671+
```
672+
673+
Then come your test methods, all of which should take **no parameters** and
674+
should **return void**. The methods will be called in order of declaration.
675+
I am implementing two methods here which illustrates to types of testing. In
676+
the first case I want to generally test the various parts of the class are
677+
working, I can use a **functional testing** approach. Once again, extreme
678+
programmers would advocate writing these tests **before** implementing the
679+
class. Then as you work your way through your class implementation you
680+
iteratively run your unit tests. More and more test functions should complete
681+
sucessfully as your class implementation work progresses, and when the whole
682+
unit test passes, your new class is done and is now complete with a repeatable
683+
way to validate it.
684+
685+
Typically your unit tests would only cover the **public** API of your
686+
class, and normally you do not need to write tests for accessors and mutators.
687+
If it should happen that an acccessor or mutator is not working as expected
688+
you would normally implement a **regression** test to check for this (see
689+
lower down).
690+
691+
```
692+
//
693+
// Functional Testing
694+
//
695+
696+
/** Check if a raster is valid. */
697+
void isValid();
698+
699+
// more functional tests here ...
700+
```
701+
702+
Next we implement our **regression tests**. Regression tests should be
703+
implemented to replicate the conditions of a particular bug. For example
704+
I recently received a report by email that the cell count by rasters was
705+
off by 1, throwing off all the statistics for the raster bands. I opened
706+
a bug (ticket #832) and then created a regression test that replicated
707+
the bug using a small test dataset (a 10x10 raster). Then I ran the test
708+
and ran it, verifying that it did indeed fail (the cell count was 99
709+
instead of 100). Then I went to fix the bug and reran the unit test and
710+
the regression test passed. I committed the regression test along with
711+
the bug fix. Now if anybody breakes this in the source code again in the
712+
future, we can immediatly identify that the code has regressed. Better
713+
yet before committing any changes in the future, running our tests will
714+
ensure our changes dont have unexpected side effects - like breaking
715+
existing functionality.
716+
717+
There is one more benifit to regression tests - they can save you time.
718+
If you ever fixed a bug that involved making changes to the source,
719+
and then running the application and performing a series of convoluted
720+
steps to replicate the issue, it will be immediately apparent that
721+
simply implementing your regression test **before** fixing the bug
722+
will let you automate the testing for bug resolution in an efficient
723+
manner.
724+
725+
To implement your regression test, you should follow the naming
726+
convention of regression<TicketID> for your test functions. If no
727+
trac ticket exists for the regression, you should create one first.
728+
Using this approach allows the person running a failed regression
729+
test easily go and find out more information.
730+
731+
```
732+
//
733+
// Regression Testing
734+
//
735+
736+
/** This is our second test case...to check if a raster
737+
reports its dimensions properly. It is a regression test
738+
for ticket #832 which was fixed with change r7650.
739+
*/
740+
void regression832();
741+
742+
// more regression tests go here ...
743+
```
744+
745+
Finally in our test class declaration you can declare privately
746+
any data members and helper methods your unit test may need. In our
747+
case I will declare a QgsRasterLayer * which can be used by any
748+
of our test methods. The raster layer will be created in the
749+
initTestCase() function which is run before any other tests, and then
750+
destroyed using cleanupTestCase() which is run after all tests. By
751+
declaring helper methods (which may be called by various test
752+
functions) privately, you can ensure that they wont be automatically
753+
run by the QTest executeable that is created when we compile our test.
754+
755+
```
756+
private:
757+
// Here we have any data structures that may need to
758+
// be used in many test cases.
759+
QgsRasterLayer * mpLayer;
760+
};
761+
762+
```
763+
764+
That ends our class declaration. The implementation is simply
765+
inlined in the same file lower down. First our init and cleanup functions:
766+
767+
```
768+
void TestQgsRasterLayer::initTestCase()
769+
{
770+
// init QGIS's paths - true means that all path will be inited from prefix
771+
QString qgisPath = QCoreApplication::applicationDirPath ();
772+
QgsApplication::setPrefixPath(qgisPath, TRUE);
773+
#ifdef Q_OS_LINUX
774+
QgsApplication::setPkgDataPath(qgisPath + "/../share/qgis");
775+
#endif
776+
//create some objects that will be used in all tests...
777+
778+
std::cout << "Prefix PATH: " << QgsApplication::prefixPath().toLocal8Bit().data() << std::endl;
779+
std::cout << "Plugin PATH: " << QgsApplication::pluginPath().toLocal8Bit().data() << std::endl;
780+
std::cout << "PkgData PATH: " << QgsApplication::pkgDataPath().toLocal8Bit().data() << std::endl;
781+
std::cout << "User DB PATH: " << QgsApplication::qgisUserDbFilePath().toLocal8Bit().data() << std::endl;
782+
783+
//create a raster layer that will be used in all tests...
784+
QString myFileName (TEST_DATA_DIR); //defined in CmakeLists.txt
785+
myFileName = myFileName + QDir::separator() + "tenbytenraster.asc";
786+
QFileInfo myRasterFileInfo ( myFileName );
787+
mpLayer = new QgsRasterLayer ( myRasterFileInfo.filePath(),
788+
myRasterFileInfo.completeBaseName() );
789+
}
790+
791+
void TestQgsRasterLayer::cleanupTestCase()
792+
{
793+
delete mpLayer;
794+
}
795+
796+
```
797+
798+
The above init function illustrates a couple of interesting things.
799+
800+
1. I needed to manually set the QGIS application data path so that
801+
resources such as srs.db can be found properly.
802+
2. Secondly, this is a data driven test so we needed to provide a
803+
way to generically locate the 'tenbytenraster.asc file. This was
804+
achieved by using the compiler define **TEST_DATA_PATH**. The
805+
define is created in the CMakeLists.txt configuration file under
806+
<QGIS Source Root>/tests/CMakeLists.txt and is available to all
807+
QGIS unit tests. If you need test data for your test, commit it
808+
under <QGIS Source Root>/tests/testdata. You should only commit
809+
very small datasets here. If your test needs to modify the test
810+
data, it should make a copy of if first.
811+
812+
Qt also provides some other interesting mechanisms for data driven
813+
testing, so if you are interested to know more on the topic, consult
814+
the Qt documentation.
815+
816+
Next lets look at our functional test. The isValid() test simply
817+
checks the raster layer was correctly loaded in the initTestCase.
818+
QVERIFY is a Qt macro that you can use to evaluate a test condition.
819+
There are a few other use macros Qt provide for use in your tests
820+
including:
821+
822+
```
823+
QCOMPARE ( actual, expected )
824+
QEXPECT_FAIL ( dataIndex, comment, mode )
825+
QFAIL ( message )
826+
QFETCH ( type, name )
827+
QSKIP ( description, mode )
828+
QTEST ( actual, testElement )
829+
QTEST_APPLESS_MAIN ( TestClass )
830+
QTEST_MAIN ( TestClass )
831+
QTEST_NOOP_MAIN ()
832+
QVERIFY2 ( condition, message )
833+
QVERIFY ( condition )
834+
QWARN ( message )
835+
```
836+
837+
Some of these macros are useful only when using the Qt framework
838+
for data driven testing (see the Qt docs for more detail).
839+
840+
```
841+
void TestQgsRasterLayer::isValid()
842+
{
843+
QVERIFY ( mpLayer->isValid() );
844+
}
845+
```
846+
847+
Normally your functional tests would cover all the range of
848+
functionality of your classes public API where feasible. With our
849+
functional tests out the way, we can look at our regression test example.
850+
851+
Since the issue in bug #832 is a misreported cell count, writing
852+
our test if simply a matter of using QVERIFY to check that the
853+
cell count meets the expected value:
854+
855+
```
856+
void TestQgsRasterLayer::regression832()
857+
{
858+
QVERIFY ( mpLayer->getRasterXDim() == 10 );
859+
QVERIFY ( mpLayer->getRasterYDim() == 10 );
860+
// regression check for ticket #832
861+
// note getRasterBandStats call is base 1
862+
QVERIFY ( mpLayer->getRasterBandStats(1).elementCountInt == 100 );
863+
}
864+
```
865+
866+
With all the unit test functions implemented, there one final thing we
867+
need to add to our test class:
868+
869+
```
870+
QTEST_MAIN(TestQgsRasterLayer)
871+
#include "moc_testqgsrasterlayer.cxx"
872+
```
873+
874+
The purpose of these two lines is to signal to Qt's moc that his is a
875+
QtTest (it will generate a main method that in turn calls each test funtion.
876+
The last line is the include for the MOC generated sources. You should
877+
replace 'testqgsrasterlayer' with the name of your class in lower case.
878+
580879
== Adding your unit test to CMakeLists.txt ==
581880

881+
Adding your unit test to the build system is simply a matter of editing
882+
the CMakeLists.txt in the test directory, cloning one of the existing
883+
test blocks, and then search and replacing your test class name into it.
884+
For example:
885+
886+
```
887+
#
888+
# QgsRasterLayer test
889+
#
890+
SET(qgis_rasterlayertest_SRCS testqgsrasterlayer.cpp)
891+
SET(qgis_rasterlayertest_MOC_CPPS testqgsrasterlayer.cpp)
892+
QT4_WRAP_CPP(qgis_rasterlayertest_MOC_SRCS ${qgis_rasterlayertest_MOC_CPPS})
893+
ADD_CUSTOM_TARGET(qgis_rasterlayertestmoc ALL DEPENDS ${qgis_rasterlayertest_MOC_SRCS})
894+
ADD_EXECUTABLE(qgis_rasterlayertest ${qgis_rasterlayertest_SRCS})
895+
ADD_DEPENDENCIES(qgis_rasterlayertest qgis_rasterlayertestmoc)
896+
TARGET_LINK_LIBRARIES(qgis_rasterlayertest ${QT_LIBRARIES} qgis_core)
897+
INSTALL(TARGETS qgis_rasterlayertest RUNTIME DESTINATION ${QGIS_BIN_DIR})
898+
ADD_TEST(qgis_rasterlayertest ${QGIS_BIN_DIR}/qgis_rasterlayertest)
899+
```
900+
901+
I'll run through these lines briefly to explain what they do, but if
902+
you are not interested, just clone the block, search and replace e.g.
903+
904+
```
905+
:'<,'>s/rasterlayer/mynewtest/g
906+
```
907+
908+
Lets look a little more in detail at the individual lines. First we
909+
define the list of sources for our test. Since we have only one source file
910+
(following the methodology I described above where class declaration and
911+
definition are in the same file) its a simple statement:
912+
913+
```
914+
SET(qgis_rasterlayertest_SRCS testqgsrasterlayer.cpp)
915+
```
916+
917+
Since our test class needs to be run through the Qt meta object compiler (moc)
918+
we need to provide a couple of lines to make that happen too:
919+
920+
```
921+
SET(qgis_rasterlayertest_MOC_CPPS testqgsrasterlayer.cpp)
922+
QT4_WRAP_CPP(qgis_rasterlayertest_MOC_SRCS ${qgis_rasterlayertest_MOC_CPPS})
923+
ADD_CUSTOM_TARGET(qgis_rasterlayertestmoc ALL DEPENDS ${qgis_rasterlayertest_MOC_SRCS})
924+
```
925+
926+
Next we tell cmake that it must make an executeable from the test class.
927+
Remember in the previous section on the last line of the class implementation
928+
I included the moc outputs directly into our test class, so that will
929+
give it (among other things) a main method so the class can be
930+
compiled as an executeable:
931+
932+
```
933+
ADD_EXECUTABLE(qgis_rasterlayertest ${qgis_rasterlayertest_SRCS})
934+
ADD_DEPENDENCIES(qgis_rasterlayertest qgis_rasterlayertestmoc)
935+
```
936+
937+
Next we need to specify any library dependencies. At the moment classes
938+
have been implemented with a catch-all QT_LIBRARIES dependency, but I will
939+
be working to replace that with the specific Qt libraries that each class
940+
needs only. Of course you also need to link to the relevant qgis
941+
libraries as required by your unit test.
942+
943+
```
944+
TARGET_LINK_LIBRARIES(qgis_rasterlayertest ${QT_LIBRARIES} qgis_core)
945+
```
946+
947+
Next I tell cmake to the same place as the qgis binaries itself. This
948+
is something I plan to remove in the future so that the tests can
949+
run directly from inside the source tree.
950+
951+
```
952+
INSTALL(TARGETS qgis_rasterlayertest RUNTIME DESTINATION ${QGIS_BIN_DIR})
953+
```
954+
955+
Finally here is where the best magic happens - we register the class with
956+
ctest. If you recall in the overview I gave in the beginning of this
957+
section we are using both QtTest and CTest together. To recap, **QtTest** adds a
958+
main method to your test unit and handles calling your test methods within
959+
the class. It also provides some macros like QVERIFY that you can use as
960+
to test for failure of the tests using conditions. The output from
961+
a QtTest unit test is an executeable which you can run from the command line.
962+
However when you have a suite of tests and you want to run each executeable
963+
in turn, and better yet integrate running tests into the build process,
964+
the **CTest** is what we use. The next line registers the unit test with
965+
CMake / CTest.
966+
967+
```
968+
ADD_TEST(qgis_rasterlayertest ${QGIS_BIN_DIR}/qgis_rasterlayertest)
969+
```
970+
971+
The last thing I should add is that if your test requires optional
972+
parts of the build process (e.g. Postgresql support, GSL libs, GRASS etc.),
973+
you should take care to enclose you test block inside a IF () block
974+
in the CMakeLists.txt file.
975+
976+
582977
== Building your unit test ==
583978

979+
To build the unit test you need only to make sure that ENABLE_TESTS=true
980+
in the cmake configuration. There are two ways to do this:
981+
982+
1. Run ccmake .. (cmakesetup .. under windows) and interactively set
983+
the ENABLE_TESTS flag to ON.
984+
1. Add a command line flag to cmake e.g. cmake -DENABLE_TESTS=true ..
985+
986+
Other than that, just build QGIS as per normal and the tests should build
987+
too.
988+
584989
== Run your tests ==
585990

991+
The simplest way to run the tests is as part of your normal build process:
992+
993+
```
994+
make && make install && make test
995+
```
996+
997+
The make test command will invoke CTest which will run each test that
998+
was registered using the ADD_TEST CMake directive described above. Typical
999+
output from make test will look like this:
1000+
1001+
```
1002+
Running tests...
1003+
Start processing tests
1004+
Test project /Users/tim/dev/cpp/qgis/build
1005+
1/ 3 Testing qgis_applicationtest ***Exception: Other
1006+
2/ 3 Testing qgis_filewritertest *** Passed
1007+
3/ 3 Testing qgis_rasterlayertest *** Passed
1008+
1009+
0% tests passed, 3 tests failed out of 3
1010+
1011+
The following tests FAILED:
1012+
1 - qgis_applicationtest (OTHER_FAULT)
1013+
Errors while running CTest
1014+
make: *** [test] Error 8
1015+
```
1016+
1017+
If a test fails, you can use the ctest command to examine more
1018+
closely why it failed. User the -R option to specify a regex for
1019+
which tests you want to run and -V to get verbose output:
1020+
1021+
```
1022+
[build] ctest -R appl -V
1023+
Start processing tests
1024+
Test project /Users/tim/dev/cpp/qgis/build
1025+
Constructing a list of tests
1026+
Done constructing a list of tests
1027+
Changing directory into /Users/tim/dev/cpp/qgis/build/tests/src/core
1028+
1/ 3 Testing qgis_applicationtest
1029+
Test command: /Users/tim/dev/cpp/qgis/build/tests/src/core/qgis_applicationtest
1030+
********* Start testing of TestQgsApplication *********
1031+
Config: Using QTest library 4.3.0, Qt 4.3.0
1032+
PASS : TestQgsApplication::initTestCase()
1033+
Prefix PATH: /Users/tim/dev/cpp/qgis/build/tests/src/core/../
1034+
Plugin PATH: /Users/tim/dev/cpp/qgis/build/tests/src/core/..//lib/qgis
1035+
PkgData PATH: /Users/tim/dev/cpp/qgis/build/tests/src/core/..//share/qgis
1036+
User DB PATH: /Users/tim/.qgis/qgis.db
1037+
PASS : TestQgsApplication::getPaths()
1038+
Prefix PATH: /Users/tim/dev/cpp/qgis/build/tests/src/core/../
1039+
Plugin PATH: /Users/tim/dev/cpp/qgis/build/tests/src/core/..//lib/qgis
1040+
PkgData PATH: /Users/tim/dev/cpp/qgis/build/tests/src/core/..//share/qgis
1041+
User DB PATH: /Users/tim/.qgis/qgis.db
1042+
QDEBUG : TestQgsApplication::checkTheme() Checking if a theme icon exists:
1043+
QDEBUG : TestQgsApplication::checkTheme()
1044+
/Users/tim/dev/cpp/qgis/build/tests/src/core/..//share/qgis/themes/default//mIconProjectionDisabled.png
1045+
FAIL! : TestQgsApplication::checkTheme() '!myPixmap.isNull()' returned FALSE. ()
1046+
Loc: [/Users/tim/dev/cpp/qgis/tests/src/core/testqgsapplication.cpp(59)]
1047+
PASS : TestQgsApplication::cleanupTestCase()
1048+
Totals: 3 passed, 1 failed, 0 skipped
1049+
********* Finished testing of TestQgsApplication *********
1050+
-- Process completed
1051+
***Failed
1052+
1053+
0% tests passed, 1 tests failed out of 1
1054+
1055+
The following tests FAILED:
1056+
1 - qgis_applicationtest (Failed)
1057+
Errors while running CTest
1058+
1059+
```
1060+
1061+
Well that concludes this section on writing unit tests in QGIS. We hope you
1062+
will get into the habit of writing test to test new functionality and to
1063+
check for regressions. Some aspects of the test system (in particular the
1064+
CMakeLists.txt parts) are still being worked on so that the testing framework
1065+
works in a truly platform way. I will update this document as things progress.
1066+
5861067
= Authors =
5871068

5881069
* Tim Sutton (author and editor)

0 commit comments

Comments
 (0)
Please sign in to comment.