Écrire des tests automatisés pour les plugins QGIS?

16

Je cherche des conseils sur l'écriture de tests automatiques pour les plugins QGIS écrits en Python.

J'ai écrit des tests pour les scripts Python dans le passé en utilisant PyUnit (le unittestmodule), mais je ne l'ai jamais fait pour une application avec une interface graphique. J'ai trouvé une page décrivant comment utiliser PyQt4.QTest pour effectuer des tests unitaires sur les widgets Qt ( http://www.voom.net/pyqt-qtest-example ), mais j'ai du mal à voir comment je peux utiliser cela avec un widget qui a été conçu pour fonctionner à partir de QGIS.

La section «Test» de la documentation PyQGIS est notamment absente.

Ce que j'ai jusqu'à présent, c'est:

  • Conserver le traitement réel des données dans des modules ou des fonctions isolés et écrire des tests unitaires pour ceux-ci;
  • Effectuer des tests de base de l'interface utilisateur à l'aide de QTest;
  • Priez pour que tout cela reste ensemble lorsque vous utilisez le plugin depuis QGIS.

Y a-t-il une meilleure façon?

Snorfalorpagus
la source

Réponses:

11

Les capacités de test des plugins QGIS (en particulier la question des tests d'intégration, dans un environnement QGIS, comme le souligne l'OP) se sont beaucoup améliorées récemment. J'espère donc que cette mise à jour aidera les lecteurs contemporains, ainsi que l'OP.

Boundless a publié un article incontournable en juillet 2016 pour quiconque souhaite sérieusement automatiser les tests des plugins QGIS intitulé; Environnement de test d'intégration continue QGIS pour les plugins Python . Il décrit l'approche et les outils qu'ils utilisent - qui sont tous open source. Les aspects clés sont: -

  • Leur testeur de plugin QGIS spécial qui peut automatiser les tests dans l'environnement QGIS
  • L'utilisation de docker QGIS images, ce qui permet de tester contre différentes versions de QGIS / configurations dans un environnement de base contenant
  • Une image Docker QGIS spéciale , qui est utilisée pour tester QGIS lui-même, mais qui - en l'appelant qgis_testrunner.shpeut être utilisée pour exécuter des tests unitaires sur un plugin
  • L'utilisation de Travis CI pour une intégration continue - c'est-à-dire que la suite de tests complète est exécutée à chaque nouvelle validation de code

Si vous connaissez Travis CI / docker, il devrait être relativement facile à configurer. Ils décrivent les 4 étapes suivantes et fournissent 2 exemples de leurs propres plugins configurés de cette manière.

  1. Tirez l'image Docker avec l'environnement de test QGIS et exécutez-la
  2. Exécutez qgis_setup.sh NameOfYourPlugin pour installer le plugin et préparer QGIS pour le testeur
  3. Effectuez éventuellement toutes les opérations nécessaires pour créer votre plugin
  4. Exécutez le lanceur de test dans le Docker en invoquant le qgis_testrunner.sh

Vous avez demandé les meilleures pratiques et à ce jour, je considérerais certainement cela. Les documents QGIS n'ont toujours pas de section dédiée aux tests de plugins (je m'attends à ce que cela change sous peu) mais l'approche "Priez pour que tout se tienne" n'est certainement plus la seule option.

MatzFan
la source
4
Boundless n'est plus. Quelqu'un a-t-il enregistré ce contenu?
Pedro Camargo
8

Il semble que cela soit possible pour unittesttester des plugins Python chargés dans une application Python autonome .

qgis.core.iface n'est pas disponible à partir d'applications autonomes, j'ai donc écrit une instance factice qui retourne une fonction qui acceptera tous les arguments qui lui seront donnés et ne fera rien d'autre. Cela signifie que les appels comme self.iface.addToolBarIcon(self.action)ne génèrent pas d'erreurs.

L'exemple ci-dessous charge un plugin myplugin, qui a des menus déroulants avec des noms de couches tirés du registre des couches de carte. Les tests vérifient si les menus ont été correctement remplis et peuvent être interagis avec. Je ne sais pas si c'est la meilleure façon de charger le plugin, mais cela semble fonctionner.

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
la source
4
J'ai depuis écrit un article basé sur cette réponse ici: snorf.net/blog/2014/01/04/…
Snorfalorpagus
3

J'ai également mis en place une interface factice, qui vous permet de tester les plugins QGIS de manière autonome. Après avoir lu le blog Snorfalorpagus, consultez ma réponse ici .

Pour trouver un exemple réel, sur la façon dont je teste (ed) les plugins QGIS, visitez ce projet github à https://github.com/UdK-VPT/Open_eQuarter/tree/master/mole et jetez un œil aux tests - paquet.

Kim
la source
-1

Cela pourrait aider: Tester les interfaces graphiques PyQt avec QTest et unittest http://www.voom.net/pyqt-qtest-example

Stefan
la source
1
C'est "cette page" liée à la question (certes pas trop clairement). Mon problème est de savoir comment tester une interface conçue pour fonctionner avec des choses comme des zones de liste déroulante remplies de couches dans QGIS.
Snorfalorpagus