为QGIS插件编写自动化测试?


16

我正在寻找有关为用Python编写的QGIS插件编写自动测试的建议。

过去,我使用PyUnit(unittest模块)编写了针对Python脚本的测试,但从未针对具有GUI的应用程序进行过测试。我已经找到了一个页面,描述了如何使用PyQt4.QTest对Qt小部件进行单元测试(http://www.voom.net/pyqt-qtest-example),但是我正在努力查看如何使用它带有设计为从QGIS内部运行的小部件。

PyQGIS文档中关于“测试”的部分明显不存在。

到目前为止,我有:

  • 将实际的数据处理保存在隔离的模块或功能中,并为这些模块或功能编写单元测试;
  • 使用QTest对UI进行基本测试;
  • 当从QGIS内部使用插件时,请祈祷一切融合在一起。

有没有更好的办法?

Answers:


11

最近,用于测试QGIS插件的功能(尤其是OP强调的QGIS环境中的集成测试问题)已得到了很大的改进。因此,我希望此更新对当代读者和OP有帮助。

Boundless在2016年7月发表了一篇必读的文章,面向认真从事自动化QGIS插件测试的任何人。用于Python插件的QGIS连续集成测试环境。它描述了他们使用的方法和工具-所有这些都是开源的。关键方面是:

  • 其特殊的QGIS插件测试仪可以自动检测内部的QGIS环境
  • 使用docker QGIS映像,允许在基于容器的环境中针对各种QGIS版本/配置进行测试
  • 特殊搬运工QGIS图像,其被用于测试QGIS本身,而是其-通过调用qgis_testrunner.sh可用于在一个插件运行单元测试
  • 使用Travis CI进行持续集成-即在每次提交新代码时都运行完整的测试套件

如果您熟悉Travis CI / docker,则应该相对容易设置。他们描述了以下4个步骤,并提供了以这种方式设置的2个示例自己的插件。

  1. 使用QGIS测试环境提取Docker映像并运行它
  2. 运行qgis_setup.sh NameOfYourPlugin以安装插件并为测试运行程序准备QGIS
  3. (可选)执行构建插件所需的所有操作
  4. 在Docker内部运行测试运行程序,调用 qgis_testrunner.sh

您要求最佳实践,到目前为止,我肯定会考虑这一点。QGIS文档仍然没有关于插件测试的专门章节(我希望这很快会改变),但是“祈求一切都在一起”的方法当然不再是唯一的选择。


4
无边无际。是否有人保存了该内容?
Pedro Camargo

8

看起来可以用来unittest测试加载到独立Python应用程序中的 Python插件。

qgis.core.iface在独立的应用程序中不可用,因此我编写了一个虚拟实例,该实例返回一个函数,该函数将接受为其提供的任何参数,并且不执行其他任何操作。这意味着像self.iface.addToolBarIcon(self.action)这样的调用不会引发错误。

以下示例加载了一个插件myplugin,该插件具有一些下拉菜单,其中的图层名称来自于地图图层注册表。测试将检查菜单是否已正确填充,并且可以进行交互。我不确定这是否是加载插件的最佳方法,但它似乎有效。

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

4
我写的,因为根据这个答案在这里的文章:snorf.net/blog/2014/01/04/...
Snorfalorpagus


-1

这可能会有所帮助:使用QTest测试PyQt GUI并进行单元测试 http://www.voom.net/pyqt-qtest-example


1
那就是问题中链接到的“本页”(承认不太清楚)。我的问题是,如何测试设计为可以在QGIS中填充了层的组合框之类的接口运行。
Snorfalorpagus
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.