Functional Testing: Testing MultiDoc Part II

Interacting with the Application Under Test

Test the New Page Dialog

MultiDoc: from IronPython in Action

For the second test we'll do something a bit more involved - testing the toolbar button that brings up the 'New Page' dialog.

  • Clicking the '+' icon activates the 'New Page' dialog
  • How do we do this programmatically?
  • To make it worse: this is a modal dialog
  • So invoking it will block

Hooks into Your Application

  • Design your application with testing in mind
  • How you do this is up to you
  • Examples for MultiDoc:
button = self.mainForm.toolBar.Items[3]
tabPages = self.mainForm.tabControl.TabPages
dialog.AcceptButton.PerformClick()

In the first two of these examples we investigate the state of the application by using exposed attributes. The danger is that our functional tests can end up knowing a bit too much about the implementation of our application. There are some alternative patterns we can use to make our tests less fragile to changes in layout, and I'll discuss these in the final section.

The third example automates accepting a dialog by calling AcceptButton.PerformClick(). This works fine - it programamtically invokes the button - but there is an alternative pattern that provides an advantage. More on this shortly...

We can also use code like:

SendKeys.SendWait('Some text...')
Cursor.Position = Point(150, 250)

Cursor and SendKeys are Windows Forms classes. They allow us to move the mouse and send key-presses to an application.

In order to send click events to specific locations (and perform actions like dragging the mouse with one button pressed) on Windows you need to use the win32api. Some examples (and code) for this is shown on the GUI Automated Testing Blog.

The AsyncExecutor

Next dealing with the problem that invoking an action that shows a dialog will block. We can use an AsyncExecutor to actually invoke this action from its own thread! executeAsynchronously is a convenience method for our FunctionalTest class that does this.

def executeAsynchronously(self, function):
    def AsyncFunction():
        return self.invokeOnGUIThread(function)
    executor = AsyncExecutor(AsyncFunction)
    return executor

See the full source code (available for download) for the implementation of the AsyncExecutor. It takes a function and launches it on a separate thread. The join method joins this thread - testing that your operation has exited cleanly. We can use this to invoke actions that block without blocking our test thread. (Windows Forms does provide an asynchronous invoke API - but it is not especially useful.)

def clickNewPageButton(self):
    button = self.mainForm.toolBar.Items[3]
    executor = self.executeAsynchronously(button.PerformClick)
    Thread.CurrentThread.Join(200)
    return executor

clickNewPageButton is a convenience method for our functional test. It fetches the toolbar button and executes the click (launching the dialog) with an AsyncExecutor.

Cancelling the Dialog

In the first part of our new user story Harold clicks the toolbar button and then cancels the dialog.

# * Harold clicks on the 'New Page' toolbar button
executor = self.clickNewPageButton()

# * A dialog called 'Name Tab' appears
dialog = self.invokeOnGUIThread(lambda: Form.ActiveForm)
title = self.invokeOnGUIThread(lambda: dialog.Text)
self.assertEquals(title, "Name Tab", "Incorrect dialog name")

# * Harold changes his mind, so he selects cancel
self.invokeOnGUIThread(dialog.CancelButton.PerformClick)
executor.join()

# * There is still only one tab
GetNumPages = lambda: len(self.mainForm.tabControl.TabPages)
numPages = self.invokeOnGUIThread(GetNumPages)
self.assertEquals(numPages, 1, "Wrong number of tabPages")

After cancelling the dialog there should still only be one tab.

Accepting the Dialog

In the second part of the test Harold types a new name into the dialog and accepts it.

# * Our capricious user clicks the button again
executor = self.clickNewPageButton()

# * The dialog appears again
dialog = self.invokeOnGUIThread(lambda: Form.ActiveForm)

# * This time he enters a name 'My New Page'
self.invokeOnGUIThread(lambda: SendKeys.SendWait('My New Page'))

# * He clicks on OK
self.invokeOnGUIThread(dialog.AcceptButton.PerformClick)

# * There are now two tabs
numPages = self.invokeOnGUIThread(GetNumPages)
self.assertEquals(numPages, 2, "Wrong number of tabPages")

After accepting the dialog there should be two tab pages in the tab controller.

You can run this test by starting newpagefunctionaltest.bat from the example code.

The actual test is FunctionalTest/tests/newpagefunctionaltest.py. It sleeps for 5 seconds around every operation and prints out what it is doing to the console.

In the final page we look at some of the problems with functional testing and how we can address the difficulties.

For buying techie books, science fiction, computer hardware or the latest gadgets: visit The Voidspace Amazon Store.

Hosted by Webfaction

Return to Top

Page rendered with rest2web the Site Builder

Last edited Tue Aug 2 00:51:33 2011.

Counter...