Escrevendo testes automatizados para plug-ins QGIS?

16

Estou procurando conselhos para escrever testes automáticos para plug-ins QGIS escritos em Python.

Eu escrevi testes para scripts Python no passado usando PyUnit (o unittestmódulo), mas nunca o fiz para um aplicativo com uma GUI. Encontrei uma página descrevendo como usar o PyQt4.QTest para fazer testes de unidade em widgets Qt ( http://www.voom.net/pyqt-qtest-example ), mas estou tendo dificuldades para ver como posso usar isso. com um widget que foi projetado para executar no QGIS.

A seção "Teste" na documentação do PyQGIS está notavelmente ausente.

O que tenho até agora é:

  • Mantenha o processamento real dos dados em módulos ou funções isoladas e escreva testes de unidade para eles;
  • Execute testes básicos da interface do usuário usando o QTest;
  • Ore para que tudo se mantenha unido ao usar o plug-in no QGIS.

Existe uma maneira melhor?

Snorfalorpagus
fonte

Respostas:

11

Os recursos para testar plug-ins QGIS (particularmente a questão do teste de integração, em um ambiente QGIS, como destaca o OP) melhoraram bastante recentemente. Espero, portanto, que esta atualização ajude os leitores contemporâneos, bem como o OP.

A Boundless publicou um artigo de leitura obrigatória em julho de 2016 para qualquer pessoa séria sobre como automatizar o teste de plug-ins do QGIS intitulado; Ambiente de Teste de Integração Contínua QGIS para Plugins Python . Ele descreve a abordagem e as ferramentas que eles usam - todos eles de código aberto. Os principais aspectos são: -

  • Seu testador de plug-in QGIS especial que pode automatizar testes dentro do ambiente QGIS
  • O uso de imagens QGIS do docker , permitindo testar contra várias versões / configurações do QGIS em um ambiente com base em contêiner
  • Uma imagem QGIS do docker especial , usada para testar o próprio QGIS, mas que - chamando, qgis_testrunner.shpode ser usada para executar testes de unidade em um plug-in
  • O uso do Travis CI para integração contínua - ou seja, o conjunto de testes completo é executado com cada novo commit de código

Se você conhece o Travis CI / docker, deve ser relativamente fácil de configurar. Eles descrevem as 4 etapas a seguir e fornecem 2 exemplos de seus próprios plug-ins configurados dessa maneira.

  1. Puxe a imagem do Docker com o ambiente de teste QGIS e execute-o
  2. Execute qgis_setup.sh NameOfYourPlugin para instalar o plug-in e prepare o QGIS para o executor de teste
  3. Opcionalmente, execute todas as operações necessárias para criar seu plugin
  4. Execute o executor de teste dentro do Docker chamando o qgis_testrunner.sh

Você pediu as melhores práticas e, a partir de hoje, eu certamente consideraria isso. Os documentos do QGIS ainda não possuem uma seção dedicada ao teste de plug-ins (espero que isso mude em breve), mas a abordagem "Ore para que tudo se mantenha unido" certamente não é mais a única opção.

MatzFan
fonte
4
Sem limites não existe mais. Alguém salvou esse conteúdo?
Pedro Camargo
8

Parece que isso é possível de ser usado unittestpara testar plugins Python carregados em um aplicativo Python independente .

O qgis.core.iface não está disponível em aplicativos independentes, por isso escrevi uma instância fictícia que retorna uma função que aceita qualquer argumento fornecido e não faz mais nada. Isso significa que chamadas como self.iface.addToolBarIcon(self.action)não geram erros.

O exemplo abaixo carrega um plug-in myplugin, que possui alguns menus suspensos com nomes de camadas retirados do registro da camada de mapa. Os testes verificam se os menus foram preenchidos corretamente e podem ser interagidos. Não tenho certeza se essa é a melhor maneira de carregar o plugin, mas parece funcionar.

widget myplugin

#!/usr/bin/env python

import unittest

import os
import sys

# configure python to play nicely with qgis
osgeo4w_root = r'C:/OSGeo4W'
os.environ['PATH'] = '{}/bin{}{}'.format(osgeo4w_root, os.pathsep, os.environ['PATH'])
sys.path.insert(0, '{}/apps/qgis/python'.format(osgeo4w_root))
sys.path.insert(1, '{}/apps/python27/lib/site-packages'.format(osgeo4w_root))

# import Qt
from PyQt4 import QtCore, QtGui, QtTest
from PyQt4.QtCore import Qt

# import PyQGIS
from qgis.core import *
from qgis.gui import *

# disable debug messages
os.environ['QGIS_DEBUG'] = '-1'

def setUpModule():
    # load qgis providers
    QgsApplication.setPrefixPath('{}/apps/qgis'.format(osgeo4w_root), True)
    QgsApplication.initQgis()

    globals()['shapefile_path'] = 'D:/MasterMap.shp'

# FIXME: this seems to throw errors
#def tearDownModule():
#    QgsApplication.exitQgis()

# dummy instance to replace qgis.utils.iface
class QgisInterfaceDummy(object):
    def __getattr__(self, name):
        # return an function that accepts any arguments and does nothing
        def dummy(*args, **kwargs):
            return None
        return dummy

class ExamplePluginTest(unittest.TestCase):
    def setUp(self):
        # create a new application instance
        self.app = app = QtGui.QApplication(sys.argv)

        # create a map canvas widget
        self.canvas = canvas = QgsMapCanvas()
        canvas.setCanvasColor(QtGui.QColor('white'))
        canvas.enableAntiAliasing(True)

        # load a shapefile
        layer = QgsVectorLayer(shapefile_path, 'MasterMap', 'ogr')

        # add the layer to the canvas and zoom to it
        QgsMapLayerRegistry.instance().addMapLayer(layer)
        canvas.setLayerSet([QgsMapCanvasLayer(layer)])
        canvas.setExtent(layer.extent())

        # display the map canvas widget
        #canvas.show()

        iface = QgisInterfaceDummy()

        # import the plugin to be tested
        import myplugin
        self.plugin = myplugin.classFactory(iface)
        self.plugin.initGui()
        self.dlg = self.plugin.dlg
        #self.dlg.show()

    def test_populated(self):
        '''Are the combo boxes populated correctly?'''
        self.assertEqual(self.dlg.ui.comboBox_raster.currentText(), '')
        self.assertEqual(self.dlg.ui.comboBox_vector.currentText(), 'MasterMap')
        self.assertEqual(self.dlg.ui.comboBox_all1.currentText(), '')
        self.dlg.ui.comboBox_all1.setCurrentIndex(1)
        self.assertEqual(self.dlg.ui.comboBox_all1.currentText(), 'MasterMap')

    def test_dlg_name(self):
        self.assertEqual(self.dlg.windowTitle(), 'Testing')

    def test_click_widget(self):
        '''The OK button should close the dialog'''
        self.dlg.show()
        self.assertEqual(self.dlg.isVisible(), True)
        okWidget = self.dlg.ui.buttonBox.button(self.dlg.ui.buttonBox.Ok)
        QtTest.QTest.mouseClick(okWidget, Qt.LeftButton)
        self.assertEqual(self.dlg.isVisible(), False)

    def tearDown(self):
        self.plugin.unload()
        del(self.plugin)
        del(self.app) # do not forget this

if __name__ == "__main__":
    unittest.main()
Snorfalorpagus
fonte
4
Desde então, escrevi um artigo com base nesta resposta aqui: snorf.net/blog/2014/01/04/…
Snorfalorpagus
3

Também montei um DummyInterface, que permite testar os plug-ins do QGIS de maneira independente. Depois de ler o blog Snorfalorpagus, confira minha resposta aqui .

Para encontrar um exemplo da vida real, sobre como eu testei (ed) os plug-ins QGIS, visite este projeto do github em https://github.com/UdK-VPT/Open_eQuarter/tree/master/mole e veja os testes - pacote.

Kim
fonte
-1

Isso pode ajudar: Teste as GUIs do PyQt com o QTest e o mais unificado http://www.voom.net/pyqt-qtest-example

Stefan
fonte
1
Essa é a "esta página" vinculada à pergunta (reconhecidamente não muito clara). Meu problema é: como testar uma interface projetada para executar com coisas como caixas de combinação preenchidas com camadas no QGIS.
Snorfalorpagus 12/09