6. Testes Unitários

Em novembro de 2007, passamos a exigir que todos os novos recursos que vão para a versão master passem a ser acompanhado de um teste unitário. Inicialmente nós limitamos este requisito ao qgis_core, e vamos estender este requisito a outras partes da base de código conforme as pessoas estão familiarizados com os procedimentos de teste unitário explicado nas seções que se seguem.

6.1. O framework de testes do QGIS - uma visão geral

O teste unitário é realizado usando uma combinação de QTestLib (a biblioteca de testes Qt) e CTest (uma estrutura para compilar e executar testes como parte do processo de criação do CMake). Vamos ter uma visão geral do processo antes de nos aprofundarmos nos detalhes:

  1. Há algum código que você deseja testar, por exemplo uma classe ou função. Os maiores defensores da programaçãosugerem que o código ainda não deve ser gravado quando você começar a compilar seus testes e, ao implementar o código, poderá validar imediatamente cada nova parte funcional que você adiciona ao seu teste. Na prática, você provavelmente precisará escrever testes para o código preexistente no QGIS, pois estamos começando com uma estrutura de teste bem depois da implementação de muita lógica do aplicativo.

  2. Você cria um teste unitário. Isso acontece em <QGIS Source Dir>/tests/src/core no caso da biblioteca principal. O teste é basicamente um cliente que cria uma instância de uma classe e chama alguns métodos nessa classe. Ele verifica o retorno de cada método para garantir que ele corresponda ao valor esperado. Se qualquer uma das chamadas falhar, a unidade vai falhar.

  3. Você inclui macros QtTestLib na sua classe de teste. Essa macro é processada pelo meta-objeto Qt (moc) e expande sua classe de teste para um aplicativo executável.

  4. Você adiciona uma seção ao CMakeLists.txt no diretório de testes que criará seu teste.

  5. Você garante que ENABLE_TESTING está ativado no ccmake / cmakesetup. Isso garantirá que seus testes sejam realmente compilados quando você digitar make.

  6. Você opcionalmente adiciona dados de teste ao <QGIS Source Dir>/tests/testdata se seu teste for orientado por dados (por exemplo, é necessário carregar um shapefile). Esses dados de teste devem ser tão pequenos quanto possível e, sempre que possível, você deve usar os conjuntos de dados já existentes. Seus testes nunca devem modificar esses dados in situ, mas fazer uma cópia temporária em algum lugar, se necessário.

  7. Você compila suas sources e instala. Faça isso usando o procedimento normal make && (sudo) make install.

  8. Você executa seus testes. Isso normalmente é feito simplesmente executando make test após a etapa make install, apesar de explicarmos outras abordagens que oferecem controle mais refinado sobre a execução de testes.

Bem com essa visão geral, vamos nos aprofundar um pouco nos detalhes. Já fizemos grande parte da configuração para você no CMake e em outros locais da árvore de origem; portanto, tudo o que você precisa fazer são as partes fáceis - escrever testes de unidade!

6.2. Criar um teste unitário

Criar um teste unitário é fácil - normalmente você faz isso apenas criando um único arquivo .cpp (o arquivo no .h é usado) e implementa todos os seus métodos de teste como métodos públicos que retornam nulos. Usaremos uma classe de teste simples para QgsRasterLayer em toda a seção a seguir para ilustrar. Por convenção, nomearemos nosso teste com o mesmo nome da classe que eles estão testando, mas prefixados com ‘Test’. Portanto, nossa implementação de teste entra em um arquivo chamado testqgsrasterlayer.cpp e a própria classe será TestQgsRasterLayer. Primeiro, adicionamos nosso banner de direitos autorais padrão:

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

Em seguida, iniciamos nossas inclusões necessárias para os testes que planejamos executar. Há uma inclusão especial que todos os testes devem ter:

#include <QtTest/QtTest>

Além disso, você continua implementando sua classe normalmente, puxando os cabeçalhos que precisar:

//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 combinando declaração de classe e implementação em um único arquivo, a declaração de classe vem a seguir. Começamos com nossa documentação doxygen. Cada caso de teste deve ser devidamente documentado. Usamos a diretiva em grupo doxygen para que todos os Testes Unitários apareçam como um módulo na documentação do Doxygen gerada. Depois disso vem uma breve descrição do teste unitário e a classe deve herdar de QObjeto e incluir a macro Q_OBJETO.

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

