Tests unitaires

À partir de novembre 2007 nous exigeons que les nouvelles fonctionnalités de la branche master soient accompagnées d’un test unitaire. Nous avions initialement limité cette exigence à la partie qgis_core et nous allons étendre ce point aux autres parties du code une fois que les développeurs se seront familiarisés avec les procédures des tests unitaires, expliquées dans les sections qui suivent.

L’environnement de test de QGIS: un aperçu

Les tests unitaires sont utilisés en combinant QTestLib (la bibliothèque de test de Qt) et CTest (l’environnement de compilation et de tests faisant partie du processus de construction CMake). Voici un aperçu de ce processus avant de rentrer dans les détails:

  • Prenons du code que vous voulez tester, par exemple, une classe ou une fonction. Les partisans de la programmation Extreme suggèrent que l’écriture du code ne commence pas tant que vous n’avez pas lancé la construction des tests. Ainsi, une fois que vous avez commencé à implémenter votre code, vous pouvez immédiatement valider chaque nouvelle partie fonctionnelle avec vos tests. Dans la pratique, vous devrez sans doute écrire des tests pour des parties déjà en place dans QGIS car nous avons lancé l’environnement de test bien après que la logique de l’application ait été déjà implémentée.

  • Créez un test unitaire. Tout se passe dans /tests/src/core dans le cas d’une bibliothèque du coeur du projet. Le test est essentiellement un client qui créé une instance de la classe et l’appelle avec des méthodes de classe. Cela permet de vérifier que le retour de chaque méthode renvoie bien la valeur attendue. Si un seul des appels échoue, le test sera également en échec.

  • Incluez les macros QTestLib dans votre classe de test. Cette macro est prise en compte par le compilateur de méta-objets Qt (moc) et elle transformera votre classe de test en application exécutable.

  • Ajoutez une section au fichier CMakeLists.txt dans le répertoire des tests qui construira votre test.

  • Assurez-vous d’avoir la variable ENABLE_TESTING activée dans ccmake / cmakesetup. Cela permettra de s’assurer que vos tests seront compilés lorsque vous lancerez make.

  • Vous pouvez ajouter optionnellement des données de test dans le répertoire /tests/testdata si vos tests ont besoin de données externes (ex: besoin d’accéder à un fichier Shape). Ces données de test doivent être les plus compactes possibles et elles doivent, dans la mesure du possible, utiliser les jeux de données déjà présents. Vos tests ne doivent jamais modifier directement ces données mais plutôt faire une copie temporaire dans un répertoire en cas de besoin.

  • Compilez vos sources et installez. Vous pouvez le faire avec le traditionnel make && (sudo) make install.

  • Lancez les tests. Vous pouvez le faire simplement avec la commande make test après avoir fait un make install même si d’autres approches donnant plus de contrôle sur le déroulement des tests sont décrites plus loin.

Maintenant que nous venons de voir comment faire, je vais rentrer un peu plus dans les détails. J’ai déjà réalisé une grande partie de la configuration dans CMake et dans les autres parties des fichiers sources de manière à ce que vous puissiez vous concentrer sur la partie facile: écrire des tests unitaires !

Créer un test unitaire

Créer un test unitaire est facile. Généralement, vous pouvez en créer un en écrivant un simple fichier .cpp (aucun fichier d’en-tête .h n’est utilisé) et implémenter vos méthodes de test sous forme de méthodes publics ne renvoyant rien. Je vais utiliser un test simple pour la classe QgsRasterLayer tout au long de la section à suivre. Par convention, le test sera nommétel que la classe, avec le préfixe ‘Test’. Notre implémentation de test ira donc dans un fichier nommé testqgsrasterlayer.cpp et la classe sera nommée TestQgsRasterLayer. Ajoutons d’abord notre bannière de droit d’auteur standardisée:

/***************************************************************************
 testqgsvectorfilewriter.cpp
 --------------------------------------
  Date : Friday, Jan 27, 2015
  Copyright: (C) 2015 by Tim Sutton
  Email: tim@kartoza.com
 ***************************************************************************
 *
 * 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.
 *
 ***************************************************************************/

Ensuite, nous ajoutons les fichiers d’en-tête nécessaires à l’exécution du test. Il en existe un que tous les tests doivent avoir:

#include <QtTest/QtTest>

Ensuite, vous pouvez implémenter votre classe normalement en ajoutant les en-têtes dont vous pourrez avoir besoin:

//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>

