September 18, 2007
Mocking wxpython for unit testing
Unit testing GUI code is usually a pain, but Python makes this so easy that I laugh with glee every time I do it.
My approach is to declare my own fake versions of the main wxpython widgets, and then overwrite the wx namespace with my fake classes. This idea is basically borrowed from a post on dirt simple. What would be serious black-magic voodoo in a lot of languages is a simple two-liner in python:
import wx
wx.__dict__.update(locals())
OK, so maybe you're saying, hey, you're not actually testing your wxpython code! Here's the deal:
- I don't really want to unit test wxpython.
- I want to mock elements that require user input, like file/dir dialogs, etc. This approach lets me simulate user interaction with the fake classes.
- Creating a
wx.Appfor every test is going to significantly slow down the execution of our unit tests. If my unit tests take 5 seconds to run instead of 2, I'm simply going to run them less often.
I'll test the actual GUI functionality using function/acceptance tests.
So now in my unit tests, all I need to do is import fakewidgets before my GUI code module(s), and fakewidgets works its magic.
>>> frame = wx.Frame(None)
Traceback (most recent call last):
<…traceback ommitted…>
wx._core.PyNoAppError: The wx.App object must be created first!
>>> import fakewidgets # Will overwrite wx namespace
>>> frame = wx.Frame(None)
>>> frame
<fakewidgets.Frame instance at 0×00C7F710>
>>>
The linked zip file (fakewidgets.zip) has my fakewidgets module. It's still not complete, since I've simply been adding classes/methods as I needed them for my unit tests, but it should be straightforward enough to add in what else you need. If you do, please pass your additions back on!
Here's how you might use the module to unit test your GUI code. Below is a very simple/silly program that pops up a frame window.
simpleframe.py
"""
import wx
class SimpleFrame(wx.Frame):
def __init__(self, *args, **kwargs):
wx.Frame.__init__(self, *args, **kwargs)
class SimpleApp(wx.App):
def OnInit(self):
frame = SimpleFrame(parent=None, title="Simple Frame")
frame.Show()
return 1
def main():
app = SimpleApp(0)
app.MainLoop()
if __name__ == "__main__":
main()
Here's how you could unit test this (I'm assuming that you're using the excellent nose module for unit testing).
import simpleframe
def test_main():
simpleframe.main()
class TestSimpleFrame(object):
def setup(self):
self.frame = simpleframe.SimpleFrame(None, title="Simple")
def test_create(self):
pass
class TestSimpleApp(object):
def setup(self):
self.app = simpleframe.SimpleApp(0)
def test_create(self):
pass
def test_on_init(self):
retval = self.app.OnInit()
assert retval == 1, retval
Look ma, no wx.App! We can even call functions that call App.MainLoop() with impunity.