class TestQgsRasterLayer: public QObject
{
    Q_OBJECT

Todos os nossos métodos de teste são implementados como slots privados. A estrutura QtTestE chamará sequencialmente cada método de slot privado na classe de teste. Existem quatro métodos ‘especiais’ que se implementados serão chamados no início do teste unitário (initCasodeTeste), no final do teste unitário (CasoDeTesteDeLimpeza). Antes de cada método de teste ser chamado, o método init() será chamado e após cada método de teste ser chamado o método limpeza() será chamado. Esses métodos são úteis porque permitem alocar e limpar recursos antes de executar cada teste e a unidade de teste como um todo.

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

Em seguida, vêm seus métodos de teste, todos os quais não devem receber parâmetros e devem retornar vazio. Os métodos serão chamados na ordem de declaração. Estamos implementando dois métodos aqui que ilustram dois tipos de teste.

No primeiro caso, queremos testar geralmente se as várias partes da classe estão funcionando. Podemos usar uma abordagem de teste funcional. Mais uma vez, programadores radicais defenderiam a escrita desses testes antes de implementar a classe. Então, enquanto você trabalha na implementação de sua classe, você executa iterativamente seus testes de unidade. Mais e mais funções de teste devem ser concluídas com sucesso à medida que o trabalho de implementação de sua classe avança e, quando todo o teste unitário for aprovado, sua nova classe estará concluída e agora completa com uma maneira repetível de validá-la.

Normalmente, seus testes de unidade cobrem apenas a API pública de sua classe e, normalmente, você não precisa escrever testes para acessadores e mutadores. Se acontecer que um acessador ou modificador não está funcionando como esperado, você normalmente implementaria um :ref:`teste de regressão <regression_test> ` para verificar isso.

//
// Functional Testing
//

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

// more functional tests here ...

6.2.1. Implementando um teste de regressão

Em seguida, implementamos nossos testes de regressão. Testes de regressão devem ser implementados para replicar as condições de um bug específico. Por exemplo:

  1. Recebemos um relatório por e-mail de que a contagem de células por rasters estava errada em 1, descartando todas as estatísticas das bandas de raster.

