6. Pruebas unitarias

A noviembre de 2007, requerimos que todas las nuevas características que vayan a entrar al master a ser acompañados con una unidad de prueba. Inicialmente, hemos limitado a este requisito qgis_core, y ampliaremos este requerimiento a otras partes del código base una vez que la gente está familiarizada con los procedimientos para las pruebas unitarias que se explican en las siguientes secciones que siguen.

6.1. La infraestructura de prueba de QGIS - una visión general

La prueba de unidad se lleva a cabo utilizando una combinación de QTestLib (la biblioteca de pruebas Qt) y CTest (una infraestructura para compilar y ejecutar pruebas como parte del proceso de construcción de CMake). Hagamos una vista general del proceso antes de ahondar en los detalles:

  1. Hay algún código que desea probar, p. Ej. una clase o función. Los defensores de la programación extrema sugieren que el código ni siquiera debería escribirse cuando empieza a construir sus pruebas, y luego, cuando implemente su código, puede validar de inmediato con su prueba, cada parte funcional nueva que agregue. En la práctica, probablemente necesitará escribir pruebas para el código preexistente en QGIS ya que estamos comenzando con una estructura de prueba mucho después de que ya se ha implementado mucha lógica de la aplicación.

  2. Creas una unidad de pruebas. Esto ocurre en <QGIS Source Dir>/tests/src/core en el caso de la librería núcleo. La prueba es básicamente un cliente que crea una instancia de una clase y llama a algunos métodos de esa clase. Comprobará los resultados de cada método para asegurar que coincide con los valores esperados. Si alguna de las llamadas falla, la prueba fallará.

  3. Incluyes macros de QtTestLib en tu clase de pruebas. Este macro es procesado por el meta object compiler (moc) de Qt y convierte tu clase de prueba en una aplicación ejecutable.

  4. Se agrega una sección a la CMakeLists.txt en el directorio de pruebas que construirá su prueba.

  5. Te aseguras de que ENABLE_TESTING está activado en ccmake / cmakesetup. Esto asegura que tus pruebas son realmente compiladas cuando escribas make.

  6. Se añaden opcionalmente datos de prueba a <QGIS Source Dir>/tests/testdata si su prueba utiliza la información (por ejemplo, necesita cargar un archivo shape). Estos datos de prueba deben ser lo más pequeñas posible y siempre que sea posible se debe utilizar las bases de datos existentes ya allí. Sus pruebas nunca deben modificar estos datos in situ, sino más bien debe hacer una copia temporal en algún lugar si es necesario.

  7. Compilas tu código fuente e instalas. Para ello, utiliza el procedimiento normal make && (sudo) make install.

  8. Usted ejecuta sus pruebas. Esto normalmente se hace simplemente haciendo``make test`` después del paso make install, aunque explicaremos otros enfoques que ofrecen un control más detallado sobre las pruebas en ejecución.

Justo con ese panorama en mente, ahondaremos en un poco más de detalle. Ya hemos hecho la mayor parte de la configuración por ti en CMake y otros lugares en el árbol de código fuente por lo que todo lo que necesitas hacer es la parte fácil - ¡escribir las pruebas unitarias!

6.2. Creando una prueba de unidad

Crear una unidad de prueba es fácil - por lo general, esto se hace sólo por la creación de un único archivo .cpp (no se utiliza el archivo .h) e implementar todos tus métodos de prueba cómo métodos públicos que devuelven nulos. Vamos a usar una clase de prueba simple QgsRasterLayer en la siguiente sección para ilustrar. Por convención nombraremos nuestra prueba con el mismo nombre que la clase que estamos evaluando, pero con el prefijo “Test”. De manera que la implementación de prueba va en un archivo llamado testqgsrasterlayer.cpp de prueba y la propia clase será TestQgsRasterLayer. Primero añadimos nuestro titular estándar de copyright:

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

