如何对Python GUI应用进行单元测试?

41 投票
4 回答
7524 浏览
提问于 2025-04-17 00:24

我现在正在尝试维护两个平行的代码库,都是为一个Python桌面应用程序服务的。其中一个使用的是PyGObject来支持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类继承的类)。列表视图和其他从ListStores继承的类是关联在一起的。图书馆的管理则是由另一个类来处理。不过,界面组件之间还是有一些交互需要测试的。例如,如果用户在过滤视图中选择了一个特定的项目来过滤图书馆,然后从过滤后的结果中选择一个项目,文本视图就必须显示正确的图书馆条目信息。这有点复杂,因为需要在TreeModelFilter和原始ListStore之间进行转换等等。

所以,我想问一下,对于这样的图形用户界面应用程序,写稳健的单元测试有什么推荐的方法吗?我看到有一些库可以用,但主要针对pygtk的库已经多年没有更新了,所以几乎肯定会和PyGObject的反射不兼容。也许我不够有创意,无法用Python的unittest模块找到一个好的方法,所以我很欢迎任何建议。

4 个回答

2

我同意Jürgen的看法,我其实对单元测试不感兴趣,更想关注集成测试。同时,我还发现了一个来自freedesktop.org的框架:http://ldtp.freedesktop.org/wiki/

这个框架可以自动化测试各种支持无障碍功能的图形用户界面应用程序(比如GTK、Qt、Swing等)。

6

有一种很棒的方法可以直接测试PyGTK的功能和控件,而不需要通过那些复杂的测试框架。这个方法我是在这篇文章中了解到的,文章讲得很清楚。基本的想法是把你的控件当作函数或类来对待,这样你就可以直接测试它们。如果你需要处理回调函数等,还有一个很不错的小技巧,我会在这里分享:

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单元测试的精彩文章

8

你确定想要对图形用户界面(GUI)进行单元测试吗?你提到的复杂例子涉及不止一个单元,所以这实际上是一个集成测试。

如果你真的想做单元测试,你应该能够创建一个单独的类,并为它的依赖项提供一些模拟对象或者占位符,然后像图形用户界面框架那样调用它的方法,比如模拟用户点击。这可能会比较麻烦,而且你需要非常清楚图形用户界面框架是如何将用户输入传递给你的类的。

我的建议是把更多的内容放在模型中。针对你给出的例子,你可以创建一个过滤管理器(FilterManager),把所有的过滤、选择和显示的操作都封装在一个方法里。然后再进行单元测试。

撰写回答