  2. Abrimos um relatório de bug (chamado #832)

  3. Criamos um teste de regressão que replicou o bug usando um pequeno conjunto de dados de teste (um raster 10x10).

  4. Executamos o teste, verificando se ele realmente falhou (a contagem de células era 99 em vez de 100).

  5. Em seguida, corrigimos o bug e executamos novamente o teste unitário e o teste de regressão passou. Cometemos o teste de regressão junto com a correção do bug. Agora, se alguém quebrar isso no código-fonte novamente no futuro, podemos identificar imediatamente que o código regrediu.

    Melhor ainda, antes de confirmar qualquer alteração no futuro, executar nossos testes garantirá que nossas alterações não tenham efeitos colaterais inesperados - como quebrar a funcionalidade existente.

Há mais um benefício nos testes de regressão - eles podem economizar seu tempo. Se você já corrigiu um bug que envolvia fazer alterações na fonte e, em seguida, executar o aplicativo e executar uma série de etapas complicadas para replicar o problema, ficará imediatamente aparente que simplesmente implementar seu teste de regressão antes de corrigir o bug permitirá automatizar os testes para resolução de bugs de forma eficiente.

Para implementar seu teste de regressão, você deve seguir a convenção de nomenclatura de regressão <TicketID> para suas funções de teste. Se não existir nenhum chamado para a regressão, você deve criar um primeiro. Usar essa abordagem permite que a pessoa que está executando um teste de regressão com falha vá facilmente e descubra mais informações.

//
// 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, em sua declaração de classe de teste, você pode declarar privadamente quaisquer membros de dados e métodos auxiliares que seu teste unitário possa precisar. No nosso caso vamos declarar uma CamadaRasterQgs * que pode ser usado por qualquer um dos nossos métodos de teste. A camada raster será criada na função initCasodeTeste() que é executada antes de qualquer outro teste, e então destruída usando Casodetestedelimpeza() que é executada após todos os testes. Ao declarar métodos auxiliares (que podem ser chamados por várias funções de teste) de forma privada, você pode garantir que eles não serão executados automaticamente pelo executável QTeste que é criado quando compilamos nosso teste.

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

Isso encerra nossa declaração de classe. A implementação é simplesmente embutida no mesmo arquivo mais abaixo. Primeiro nossas funções de inicialização e limpeza:

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

A função init acima ilustra algumas coisas interessantes.

  1. Precisávamos definir manualmente o caminho de dados do aplicativo QGIS para que recursos como: arquivo:srs.db pudessem ser encontrados corretamente.

  2. Em segundo lugar, este é um teste orientado a dados, então precisávamos fornecer uma maneira de localizar genericamente o arquivo:dezpordezraster.asc. Isso foi conseguido usando o compilador define TESTE_CAMINHO_DADOS. A definição é criada no arquivo de configuração CFaçaListas.txt em: arquivo:/testes/CFacaListas.txt e está disponível para todos os testes de unidade QGIS. Se você precisar de dados de teste para seu teste, faça o caminho: arquivo:/testes/dadosdeteste. Você só deve confirmar conjuntos de dados muito pequenos aqui. Se seu teste precisar modificar os dados de teste, ele deve fazer uma cópia deles primeiro.

O Qt também fornece alguns outros mecanismos interessantes para testes orientados a dados, portanto, se você estiver interessado em saber mais sobre o assunto, consulte a documentação do Qt.

Em seguida, vamos olhar para o nosso teste funcional. O teste Éválido() simplesmente verifica se a camada raster foi carregada corretamente no initCasodeTeste. QVERIFICAR é uma macro Qt que você pode usar para avaliar uma condição de teste. Existem algumas outras macros de uso que o Qt fornece para uso em seus testes, incluindo:

  • QCOMPARE ( atual, esperado )

  • QFALHA_ESPERADA ( índice de dados, comentário, modo )

  • QFALHA ( mensagem )

  • QFETCH ( type, name )

  • QPULAR ( descrição, modo )

  • QTESTE ( atual, elemento de teste )

  • QTESTE_MAÇÃ_PRINCIPAL ( Classe de teste )

  • QTESTE_PRINCIPAL ( Classe de Teste)

  • QTESTE_NOOP_PRINCIPAL ()

  • QVERIFICA2 ( condição, mensagem )

  • QVERIFICA ( condição)

  • QAVISO ( mensagem )

Algumas dessas macros são úteis apenas ao usar a estrutura Qt para testes orientados a dados (consulte os documentos do Qt para obter mais detalhes).

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

Normalmente, seus testes funcionais cobririam toda a gama de funcionalidades da API pública de suas classes, sempre que possível. Com nossos testes funcionais prontos, podemos ver nosso exemplo de teste de regressão.

Como o problema no bug #832 é uma contagem de células relatada incorretamente, escrever nosso teste é simplesmente uma questão de usar QVERIFICA para verificar se a contagem de células atende ao 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 );
}

Com todas as funções de teste unitário implementadas, há uma última coisa que precisamos adicionar à nossa classe de teste:

QTEST_MAIN(TestQgsRasterLayer)
#include "testqgsrasterlayer.moc"

O propósito destas duas linhas é sinalizar ao modelo do Qt que este é um QtTeste (ele irá gerar um método principal que por sua vez chama cada função de teste. A última linha é a inclusão para as fontes geradas pelo MODELO. Você deve substituir o ``testarcamadarasterqgs `` com o nome de sua classe em letras minúsculas.

6.3. Comparando imagens para testes de renderização

A renderização de imagens em diferentes ambientes pode produzir diferenças sutis devido a implementações específicas da plataforma (por exemplo, diferentes algoritmos de renderização de fonte e anti-aliasing), às fontes disponíveis no sistema e por outros motivos obscuros.

Quando um teste de renderização é executado no Travis e falha, procure o link do traço na parte inferior do log do Travis. Este link irá levá-lo a uma página ctraço onde você pode ver as imagens renderizadas versus as esperadas, juntamente com uma imagem de “diferença” que destaca em vermelho quaisquer pixels que não correspondam à imagem de referência.

O sistema de teste unitário QGIS tem suporte para adicionar imagens de “máscara”, que são usadas para indicar quando uma imagem renderizada pode diferir da imagem de referência. Uma imagem de máscara é uma imagem (com o mesmo nome da imagem de referência, mas incluindo um sufixo _mascara.png) e deve ter as mesmas dimensões da imagem de referência. Em uma imagem de máscara, os valores de pixel indicam o quanto esse pixel individual pode diferir da imagem de referência, portanto, um pixel preto indica que o pixel na imagem renderizada deve corresponder exatamente ao mesmo pixel na imagem de referência. Um pixel com RGB 2, 2, 2 significa que a imagem renderizada pode variar em até 2 em seus valores RGB da imagem de referência, e um pixel totalmente branco (255, 255, 255) significa que o pixel é efetivamente ignorado ao comparar as imagens esperadas e renderizadas.

Um script utilitário para gerar imagens de máscara está disponível como scripts/gerar_teste_mascara_imagem.py. Este script é usado passando o caminho de uma imagem de referência (por exemplo, testes/dadosdeteste/controle_imagens/anotações/esperado_anotação_estilodepreenchimento/esperado_anotação_estilodepreenchimento.png) e o caminho para sua imagem renderizada.

Por exemplo.

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

Você pode encurtar o caminho para a imagem de referência passando uma parte parcial do nome do teste, por exemplo,

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

(Este atalho só funciona se uma única imagem de referência correspondente for encontrada. Se várias correspondências forem encontradas, você precisará fornecer o caminho completo para a imagem de referência.)

O script também aceita URLs http para a imagem renderizada, então você pode copiar diretamente uma URL de imagem renderizada da página de resultados do ctraço e passá-la para o script.

Tenha cuidado ao gerar imagens de máscara - você deve sempre visualizar a imagem de máscara gerada e revisar quaisquer áreas brancas na imagem. Como esses pixels são ignorados, certifique-se de que essas imagens brancas não cubram nenhuma parte importante da imagem de referência - caso contrário, seu teste unitário não terá sentido!

Da mesma forma, você pode “branquear” manualmente partes da máscara se desejar excluí-las do teste deliberadamente. Isto pode ser útil e. para testes que misturam renderização de símbolo e texto (como testes de legenda), onde o teste unitário não foi projetado para testar o texto renderizado e você não deseja que o teste esteja sujeito a diferenças de renderização de texto entre plataformas.

Para comparar imagens em testes unitários do QGIS você deve usar a classe VerificadordemultirenderizaçãoQgs ou uma de suas subclasses.

Para melhorar a robustez dos testes aqui estão algumas dicas:

  1. Desative o antialiasing se puder, pois isso minimiza as diferenças de renderização entre plataformas.

  2. Certifique-se de que suas imagens de referência sejam “volumosas”… ou seja, não tenham linhas de 1 px de largura ou outros recursos finos e use fontes grandes e em negrito (recomenda-se 14 pontos ou mais).

  3. Às vezes, os testes geram imagens de tamanhos ligeiramente diferentes (por exemplo, testes de renderização de legendas, em que o tamanho da imagem depende do tamanho da renderização da fonte - que está sujeito a diferenças entre plataformas). Para explicar isso, use VerificadordemultirenderizaçãoQgs::definirTolerânciadeTamanho() e especifique o número máximo de pixels que a largura e a altura da imagem renderizada diferem da imagem de referência.

  4. Não use fundos transparentes em imagens de referência (o Traço não os suporta). Em vez disso, use VerificadordemultirenderizaçãoQgs::desenharfundo() para desenhar um padrão de placa de verificação () para o fundo da imagem de referência.

  5. Quando fontes são necessárias, use a fonte especificada em QgsFontesUteis::Famíliadefontesdetestepadrão() (“QGIS Vera Sans”).

Se o travis relatar erros para novas imagens (por exemplo, devido a antialiasing ou diferenças de fonte), o script:fonte: análise dos resultados do traço.py <scripts/análise_dos_resultados_do_traço.py> pode ajudá-lo quando você estiver atualizando as máscaras de teste locais.

6.4. Adicionando seu teste unitário ao CFazerListas.txt

Adicionar seu teste unitário ao sistema de compilação é simplesmente uma questão de editar o: arquivo:CFazerListas.txt no diretório de teste, clonar um dos blocos de teste existentes e, em seguida, substituir o nome da classe de teste nele. Por exemplo:

# QgsRasterLayer test
ADD_QGIS_TEST(rasterlayertest testqgsrasterlayer.cpp)

6.4.1. A macro ADICIONAR_QGIS_TESTE explicada

Vamos percorrer essas linhas brevemente para explicar o que elas fazem, mas se você não estiver interessado, basta seguir a etapa explicada na seção acima.

MACRO (ADD_QGIS_TEST testname testsrc)
SET(qgis_${testname}_SRCS ${testsrc} ${util_SRCS})
SET(qgis_${testname}_MOC_CPPS ${testsrc})
QT4_WRAP_CPP(qgis_${testname}_MOC_SRCS ${qgis_${testname}_MOC_CPPS})
ADD_CUSTOM_TARGET(qgis_${testname}moc ALL DEPENDS ${qgis_${testname}_MOC_SRCS})
ADD_EXECUTABLE(qgis_${testname} ${qgis_${testname}_SRCS})
ADD_DEPENDENCIES(qgis_${testname} qgis_${testname}moc)
TARGET_LINK_LIBRARIES(qgis_${testname} ${QT_LIBRARIES} qgis_core)
SET_TARGET_PROPERTIES(qgis_${testname}
PROPERTIES
# skip the full RPATH for the build tree
SKIP_BUILD_RPATHTRUE
# when building, use the install RPATH already
# (so it doesn't need to relink when installing)
BUILD_WITH_INSTALL_RPATH TRUE
# the RPATH to be used when installing
INSTALL_RPATH ${QGIS_LIB_DIR}
# add the automatically determined parts of the RPATH
# which point to directories outside the build tree to the install RPATH
INSTALL_RPATH_USE_LINK_PATH true)
IF (APPLE)
# For macOS, the executable must be at the root of the bundle's executable folder
INSTALL(TARGETS qgis_${testname} RUNTIME DESTINATION ${CMAKE_INSTALL_PREFIX})
ADD_TEST(qgis_${testname} ${CMAKE_INSTALL_PREFIX}/qgis_${testname})
ELSE (APPLE)
INSTALL(TARGETS qgis_${testname} RUNTIME DESTINATION ${CMAKE_INSTALL_PREFIX}/bin)
ADD_TEST(qgis_${testname} ${CMAKE_INSTALL_PREFIX}/bin/qgis_${testname})
ENDIF (APPLE)
ENDMACRO (ADD_QGIS_TEST)

Vamos olhar um pouco mais detalhadamente para as linhas individuais. Primeiro definimos a lista de fontes para nosso teste. Como temos apenas um arquivo de origem (seguindo a metodologia descrita acima, onde a declaração e a definição da classe estão no mesmo arquivo), é uma declaração simples:

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

Como nossa classe de teste precisa ser executada através do compilador de objetos meta Qt (modelo), precisamos fornecer algumas linhas para que isso aconteça também:

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

Em seguida, informamos ao cfazer que ele deve fazer um executável da classe de teste. Lembre-se que na seção anterior, na última linha da implementação da classe, incluímos as saídas do modelo diretamente em nossa classe de teste, de modo que fornecerá (entre outras coisas) um método principal para que a classe possa ser compilada como um executável:

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

Em seguida, precisamos especificar quaisquer dependências de biblioteca. No momento, as classes foram implementadas com uma dependência QT_BIBLIOTECAS abrangente, mas estaremos trabalhando para substituí-la pelas bibliotecas Qt específicas que cada classe precisa apenas. É claro que você também precisa vincular as bibliotecas qgis relevantes conforme exigido pelo seu teste unitário.

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

Em seguida, dizemos ao cfazer para instalar os testes no mesmo local que os próprios binários do qgis. Isso é algo que planejamos remover no futuro para que os testes possam ser executados diretamente de dentro da árvore de origem.

SET_TARGET_PROPERTIES(qgis_${testname}
PROPERTIES
# skip the full RPATH for the build tree
SKIP_BUILD_RPATHTRUE
# when building, use the install RPATH already
# (so it doesn't need to relink when installing)
BUILD_WITH_INSTALL_RPATH TRUE
# the RPATH to be used when installing
INSTALL_RPATH ${QGIS_LIB_DIR}
# add the automatically determined parts of the RPATH
# which point to directories outside the build tree to the install RPATH
INSTALL_RPATH_USE_LINK_PATH true)
IF (APPLE)
# For macOS, the executable must be at the root of the bundle's executable folder
INSTALL(TARGETS qgis_${testname} RUNTIME DESTINATION ${CMAKE_INSTALL_PREFIX})
ADD_TEST(qgis_${testname} ${CMAKE_INSTALL_PREFIX}/qgis_${testname})
ELSE (APPLE)
INSTALL(TARGETS qgis_${testname} RUNTIME DESTINATION ${CMAKE_INSTALL_PREFIX}/bin)
ADD_TEST(qgis_${testname} ${CMAKE_INSTALL_PREFIX}/bin/qgis_${testname})
ENDIF (APPLE)

Finalmente o acima usa ADICIONAR_TESTE para registrar o teste com cfazer / cteste. Aqui é onde a melhor mágica acontece - registramos a classe com cteste. Se você se lembrar da visão geral que demos no início desta seção, estamos usando o QtTeste e o CTeste juntos. Para recapitular, o QtTeste adiciona um método principal à sua unidade de teste e lida com a chamada de seus métodos de teste dentro da classe. Ele também fornece algumas macros como QVERIFICA que você pode usar para testar a falha dos testes usando condições. A saída de um teste unitário do QtTeste é um executável que você pode executar a partir da linha de comando. No entanto, quando você tem um conjunto de testes e deseja executar cada executável por vez, e melhor ainda integrar os testes em execução no processo de compilação, o CTeste é o que usamos.

6.5. Construindo sua unidade teste

Para construir o teste unitário você só precisa ter certeza de que PERMITIR_TESTES=verdadeiro na configuração do cfazer. Existem duas maneiras de fazer isso:

  1. Execute ccfazer .. (ou cfazeraconfiguração .. no Windows) e defina interativamente o sinalizador ATIVAR_TESTES para ATIVADO.

  2. Adicione um sinalizador de linha de comando ao cfazer, por exemplo. cfazer -DESATIVADO_TESTES=verdadeiro ..

Fora isso, basta construir o QGIS normalmente e os testes também devem ser construídos.

6.6. Execute seus testes

A maneira mais simples de executar os testes é como parte do seu processo de criação normal:

make && make install && make test

O comando fazer teste invocará o CTeste que executará cada teste que foi registrado usando a diretiva ADICIONAR_TESTE CFazer descrita acima. A saída típica de fazer teste será assim:

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

Se um teste falhar, você pode usar o comando cteste para examinar mais detalhadamente por que ele falhou. Use a opção -R para especificar um regex para quais testes você deseja executar e -V para obter uma saída detalhada:

$ 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. Executando testes individuais

Os testes C++ são aplicativos comuns. Você pode executá-los a partir da pasta de compilação como qualquer executável.

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

Estes testes também aceitam argumentos de linha de comando. Isto torna possível a execução de um subconjunto específico de testes:

$ ./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. Depurando testes de unidade

Testes C++

Para testes de unidade C++, o QtCriador adiciona automaticamente alvos de execução, para que você possa iniciá-los a partir do depurador.

Se você for para Projectos e lá para a guia Construir e executar –> Área de trabalho Executar, você também pode especificar parâmetros de linha de comando que permitirão que um subconjunto de testes seja executado dentro de um arquivo .cpp no depurador.

Testes Python

Também é possível iniciar testes de unidade Python do QtCriador com GDB. Para isso, você precisa ir para Projectos e escolher Executar em Construir e executar. Em seguida, adicione uma nova Configuração de execução com o executável /usr/bin/python3 e os argumentos da linha de comando definidos para o caminho do arquivo python de teste unitário, por exemplo :arquivo:`/paginainicial/usuario/desenvolvedor/qgis/QGIS/testes/src/python/teste_ferramentaeditoradeformuláriodeatributoqgs.py`.

Agora também mude o Ambiente de execução e adicione 3 novas variáveis:

Variável

Valor

PYTHONPATH

[construir]/saída/python/:[construir]/saída//python/plug-ins:[fonte]/testes/src/python

QGIS_PREFIX_PATH

[construir]/saída

LD_LIBRARY_PATH

[construir]/saída/lib

Substitua [construir] pelo seu diretório de compilação e [fonte] pelo seu diretório de origem.

6.6.3. Divirta-se

Bem, isso conclui esta seção sobre como escrever testes de unidade no QGIS. Esperamos que você adquira o hábito de escrever teste para testar novas funcionalidades e verificar regressões. Alguns aspectos do sistema de teste (em particular as partes :arquivo:`CFazerListas.txt`) ainda estão sendo trabalhados para que a estrutura de teste funcione de maneira verdadeiramente independente da plataforma.