A continuación empezamos nuestros includes necesarios para las pruebas que pensamos correr. Hay un include especial que todos las pruebas deben incluir:

#include <QtTest/QtTest>

De este punto en adelante simplemente continua implementando tu clase como de costumbre, trayendo cualquier header que puedas necesitar:

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

Como estamos estamos combinando la declaración y la implementación de la clase en un solo archivo, la declaración de la clase viene a continuación. Comenzamos con nuestra documentación doxygen. Cada prueba debe estar adecuadamente documentada. usamos la directiva ingroup de doxygen de manera que todos los UnitTests aparezcan como un módulo en la documentación Doxygen generada. Después de esto viene una corta descripción de la prueba unitaria y la clase debe heredar de QObject e incluir el macro Q_OBJECT.

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

class TestQgsRasterLayer: public QObject
{
    Q_OBJECT

Todos nuestros métodos de prueba están implementados como «slots» privados. El framework QtTest llamará secuencialmente cada método «slot» privado en la clase de pruebas. Existen cuatro métodos «especiales» que si son implementados serán llamados al inicio de la prueba unitaria (initTestCase), y al final de la prueba unitaria (cleanupTestCase). Antes de que cada método de prueba sea llamado, el método init() será llamado y después de que cada método de prueba sea llamado, el método cleanup() será llamado. Estos métodos son útiles en cuanto a que te permiten asignar y limpiar recursos antes de correr cada prueba, y la prueba unitaria en su conjunto.

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

A continuación vienen tus métodos de prueba, todos los cuales no deben tener parámetros y no deben devolver nada (void / vacío).

En el primer caso, generalmente queremos probar si las diversas partes de la clase están funcionando. Podemos usar un enfoque de prueba funcional. Una vez más, los programadores extremos recomendarían escribir estas pruebas antes de implementar la clase. Luego, a medida que avanza en la implementación de su clase, ejecutará iterativamente las pruebas de la unidad. Cada vez más funciones de prueba deben completarse con éxito a medida que avanza el trabajo de implementación de su clase, y cuando pasa la prueba de la unidad completa, su nueva clase está terminada y ahora está completa con una forma repetible de validarla.

Por lo general, sus pruebas de unidad solo cubrirían el API público de su clase, y normalmente no necesita escribir pruebas para accesores y mutadores. Si ocurriera que un descriptor de acceso o mutador no funciona como se espera, normalmente implementaría una: ref: prueba de regresión <regression_test> para verificar esto.

//
// Functional Testing
//

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

// more functional tests here ...

6.2.1. Implementando una prueba de regresión

Luego implementamos nuestras pruebas de regresión. Las pruebas de regresión deben implementarse para replicar las condiciones de un error en particular. Por ejemplo:

  1. Recibimos un informe por correo electrónico de que el recuento de celdas por rásteres estaba desfasado en 1, eliminando todas las estadísticas para las bandas de ráster.