Etant donné que nous combinons, dans un seul fichier, la déclaration et l’implémentation de la classe, nous ajoutons ensuite la déclaration de la classe. Nous ajoutons alors la documentation doxygen. Chaque test doit être correctement documenté. Nous utilisons la directive doxygen ingroup de manière à ce que tous les tests unitaires apparaissent dans un seul module dans la documentation générée par Doxygen. Vient ensuite une description résumée du test unitaire, la classe doit hériter de QObject et inclure la macro Q_OBJECT.

/** \ingroup UnitTests
 * This is a unit test for the QgsRasterLayer class.
 */

class TestQgsRasterLayer: public QObject
{
    Q_OBJECT

Toutes nos méthodes de test sont implémentées sous la forme d’un emplacement privé. Le cadriciel QtTest appellera séquentiellement chaque méthode d’emplacement privé dans la classe de test. Il existe quatre méthodes ‘spéciales’ qui seront appelées au début du test unitaire (initTestCase) ou à la fin du test (cleanupTestCase). Avant le lancement de chaque méthode du test, la méthode init() est appelée. La méthode cleanup() est appelée à la fin de chaque méthode du tests. Ces méthodes sont intéressantes car elles vous permettent d’allouer et de nettoyer des ressources avant l’exécution de chaque test ainsi que pour l’intégralité du test unitaire.

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();

Viennent ensuite vos méthodes de tests qui ne doivent avoir aucun paramètre et renvoyer ‘void’. Les méthodes sont appelées dans l’ordre de la déclaration. J’implémente ici deux méthodes qui illustrent deux types de tests. Dans le premier cas, je veux vérifier que les différentes parties de la classe fonctionnent et je peux utiliser une approche fonctionnelle. Une fois de plus, les partisans de l’extreme programming nous recommanderaient de créer ces tests avant d’implémenter la classe. Vous pouvez alors conduire vos tests unitaires au fur et à mesure de l’implémentation de votre classe. Vous devez écrire de plus en plus de fonctions de test à mesure que l’écriture de la classe s’améliore et une fois que le test unitaire est confirmé, votre nouvelle classe est terminée, tout en étant accompagnée d’un moyen de vérification répétable.

Généralement vos tests unitaires doivent couvrir uniquement la partie publique de l’API de votre classe et vous n’avez pas besoin d’écrire des tests pour les méthodes d’accès et les modificateurs. S’il devait arriver qu’une méthode d’accès ou de modification ne fonctionne pas comme prévu, vous devriez normalement implémenter un test de régression pour gérer ce cas (voir plus loin).

//
// Functional Testing
//

/** Check if a raster is valid. */
void isValid();

// more functional tests here ...

Ensuite, nous implémentons nos tests de non-régression. Les tests de non-régression doivent être écrits pour répliquer les conditions d’un bogue donné. Par exemple, j’ai reçu récemment un rapport de bogue par courrier électronique indiquant que le décompte des cellules de raster avait une erreur de 1, invalidant les statistiques des bandes de raster. J’ai ouvert un rapport de bogue (ticket #832) et j’ai alors créé un test de non-régression qui répliquait le bogue en utilisant un jeu de données assez compact (un raster de 10x10 cellules). J’ai ensuite lancé le test pour vérifier qu’il échouait bien (le décompte de cellule valait 99 au lieu de 100). J’ai ensuite corrigé le bogue et relancé le test unitaire qui, cette fois, est passé. J’ai déposé le test de non-régression ainsi que le correctif du bogue dans les sources. À partir de maintenant, si un futur développeur casse ce travail dans le code, nous pourrons directement identifier la régression dans le code. Lancer nos tests nous permettra de nous assurer que nos changements n’ont pas d’effets secondaires non prévus, comme casser des fonctionnalités existantes.

Il existe également une autre avancée offerte par les tests de non-régression: ils peuvent vous permettre de gagner du temps. Si vous avez déjà corrigé un bogue qui implique du changement de code et que vous avez lancé l’application et réalisé une série de tests manuels pour répliquer le problème, vous pourrez comprendre facilement que l’implémentation d’un test de non-régression avant la correction du bogue vous permettra d’automatiser cette correction de manière efficace.

Pour implémenter votre test de non-régression, il est préférable de suivre la convention de nommage du type regression pour les fonctions du test. S’il n’existe aucun ticket Redmin pour le bogue, vous devez en créer un d’abord. Cette approche permet de trouver plus facilement de l’information sur le test de non-régression qui échoue.

//
// 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 ...

En dernier lieu, vous pouvez déclarer en privé n’importe quelle propriété ainsi que les méthodes helper parmi les membres de la classe dans votre test unitaire. Dans notre cas, je vais déclarer un pointeur QgsRasterLayer * qui pourra être utilisé dans n’importe quelle méthode du test. La couche raster sera créée dans la fonction initTestCase() qui est lancée avant tous les autres tests; elle sera détruite par cleanupTestCase() qui sera lancée après les autres tests. En déclarant de manière privée les méthodes helper (qui peuvent être appelées par plusieurs fonctions de test), vous vous assurerez qu’elles ne seront pas automatiquement lancées par l’exécutable QTest créé lors de la compilation des tests.

  private:
    // Here we have any data structures that may need to
    // be used in many test cases.
    QgsRasterLayer * mpLayer;
};

Ceci termine la déclaration de notre classe. L’implémentation est simplement incluse dans le même fichier plus bas. D’abord la fonction d’initialisation puis la fonction de nettoyage:

void TestQgsRasterLayer::initTestCase()
{
  // init QGIS's paths - true means that all path 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;
}

La fonction d’initialisation ci-dessus illustre quelques points d’intérêt.

