Python GUI应用程序的推荐单元测试方法是什么?

40

我目前很愚蠢地尝试维护一个Python桌面应用程序的两个平行代码库,一个使用PyGObject introspection来支持GTK 3,另一个使用PyGTK来支持GTK 2。我主要在PyGObject分支上工作,然后将更改移植到PyGTK分支。由于这些实现之间的所有细微差异,我经常忽略一些问题并导致错误,这些错误被我错过并意外发布,只能由用户发现。

我正在努力想出一种好的方法来设计一些单元测试,最好适用于两个代码库。它不是一个过于复杂的程序(本质上是一个图书管理工具,就像iTunes):

- Main Window
  |- Toolbar with some buttons (add/edit/remove items, configure the program)
  |
  |- VPaned
  |--- Top HPaned
  |------ ListView (listing values by which a library of items can be filtered)
  |------ ListView (listing the contents of the library
  |--- Bottom HPaned
  |------ Image (displaying cover art for the currently selected item in the library)
  |------ TextView (displaying formatted text describing the currently selected item)
 - Edit dialog
 - Configuration dialog
 - About dialog 

我尽可能地将视图与模型分开。每个项目都在自己的类中实现(也就是继承自GTK类)。ListView与其他继承自ListStore的类耦合。库本身由不同的类处理。然而,这些小部件之间存在需要测试的交互作用。例如,如果用户在过滤视图中选择特定项来过滤库,然后从过滤结果中选择一个项目,则文本视图必须显示正确库条目的信息,这有点复杂,因为需要在TreeModelFilter和原始ListStore之间进行迭代器转换等等。
因此,我想问一下,编写此类GUI应用程序的健壮单元测试的推荐方法是什么?我看到有一些库可以做到这一点,但主要针对pygtk的库已经多年没有更新了,所以它们几乎肯定会在PyGObject introspection中失败。也许我不够有创意,无法使用Python的unittest模块找到好的方法,因此我愿意听取建议。

在有人问之前:pygtk不支持GTK 3,但我发现对于GTK 2的pygobject内省支持太不完整了,不能依赖它。 - user626998
1
对我来说,在使用图形用户界面时避免出现回归的最佳方式是进行手动测试。单元测试非常适用于测试工作函数(例如MVC),但我不知道如何处理GUI... - ykatchou
这就是我一直在做的事情,但显然我不太擅长,因为每次发布都会漏掉一些东西。 - user626998
4
你有没有查看过 @ http://unpythonic.blogspot.com/2007/03/unit-testing-pygtk.html? - evandrix
你应该把那个作为答案发布。我还没有遇到过那种方法。我认为我可以使用那种方法拼凑出一些复杂的测试脚本。 - user626998
4个回答

8
你确定要对GUI进行单元测试吗?你举的复杂性例子涉及超过一个单元,因此这是一个集成测试。
如果你真的想进行单元测试,你应该能够实例化一个单个类并为其依赖项提供模拟或存根,然后调用类的方法,就像GUI框架为用户点击等操作所做的那样。这可能会很繁琐,并且你必须确切地知道GUI框架如何将用户输入分派到你的类中。
我的建议是在模型中加入更多内容。对于你给出的例子,你可以创建一个FilterManager,将所有的过滤/选择/显示内容抽象为一个单一的方法。然后对其进行单元测试。

啊,也许我术语使用不当(距离我上次学习/使用这些已经快10年了)。如果我可以天真地问一下,那么集成测试是否像单元测试一样具有强大的定义或可设计性?尽管如此,我认为很多东西都可以按照我在问题评论中提到的方法进行单元测试。 - user626998
1
单元测试的核心概念是一次只测试一个单元,以保持测试完整、简单、快速并能够准确地定位故障。有时这很困难,特别是对于 GUI。您可能还可以使用 Python 的 unittest 模块进行非单元测试,但您也会失去一些好处,我不会再称其为单元测试了。 - Jürgen Strobel
1
这并不是一个真正的答案... 显然他想要使用PyGTK进行集成测试,所以指导他正确的方向,而不是纠正术语。 - schlamar
1
@schlamar,我更希望您提供您的答案,而不是暗示采取不同的方法并进行投票。我自己没有集成测试经验。 - Jürgen Strobel
也许你应该查看这个链接:http://www.obeythetestinggoat.com/fast-tests-useless-hot-lava-be-damned.html - schlamar

6

有一种很好的方法可以直接测试PyGTK函数和小部件,而不需要经过接受/功能/集成测试框架来进行测试。我在this post中了解到这一点,该文章相当自我解释。但基本思想是将小部件视为函数/类,并可以直接测试它们。如果需要处理回调等,则有一个巧妙的技巧,我将在此处重现:

import time
import gtk

# Stolen from Kiwi
def refresh_gui(delay=0):
  while gtk.events_pending():
      gtk.main_iteration_do(block=False)
  time.sleep(delay)

正如博客文章中提到的,此代码属于LGPL。否则,从某种意义上说,只要您不 show() 窗口或小部件,您可以尽情测试它们,并且它们应该表现得像真实的一样,因为它们本质上就是这样。它们只是没有被显示出来。
当然,您需要通过调用例如按钮上的 clicked() 来模拟对按钮和交互式小部件的交互。请参见 Ali Afshar 在 PyGTK 中进行单元测试的优秀文章

2

为了贯彻Jürgen正确的主题,我不感兴趣的是单元测试,而是对集成测试感兴趣。我还发现了来自freedesktop.org的这个框架: http://ldtp.freedesktop.org/wiki/

它可以自动化进行各种可访问性GUI应用程序的测试(包括GTK、Qt、Swing等)。


1
你可以使用 X11 帧缓冲:
Xvfb :119 -screen 0 1024x768x16 &
export DISPLAY=:119
... run your tests

请确保不要输入gtk.main(),因为这将等待鼠标或键盘输入。您可以使用此模式让gtk处理所有事件:
def refresh_gui():
  while gtk.events_pending():
      gtk.main_iteration_do(block=False)

据我所知,您无法查看您的应用程序,但您可以测试回调函数。

xvfb-run -a testname 更好。 - lidaobing

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接