Unit Testing PyGTK
Unit testing a GUI has always been something that scares me, and scares other people too.
Because of this there are about a gazillion tools that behave in different ways, for example in Python, just have a look at:
http://pycheesecake.org/wiki/PythonTestingToolsTaxonomy#GUITestingTools
(Since the whole world is obsessed with Web Applications, and this is becoming perversely true even in Python circles, some of these GUI Testing tools are actually web testing tools, but never mind that.)
How do I do it? Well, we may talk in another blog about one of those tools, the awesome hack that is kiwi.ui.test but it is slightly restrictive on what you want to do, and how you should do it. Specifically, it needs you to have every widget named (which is fine when testing Glade-created interfaces, but a pain when hand-building them).
Enter this one function that I found in the kiwi source. Kiwi is LGPL (whatever that means, but you should read the license if you are going to use it).
Simple, and tiny, and it has the effect of turning your awful asynchronous run-events-when-it-wants-to User Interface into a simple synchronous thing.
How? Well simply it runs all the events that are waiting in the gtk event queue, as if you ran gtk.main() for a short while until it was done with everything.
The optional delay argument is a short delay that might prove useful, but I have yet to actually use it.
Let's look at an example of how to use this in a unit test.
And we run it:
Great!
Things we should note:
Conclusion: Keep it simple! In a GUI Testing War, I would hedge this function against anything.
Because of this there are about a gazillion tools that behave in different ways, for example in Python, just have a look at:
http://pycheesecake.org/wiki/PythonTestingToolsTaxonomy#GUITestingTools
(Since the whole world is obsessed with Web Applications, and this is becoming perversely true even in Python circles, some of these GUI Testing tools are actually web testing tools, but never mind that.)
How do I do it? Well, we may talk in another blog about one of those tools, the awesome hack that is kiwi.ui.test but it is slightly restrictive on what you want to do, and how you should do it. Specifically, it needs you to have every widget named (which is fine when testing Glade-created interfaces, but a pain when hand-building them).
Enter this one function that I found in the kiwi source. Kiwi is LGPL (whatever that means, but you should read the license if you are going to use it).
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)
Simple, and tiny, and it has the effect of turning your awful asynchronous run-events-when-it-wants-to User Interface into a simple synchronous thing.
How? Well simply it runs all the events that are waiting in the gtk event queue, as if you ran gtk.main() for a short while until it was done with everything.
The optional delay argument is a short delay that might prove useful, but I have yet to actually use it.
Let's look at an example of how to use this in a unit test.
from unittest import TestCase, main
import gtk
class MyView(gtk.VBox):
def __init__(self):
super(MyView, self).__init__()
self._button = gtk.Button('Click Me')
self._label = gtk.Label()
self.pack_start(self._button)
self.pack_start(self._label)
self._count = 0
self._button.connect('clicked', self.on_button_clicked)
def on_button_clicked(self, button):
self._count = self._count + 1
self._label.set_text('clicked %s times' % self._count)
class MyViewTest(TestCase):
def setUp(self):
self._v = MyView()
def test_count(self):
self.assertEqual(self._v._count, 0)
self._v._button.clicked()
refresh_gui()
self.assertEqual(self._v._count, 1)
def test_label(self):
self._v._button.clicked()
refresh_gui()
self.assertEqual(self._v._label.get_text(), 'clicked 1 times')
if __name__ == '__main__':
main()
And we run it:
Ran 2 tests in 0.013s
OK
Great!
Things we should note:
- We never had to put our custom widget into a window
- We never had to display it to the outside world
- We can be sure asynchronous things like signal callbacks have happened
- We can treat our UI as a purely synchronous application
- We use unittest.TestCase, we don't need any fancy UI testing framework's subclasses.
Conclusion: Keep it simple! In a GUI Testing War, I would hedge this function against anything.