  1. J’avais besoin de paramétrer manuellement le chemin vers l’application QGIS de manière à ce que certaines ressources (comme le fichier srs.db) soient accessibles correctement.

  2. De plus, il s’agit d’un test sur les données et nous avons donc besoin de fournir un moyen de localiser de manière générique le fichier tenbytenraster.asc. Il suffit d’utiliser la définition du compilateur TEST_DATA_PATH. Cette définition est créée dans le fichier de configuration CMakeLists.txt dans le répertoire /tests/CMakeLists.txt et elle est disponible pour tous les tests unitaires QGIS. Si vous avez besoin de données pour votre test, ajoutez les (via un commit) dans le répertoire /tests/testdata. Vous devriez uniquement ajouter des données de petite taille dans ce répertoire. Si votre test doit modifier des données, vous devriez copier ces dernière d’abord.

Qt fournit également d’autres mécanismes d’intéressants pour les tests sur les données. Si vous désirez en savoir davantage sur le sujet, consultez la documentation Qt.

Étudions ensuite notre test fonctionnel. Le test isValid() vérifie simplement que la couche raster a été correctement chargée dans initTestCase. QVERIFY est une macro Qt que vous pouvez utiliser pour évaluer une condition de test. Il existe quelques autres macros fournies par Qt que vous pouvez utiliser dans vos tests:

  • 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 )

Certaines de ces macros sont utiles uniquement lorsque vous utilisez le cadriciel Qt pour les tests sur les données (consultez la documentation Qt pour plus de détails).

void TestQgsRasterLayer::isValid()
{
  QVERIFY ( mpLayer->isValid() );
}

Normalement, vos tests fonctionnels devraient couvrir la totalité des fonctionnalités de vos classes publiques d’API, lorsque c’est possible. Maintenant que nos tests fonctionnels sont couverts, nous pouvons nous intéresser à notre exemple de test de non-régression.

Étant donné que le bogue #832 concerne un décompte de cellules incorrect, l’écriture de notre test est simplement une question d’utilisation de QVERIFY pour vérifier que le décompte des cellules correspond bien à la valeur attendue:

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 );
}

Une fois que les fonctions du test unitaire sont implémentées, il nous reste un élément final à ajouter à notre classe de test:

QTEST_MAIN(TestQgsRasterLayer)
#include "testqgsrasterlayer.moc"

L’objectif de ces deux lignes est d’indiquer au cmo Qt qu’il s’agit d’un QtTest (qui générera une méthode main qui fera à son tour l’appel à chaque fonction du test). La dernière ligne correspond à l’inclusion pour les sources générées par le compilateur de meta-objet Qt. Vous devriez remplacer ‘testqgsrasterlayer’ par le nom de votre classe en caractères minuscules.

Ajouter votre test unitaire à CMakeLists.txt

Ajouter votre test unitaire au système de compilation consiste simplement à éditer le fichier CMakeLists.txt dans le répertoire de test, en clonant un des blocs de texte existants et en remplaçant l’ancien nom par le nom de votre classe. Par exemple:

# QgsRasterLayer test
ADD_QGIS_TEST(rasterlayertest testqgsrasterlayer.cpp)