  2. Abrimos un reporte de error (ticket #832)

  3. Creamos una prueba de regresión que replica el error usando un set de datos pequeño (un ráster de 10x10)

  4. Realizamos la prueba, verificando que efectivamente fallaba (el conteo de celdas fue de 99 en lugar de 100).

  5. Luego fuimos a arreglar el error y volvimos a clasificar la prueba de la unidad y la prueba de regresión pasó. Sometimos la prueba de regresión junto con la corrección de errores. Ahora, si alguien vuelve a modificar esto en el código fuente en el futuro, podemos identificar de inmediato que el código ha regresado a un estado previo.

    Mejor aún, antes de realizar cualquier cambio en el futuro, ejecutar nuestras pruebas garantizarán que nuestros cambios no tengan efectos secundarios inesperados, como afectar la funcionalidad existente.

Las pruebas de regresión tienen otro beneficio: pueden ahorrarle tiempo. Si alguna vez solucionó un error que implicaba realizar cambios en la fuente, y luego ejecutar la aplicación y realizar una serie de pasos complicados para replicar el problema, será inmediatamente evidente que simplemente implementando su prueba de regresión antes de corregir el error le permitirá automatizar las pruebas para la resolución de errores de manera eficiente.

Para implementar su prueba de regresión, debe seguir la convención de nomenclatura de ** regresión<TicketID>** para sus funciones de prueba. Si no existe un ticket para la regresión, primero debe crear uno. El uso de este enfoque permite a la persona que ejecuta una prueba de regresión fallida ir fácilmente y obtener más información.

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

Finalmente, en su declaración de clase de prueba, puede declarar en privado cualquier miembro de datos y métodos auxiliares que su prueba de unidad pueda necesitar. En nuestro caso, declararemos un QgsRasterLayer * que puede ser utilizado por cualquiera de nuestros métodos de prueba. La capa ráster se creará en la función initTestCase() que se ejecuta antes de cualquier otra prueba, y luego se destruirá usando cleanupTestCase() que se ejecuta después de todas las pruebas. Al declarar los métodos de ayuda (que pueden ser llamados por varias funciones de prueba) de forma privada, puede asegurarse de que el ejecutable QTest que se crea cuando compilamos nuestra prueba no los ejecute automáticamente.

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

Eso termina nuestra declaración de clase. La implementación simplemente está en línea en el mismo archivo más abajo. Primero nuestras funciones init y cleanup:

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 función init anterior ilustra un par de cuestiones interesantes.

  1. Necesitábamos establecer manualmente la ruta de datos de la aplicación QGIS para que recursos como: file:srs.db se puedan encontrar correctamente.

  2. En segundo lugar, esta es una prueba basada en datos, por lo que necesitábamos proporcionar una forma de ubicar genéricamente el archivo: file:tenbytenraster.asc. Esto se logró haciendo que el compilador defina TEST_DATA_PATH. La definición se crea en el archivo de configuración CMakeLists.txt en: archivo:<QGIS Source Root>/tests/CMakeLists.txt y está disponible para todas las pruebas de unidades de QGIS. Si necesita datos de muestra para su prueba, ingresarlos bajo <QGIS Source Root>/tests/testdata. Aquí solo debe confirmar sets de datos muy pequeños. Si su prueba necesita modificar los datos de la prueba, primero debe hacer una copia.

Qt también proporciona algunos mecanismos interesantes para las pruebas basadas en datos, por lo que si está interesado en saber más sobre este tema, consulte la documentación oficial de Qt.

A continuación, veamos nuestra prueba funcional. La prueba isValid () simplemente verifica que la capa ráster se haya cargado correctamente en initTestCase. QVERIFY es un macro de Qt que puede usar para evaluar una condición de prueba. Hay otros macros que Qt proporciona para usar en sus pruebas, que incluyen:

  • QCOMPARE ( actual, esperada )

  • QEXPECT_FAIL ( dataIndex, comment, mode )

  • QFAIL ( mensaje )

  • QFETCH ( tipo, nombre )

  • QSKIP ( descripción, modo )

  • QTEST ( actual, testElement )

  • QTEST_APPLESS_MAIN ( TestClass )

  • QTEST_MAIN ( TestClass )

  • QTEST_NOOP_MAIN ()

  • QVERIFY2 ( condición, mensaje )

  • QVERIFY ( condición )

  • QWARN ( mensaje )

Algunos de estos macros son útiles solo cuando se usa la estructura Qt para pruebas basadas en datos (consulte los documentos de Qt para obtener más detalles).

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

ormalmente, sus pruebas funcionales cubrirían todo el rango de funcionalidad de sus clases de API pública cuando sea posible. Con nuestras pruebas funcionales fuera del camino, podemos ver nuestro ejemplo de prueba de regresión.

Dado que el problema en el error #832 es un recuento incorrecto de celdas, escribir nuestra prueba es simplemente una cuestión de usar QVERIFY para verificar que el recuento de celdas cumpla con el valor esperado:

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

Con todas las funciones de prueba unitarias implementadas, hay una última cosa que debemos agregar a nuestra clase de prueba:

QTEST_MAIN(TestQgsRasterLayer)
#include "testqgsrasterlayer.moc"

El propósito de estas dos líneas es señalar al moc Qt’s que se trata de un QtTest (generará un método principal que a su vez llama a cada función de prueba. La última línea es la inclusión de las fuentes generadas por MOC. Debe reemplazar testqgsrasterlayer con el nombre de tu clase en minúsculas.

6.3. Comparación de imágenes para pruebas de renderizado

La representación de imágenes en diferentes entornos puede producir diferencias sutiles debido a implementaciones específicas de la plataforma (por ejemplo, diferentes tipos de letra y algoritmos de suavizado), a las fuentes disponibles en el sistema y por otras razones oscuras.

Cuando se ejecuta una prueba de renderizado en Travis y falla, busque el enlace del guión en la parte inferior del registro de Travis. Este enlace lo llevará a una página cdash donde puede ver las imágenes renderizadas frente a las esperadas, junto con una imagen de «diferencia» que resalta en rojo los píxeles que no coinciden con la imagen de referencia.

El sistema de prueba de la unidad QGIS tiene soporte para agregar imágenes de «máscara», que se utilizan para indicar cuándo una imagen renderizada puede diferir de la imagen de referencia. Una imagen de máscara es una imagen (con el mismo nombre que la imagen de referencia, pero que incluye un sufijo _mask.png), y debe tener las mismas dimensiones que la imagen de referencia. En una imagen de máscara, los valores de píxel indican cuánto puede diferir ese píxel individual de la imagen de referencia, por lo que un píxel negro indica que el píxel en la imagen renderizada debe coincidir exactamente con el mismo píxel en la imagen de referencia. Un píxel con RGB 2, 2, 2 significa que la imagen renderizada puede variar hasta 2 en sus valores RGB de la imagen de referencia, y un píxel completamente blanco (255, 255, 255) significa que el píxel se ignora efectivamente al comparar Las imágenes esperadas y renderizadas.

Un script de utilidad para generar imágenes de máscara está disponible como``scripts/generate_test_mask_image.py``. Este script se usa pasándole la ruta de una imagen de referencia (p. Ej. tests/testdata/control_images/annotations/expected_annotation_fillstyle/expected_annotation_fillstyle.png) y el camino hacia tu imagen renderizada.

E.g.

scripts/generate_test_mask_image.py tests/testdata/control_images/annotations/expected_annotation_fillstyle/expected_annotation_fillstyle.png /tmp/path_to_rendered_image.png

En su lugar, puede atajar la ruta a la imagen de referencia pasando una parte parcial del nombre de la prueba, p. Ej.

scripts/generate_test_mask_image.py annotation_fillstyle /tmp/path_to_rendered_image.png

(Este acceso directo solo funciona si se encuentra una sola imagen de referencia coincidente. Si se encuentran varias coincidencias, deberá proporcionar la ruta completa a la imagen de referencia).

El script también acepta URL http para la imagen renderizada, por lo que puede copiar directamente una URL de la imagen renderizada desde la página de resultados de cdash y pasarla al script.

Tenga cuidado al generar imágenes de máscara: siempre debe ver la imagen de máscara generada y revisar las áreas blancas de la imagen. Como se ignoran estos píxeles, asegúrese de que estas imágenes en blanco no cubran ninguna parte importante de la imagen de referencia; de lo contrario, la prueba de su unidad no tendrá sentido.

Del mismo modo, puede «blanquear» manualmente las porciones de la máscara si deliberadamente desea excluirlas de la prueba. Esto puede ser útil, p. Ej. para pruebas que combinan la representación de símbolos y texto (como las pruebas de leyenda), donde la prueba unitaria no está diseñada para probar el texto renderizado y no desea que la prueba esté sujeta a diferencias de representación de texto multiplataforma.

Para comparar imágenes en pruebas unitarias QGIS, debe usar la clase `` QgsMultiRenderChecker “” o una de sus subclases.

Para mejorar la robustez de las pruebas, aquí hay algunos consejos:

  1. Desactive el antialiasing si puede, ya que esto minimiza las diferencias de representación entre plataformas.

  2. Asegúrese de que sus imágenes de referencia sean «sólidas» … es decir no tengan líneas de 1 px de ancho u otras características finas, y use fuentes grandes y en negrita (se recomiendan 14 puntos o más).

  3. A veces, las pruebas generan imágenes de tamaño ligeramente diferente (por ejemplo, pruebas de representación de leyendas, donde el tamaño de la imagen depende del tamaño de representación de la fuente, que está sujeto a diferencias entre plataformas). Para tener esto en cuenta, use ``QgsMultiRenderChecker::setSizeTolerance()”” y especifique el número máximo de píxeles que el ancho y la altura de la imagen renderizada difieren de la imagen de referencia.

  4. No use fondos transparentes en las imágenes de referencia (CDash no las admite). En su lugar, use QgsMultiRenderChecker::drawBackground() para dibujar un patrón de tablero para el fondo de la imagen de referencia.

  5. Cuando se requieren fuentes, use la fuente especificada en QgsFontUtils::standardTestFontFamily() («QGIS Vera Sans»).

Si travis informa errores de nuevas imágenes (por ejemplo, debido a diferencias de tipo o suavizado), el script parse_dash_results.py puede ayudarle cuando actualice las máscaras de prueba locales.

6.4. Agregar su prueba de unidad a CMakeLists.txt

Agregar su prueba de unidad al sistema de compilación es simplemente una cuestión de editar el archivo :CMakeLists.txt en el directorio de prueba, clonar uno de los bloques de prueba existentes y luego reemplazar el nombre de su clase de prueba. Por ejemplo:

# QgsRasterLayer test
ADD_QGIS_TEST(rasterlayertest testqgsrasterlayer.cpp)

6.4.1. La macro ADD_QGIS_TEST explicada

Revisaremos estas líneas brevemente para explicar lo que hacen, pero si no está interesado, simplemente siga el paso explicado en la sección anterior.

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)

Veamos un poco más en detalle las líneas individuales. Primero definimos la lista de fuentes para nuestra prueba. Dado que solo tenemos un archivo fuente (siguiendo la metodología descrita anteriormente donde la declaración de clase y la definición están en el mismo archivo) es una declaración simple:

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

Desde nuestra clase de prueba se debe ejecutar a través del compilador objeto meta Qt (moc) que necesitamos para proporcionar un par de líneas para que esto suceda también:

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

A continuación, le decimos a cmake que debe hacer un ejecutable de la clase de prueba. Recuerde que en la sección anterior en la última línea de la implementación de la clase incluimos los resultados de moc directamente en nuestra clase de prueba, por lo que le dará (entre otras cosas) un método principal para que la clase pueda compilarse como un ejecutable:

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

A continuación, debemos especificar las dependencias de la biblioteca. Por el momento, las clases se han implementado con una dependencia general QT_LIBRARIES, pero estaremos trabajando para reemplazar eso con las bibliotecas Qt específicas que cada clase solo necesita. Por supuesto, también debe vincular a las bibliotecas qgis relevantes según lo requiera su prueba unitaria.

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

A continuación, le decimos a cmake que instale las pruebas en el mismo lugar que los binarios qgis. Esto es algo que planeamos eliminar en el futuro para que las pruebas puedan ejecutarse directamente desde el árbol de origen.

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)

Finalmente, lo anterior usa `` ADD_TEST “” para registrar la prueba con cmake / ctest. Aquí es donde sucede la mejor magia: registramos la clase con ctest. Si recuerda en la descripción general que proporcionamos al comienzo de esta sección, estamos usando QtTest y CTest juntos. Para recapitular, QtTest agrega un método principal a su unidad de prueba y se encarga de llamar a sus métodos de prueba dentro de la clase. También proporciona algunas macros como `` QVERIFY “” que puede usar para probar el fracaso de las pruebas usando condiciones. La salida de una prueba de unidad QtTest es un ejecutable que puede ejecutar desde la línea de comandos. Sin embargo, cuando tiene un conjunto de pruebas y desea ejecutar cada ejecutable a su vez, e integrar las pruebas en ejecución en el proceso de compilación, CTest es lo que usamos.

6.5. Construyendo su unidad de prueba

Para construir la prueba unitaria, solo necesita asegurarse de que ``ENABLE_TESTS=true”” en la configuración de cmake. Hay dos maneras de hacer esto:

