Functional Testing: Testing MultiDoc Part II
Interacting with the Application Under Test
Test the New Page Dialog

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:
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:
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 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.)
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.
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.
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.
Last edited Fri Nov 27 18:32:35 2009.
Counter...