La macro ADD_QGIS_TEST expliquée

Je vais brièvement expliquer ce qu’elles font mais si vous n’êtes pas intéressé, vous pouvez simplement reproduire les étapes expliquées dans les sections ci-dessus.

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 Mac OS X, 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)

Etudions plus en détails chaque ligne. Nous définissons d’abord la liste des sources de notre test. Étant donné que nous avons un seul fichier source (en suivant la méthodologie décrite au dessus ou la déclaration de la classe et la définition sont dans un seul fichier), il s’agit d’une simple formulation:

SET(qgis_${testname}_SRCS ${testsrc} ${util_SRCS})

Etant donné que notre classe doit être lancée à travers le compilateur de méta-objet Qt (cmo), nous devons fournir quelques lignes en plus pour déclencher ce comportement:

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})

Ensuite, nous indiquons à cmake qu’il doit produire un exécutable à partir de la classe de test. Souvenez-vous dans la section précédente, de la dernière ligne de l’implémentation de la classe où j’avais inclus les sorties vers le cmo directement dans la classe de test de manière à ce qu’il produise (entre-autres) une méthode main pour que la classe puisse être compilée comme un exécutable:

ADD_EXECUTABLE(qgis_${testname} ${qgis_${testname}_SRCS})
ADD_DEPENDENCIES(qgis_${testname} qgis_${testname}moc)

Ensuite, nous devons indiquer les dépendances vers des bibliothèques externes. Pour le moment, les classes ont été implémentées avec une dépendance générale QT_LIBRARIES mais je vais travailler ce point en remplaçant ce paramètre par les bibliothèques Qt spécifiques dont chaque classe à besoin. Bien entendu, vous devez réaliser une opération de lien (link) vers les bibliothèques QGIS concernées par votre test unitaire.

TARGET_LINK_LIBRARIES(qgis_${testname} ${QT_LIBRARIES} qgis_core)

Ensuite, j’indique à cmake d’installer les tests au même endroit que les binaires de QGIS. Il s’agit d’un point que je pense supprimer dans le futur de manière à ce que les tests puissent être lancés directement depuis les sources.

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 Mac OS X, 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)

Enfin, ce qui est au dessus utilise ADD_TEST pour enregistrer le test avec cmake/ctest. C’est ici que le côté magique opère: nous enregistrons la classe avec ctest. Si vous vous rappelez de l’aperçu donné dans le début de ce chapitre, nous utilisons à la fois QtTest et CTest ensemble. Pour résumer, QtTest ajoute une méthode main à votre test unitaire et gère les appels aux méthodes du test au sein de la classe. Il fournit également des macros comme QVERIFY que vous pouvez utiliser comme test d’échec avec des conditions. La sortie d’un test unitaire QtTest est un exécutable que vous pouvez lancer depuis la ligne de commande. Néanmoins, lorsque vous disposez d’une suite de tests et que vous souhaitez lancer les exécutables les uns à la suite des autres ou que vous voulez intégrer le lancement des tests à la chaîne de compilation, CTest est alors utilisé.

Compiler votre test unitaire

Pour compiler notre test unitaire, vous devez vous assurer que ENABLE_TESTS=true est dans la configuration de CMake. Il existe deux moyens pour y parvenir:

  1. Lancez cmake .. (ou cmakesetup .. sous MS-Windows) et positionnez interactivement l’option ENABLE_TESTS à ON.

  2. Ajoutez une option à la ligne de commande de cmake; ex: cmake -DENABLE_TESTS=true ..

À part cela, compilez QGIS comme d’habitude et les tests devraient également se compiler.

Lancer vos tests

Le moyen le plus simple de lancer les tests est de les inclure directement dans le processus de compilation:

make && make install && make test

La commande make test invoquera CTest qui lancera chaque test enregistré en utilisant la directive CMake ADD_TEST décrite plus haut. Voici à quoi ressemble la sortie courante de make test:

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

Si un test échoue, vous pouvez utiliser la commande ctest pour examiner plus en détails pourquoi il a échoué. Utilisez l’option -R pour indiquer une expression rationnelle pour désigner les tests que vous voulez lancer et -V pour activer la sortie verbeuse.

$ 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

Well that concludes this section on writing unit tests in QGIS. We hope you will get into the habit of writing test to test new functionality and to check for regressions. Some aspects of the test system (in particular the CMakeLists.txt parts) are still being worked on so that the testing framework works in a truly platform way. I will update this document as things progress.