  1. Ejecute ``ccmake…”” (o ``cmakesetup…”” en Windows) y establezca interactivamente el indicador ``ENABLE_TESTS”” en ``ON””.

  2. Agregue un indicador de línea de comando a cmake, p. Ej.``cmake-DENABLE_TESTS=verdadero…””

Aparte de eso, solo construye QGIS como de costumbre y las pruebas también deberían compilarse.

6.6. Ejecutar las pruebas

La forma más sencilla de ejecutar las pruebas es como parte de su proceso de construcción normal:

make && make install && make test

El comando ``make test”” invocará CTest, que ejecutará cada prueba registrada con la directiva ADD_TEST CMake descrita anteriormente. La salida típica de ``make test”” se verá así:

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 una prueba falla, puede usar el comando ctest para examinar más de cerca por qué falló. Use la opción ``-R “” para especificar una expresión regular para las pruebas que desea ejecutar y ``-V”” para obtener una salida detallada:

$ 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. Ejecutando pruebas individuales

Las pruebas de C++ son aplicaciones comunes. Puede ejecutarlos desde la carpeta de compilación como cualquier ejecutable.

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

Estas pruebas toman también argumentos de línea de comandos. Esto hace posible correr un subconjunto específico de pruebas:

$ ./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. Pruebas unitarias de depuración

Pruebas C++

Para las pruebas unitarias de C++, QtCreator agrega automáticamente objetivos de ejecución, para que pueda iniciarlos desde el depurador.

Si va a * Proyectos * y allí a la pestaña * Construir&ejecutar* -> Escritorio*Ejecutar *, también puede especificar parámetros de línea de comando que permitirán ejecutar un subconjunto de las pruebas dentro de un archivo .cpp en el depurador

Pruebas Python

También es posible iniciar las pruebas unitarias de Python desde QtCreator con GDB. Para esto, debe ir a Proyectos y elegir Ejecutar en Construir y ejecutar. Luego agregue una nueva `` Configuración de ejecución “” con el ejecutable ``/usr/bin/python3”” y los argumentos de la línea de comando establecidos en la ruta del archivo de prueba de unidad de Python, p. Ej: archivo: /home/user/dev/qgis/QGIS/tests/src/python/test_qgsattributeformeditorwidget.py.

Ahora también cambie el `` Entorno de ejecución “” y agregue 3 nuevas variables:

Variable

Valor

PYTHONPATH

[build]/output/python/:[build]/output/python/plugins:[source]/tests/src/python

QGIS_PREFIX_PATH

[build]/output

LD_LIBRARY_PATH

[build]/output/lib

Reemplace ``[build]”” con su directorio de compilación y ``[source]”” con su directorio de origen.

6.6.3. Que te diviertas

Bueno, eso concluye esta sección sobre la escritura de pruebas unitarias en QGIS. Esperamos que se acostumbre a escribir pruebas para probar nuevas funciones y verificar regresiones. Algunos aspectos del sistema de prueba (en particular, las partes: file: CMakeLists.txt) todavía se están trabajando para que el marco de prueba funcione de una manera verdaderamente independiente de la plataforma.