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

时间:2022-09-06 07:24:51

I'm currently foolish enough to try to maintaintain two parallel code bases for a Python desktop application, one using PyGObject introspection for GTK 3 and one using PyGTK for GTK 2. I work mainly on the PyGObject branch and then I port changes over to the PyGTK branch. Due to all the minor differences between these implementations, I often overlook things and cause breakage that I miss and accidentally release, only to be caught by the users.

我现在愚蠢地试图为Python桌面应用程序维护两个并行代码库,一个使用PyGObject内省GTK 3,另一个使用PyGTK for GTK 2.我主要在PyGObject分支上工作,然后我将端口转换为PyGTK分支。由于这些实现之间的所有细微差别,我经常忽略事情并导致我想念和意外释放的破损,只是被用户抓住。

I'm trying to figure out a good way to design some unittests that would, preferably, be suitable to run on both code bases. It's not an overly complicated program, (it's essentially a library management tool, imagine like iTunes):

我试图找出一种设计一些单元测试的好方法,这些单元测试最好适合在两个代码库上运行。这不是一个过于复杂的程序,(它本质上是一个图书馆管理工具,想像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 

I've tried to separate views from models as much as possible. Each of those items is implemented in its own class (well, in classes which inherit from the GTK classes listed). The ListViews are coupled with other classes which inherit from ListStores. The library itself is handled by a different class. Nonetheless, there are interactions between the widgets that need to be tested. For example, if the user selects a particular item in the filter view, filtering the library, and then selects an item from the filtered results, the text view must display the information for the correct library entry, which is semi-complicated due to translating iters between TreeModelFilter and the original ListStore, etc etc.

我试图尽可能地将模型与模型分开。这些项中的每一个都在它自己的类中实现(好吧,在从列出的GTK类继承的类中)。 ListViews与从ListStores继承的其他类耦合。图书馆本身由另一个班级处理。尽管如此,需要测试的小部件之间存在交互。例如,如果用户在过滤器视图中选择特定项目,过滤库,然后从过滤结果中选择一个项目,则文本视图必须显示正确库条目的信息,这是因为翻译而半复杂在TreeModelFilter和原始ListStore之间等等。

So, I ask, what is the recommended way for writing robust unit tests for such a GUI application? I've seen that there are some libraries for this but the main ones for pygtk haven't been updated in years and so they will almost certainly fail with PyGObject introspection. Perhaps I'm not creative enough to figure out a good way to do it using Python's unittest module, so I'm open to suggestions.

所以,我想问一下,为这样的GUI应用程序编写健壮的单元测试的推荐方法是什么?我已经看到有一些这样的库,但pygtk的主要库已经多年没有更新,因此它们几乎肯定会因PyGObject内省而失败。也许我没有足够的创造力来找出使用Python的unittest模块做到这一点的好方法,所以我愿意接受建议。

4 个解决方案

#1


4  

Are you sure you want to unit test the GUI? Your example of complexity involves more than 1 unit and is therefore an integration test.

您确定要对GUI进行单元测试吗?您的复杂性示例涉及超过1个单元,因此是集成测试。

If you really want to unit test, you should be able to instantiate a single class providing mocks or stubs for its dependencies, then call methods on it like the GUI framework would for e.g. a user click. This can be tedious and you have to know exactly how the GUI framework dispatches user input to your classes.

如果你真的想进行单元测试,那么你应该能够实例化一个为其依赖项提供模拟或存根的类,然后调用它上面的方法就像GUI框架那样。用户点击。这可能很乏味,您必须确切知道GUI框架如何将用户输入分派给您的类。

My advice is to put even more stuff in models. For your given example you could create a FilterManager which abstracts all the filter/select/display stuff behind a single method. Then unit test it.

我的建议是在模型中加入更多东西。对于您给出的示例,您可以创建一个FilterManager,它抽象出单个方法后面的所有过滤器/选择/显示内容。然后单元测试它。

#2


5  

There is a great way to test PyGTK functions and widgets directly, without going through acceptance/functional/integration testing frameworks that clikety their way into oblivion. I learned about this in this post which is fairly self explanatory. But the basic idea is that you treat your widgets as functions/classes, and you can test them directly. If you need to process callbacks and so on, there's a neat trick that I will reproduce here:

有一种很好的方法可以直接测试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)

As mentionned in the blog post, this code is LGPL. Otherwise, when you think about it, as long as you don't show() windows or widgets, you can test them all you want and they should behave as if they were real because, in a way, they are. They are just not displayed.

正如博客文章中提到的,这段代码是LGPL。否则,当你想到它时,只要你不显示()窗口或窗口小部件,你可以测试它们你想要的所有东西,它们应该表现得好像它们是真实的,因为在某种程度上它们是真的。他们只是没有显示。

Of course, you need to simulate interaction on buttons and interactive widgets yourself by calling clicked() on a button for example. See again Ali Afshar's excellent post about unit testing in PyGTK.

当然,您需要通过调用按钮上的clicked()来自己模拟按钮和交互式小部件上的交互。再看看Ali Afshar在PyGTK中关于单元测试的优秀帖子。

#3


2  

In sticking with the theme that Jürgen was correct in that I'm not interested in unit testing, but actually interested in integration testing, I also found this framework from freedesktop.org: http://ldtp.freedesktop.org/wiki/

坚持Jürgen的主题是正确的,因为我对单元测试不感兴趣,但实际上对集成测试感兴趣,我也从freedesktop.org找到了这个框架:http://ldtp.freedesktop.org/wiki/

It allows automating a variety of tests for Accessibility-enabled GUI applications (including GTK, Qt, Swing, etc).

它允许为启用辅助功能的GUI应用程序(包括GTK,Qt,Swing等)自动执行各种测试。

#4


0  

You can use a X11 framebuffer:

您可以使用X11帧缓冲:

Xvfb :119 -screen 0 1024x768x16 &
export DISPLAY=:119
... run your tests

Be sure, to not enter gtk.main(), since this would wait for mouse or keyboard input. You can use this pattern to let gtk handle all events:

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

def refresh_gui():
  while gtk.events_pending():
      gtk.main_iteration_do(block=False)

AFAIK you can't see your application, but you can test your callbacks.

AFAIK你看不到你的应用程序,但你可以测试你的回调。

#1


4  

Are you sure you want to unit test the GUI? Your example of complexity involves more than 1 unit and is therefore an integration test.

您确定要对GUI进行单元测试吗?您的复杂性示例涉及超过1个单元,因此是集成测试。

If you really want to unit test, you should be able to instantiate a single class providing mocks or stubs for its dependencies, then call methods on it like the GUI framework would for e.g. a user click. This can be tedious and you have to know exactly how the GUI framework dispatches user input to your classes.

如果你真的想进行单元测试,那么你应该能够实例化一个为其依赖项提供模拟或存根的类,然后调用它上面的方法就像GUI框架那样。用户点击。这可能很乏味,您必须确切知道GUI框架如何将用户输入分派给您的类。

My advice is to put even more stuff in models. For your given example you could create a FilterManager which abstracts all the filter/select/display stuff behind a single method. Then unit test it.

我的建议是在模型中加入更多东西。对于您给出的示例,您可以创建一个FilterManager,它抽象出单个方法后面的所有过滤器/选择/显示内容。然后单元测试它。

#2


5  

There is a great way to test PyGTK functions and widgets directly, without going through acceptance/functional/integration testing frameworks that clikety their way into oblivion. I learned about this in this post which is fairly self explanatory. But the basic idea is that you treat your widgets as functions/classes, and you can test them directly. If you need to process callbacks and so on, there's a neat trick that I will reproduce here:

有一种很好的方法可以直接测试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)

