[TIP] best strategy for retrofitting code with unit tests

Michael Foord fuzzyman at voidspace.org.uk
Mon Mar 5 15:21:47 PST 2007


Nate Lowrie wrote:
> On 3/5/07, Michael Foord <fuzzyman at voidspace.org.uk> wrote:
>> Nate Lowrie wrote:
>> > Ok, I have done a couple of small - medium sized projects through a
>> > TDD cycle and have been successful at it.  Now, we are trying to
>> > retrofit a large framework (Dabo) with unit tests.  It's a 3 tier
>> > framework with an agnostic db layer, business object/logic layer, and
>> > a UI layer that currently wraps WxPython.
>> >
>> > What is the best strategy for this?  Should we just adopt a TDD for
>> > all new code and do a refactor/red/green cycle on the existing code as
>> > the new code comes in?  Should we unit test the business layer only
>> > and resign to acceptance testing on the other 2?
>> >
>>
>> NB I've not had to *do* this, so my suggestion is theoretical only.
>>
>> I would add acceptance tests for the higher levels as soon as you can.
>
> Any suggestions for acceptance testing the UI layer?  It is a wrapper
> over wx that makes th UI more pythonic.  It also constitutes about 60%
> of the code base.  I remember seeing something about pywinauto but
> wasn't quite sure if that was for window forms or another toolkit.
>

pywinauto is for general Windows automation - so it may well be what you 
need.

For Windows Forms we hit the win32 api directly for automation. This is 
easy because exposing the win32 api to IronPython code with the use of a 
bit of C# is trivially easy.

You can probably get the same effect using the pywin32 extensions by 
Mark Hammond. The relevant stuff you need is almost all in User32.dll. 
You might find that pywinauto or pyAA expose most of this already.

To test the results our functional test framework gives us access to the 
controls we need (text boxes etc) so that we can test their state.

We have a class called 'FunctionalTest' which is a subclass of 
'unittest.TestCase'. This exposes methods like 'getMainTextBox'. The 
setUp of FunctionalTest launches the application.

We can then do (trivial example) :

textbox = self.getMainTextBox()
SendKeys.SendWait('SomeText{ENTER}')
self.assertEquals(textbox.Text, "SomeText")

We have convenience methods to move the mouse to the centre of a GUI 
component and do a left click or a right click. (Or a drag operation or 
whatever - simulating user input.)

There is a bit of added complexity [#]_ because the GUI is inevitably 
running on another thread - so to access live properties of GUI 
components you probably need to invoke calls onto the GUI thread. 
Windows Forms makes this easy, you would need to see how wx does this.

It is quite a bit of work to get a good functional test framework in 
place - but well worth the effort.

We've put quite a lot of work into our convenience functions (including 
custom asserts) - so the calls to simulate user action are effectively a 
DSL. We have actually considered creating a mini-specification language 
for our user stories, which could be automatically be translated into 
actions and tests.

    The user clicks the red button
    The scary dialog appears with the message "don't press the red button"

Could be translated to :

self.pressRedButton()
scaryDialog = self.dialogManager().getScaryDialog()
self.assertDialogVisible(scaryDialog, message="don't press the red button")

All the invokes are done 'behind the scenes', so I assert that our 
functional tests (built on the unit test framework) are generally very 
readable.

All the best,

Fuzzyman
http://www.voidspace.org.uk/python/articles.shtml

.. [#] Plus further complexity because SendKeys seems to be unreliable. 
We have a version which checks that the keypreses actually arrive and 
resends ones that are dropped. *sigh*





More information about the testing-in-python mailing list