As mentionned in the blog post, this code is LGPL. Otherwise, when you think about it, as long as you don't show() windows or widgets, you can test them all you want and they should behave as if they were real because, in a way, they are. They are just not displayed.

正如博客文章中提到的,这段代码是LGPL。否则,当你想到它时,只要你不显示()窗口或窗口小部件,你可以测试它们你想要的所有东西,它们应该表现得好像它们是真实的,因为在某种程度上它们是真的。他们只是没有显示。

Of course, you need to simulate interaction on buttons and interactive widgets yourself by calling clicked() on a button for example. See again Ali Afshar's excellent post about unit testing in PyGTK.

当然,您需要通过调用按钮上的clicked()来自己模拟按钮和交互式小部件上的交互。再看看Ali Afshar在PyGTK中关于单元测试的优秀帖子。

#3


2  

In sticking with the theme that Jürgen was correct in that I'm not interested in unit testing, but actually interested in integration testing, I also found this framework from freedesktop.org: http://ldtp.freedesktop.org/wiki/

坚持Jürgen的主题是正确的,因为我对单元测试不感兴趣,但实际上对集成测试感兴趣,我也从freedesktop.org找到了这个框架:http://ldtp.freedesktop.org/wiki/

It allows automating a variety of tests for Accessibility-enabled GUI applications (including GTK, Qt, Swing, etc).

它允许为启用辅助功能的GUI应用程序(包括GTK,Qt,Swing等)自动执行各种测试。

#4


0  

You can use a X11 framebuffer:

您可以使用X11帧缓冲:

Xvfb :119 -screen 0 1024x768x16 &
export DISPLAY=:119
... run your tests

Be sure, to not enter gtk.main(), since this would wait for mouse or keyboard input. You can use this pattern to let gtk handle all events:

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

def refresh_gui():
  while gtk.events_pending():
      gtk.main_iteration_do(block=False)

AFAIK you can't see your application, but you can test your callbacks.

AFAIK你看不到你的应用程序,但你可以测试你的回调。