 41e98f0bef
			
		
	
	
		41e98f0bef
		
	
	
	
	
		
			
			git-svn-id: svn://svn.cy55.de/Zope3/src/loops/trunk@2069 fd906abe-77d9-0310-91a1-e0d9ade77398
		
			
				
	
	
		
			343 lines
		
	
	
	
		
			13 KiB
		
	
	
	
		
			HTML
		
	
	
	
	
	
			
		
		
	
	
			343 lines
		
	
	
	
		
			13 KiB
		
	
	
	
		
			HTML
		
	
	
	
	
	
| <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
 | |
| <html>
 | |
| <head>
 | |
| <title>Windows Application Test System Using Python</title>
 | |
| <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
 | |
| <link href="../tac.css" rel="stylesheet" type="text/css">
 | |
| </head>
 | |
| 
 | |
| <body>
 | |
| <img src="../images/tizmoi.jpg">
 | |
| <H2>WATSUP - Windows Application Test System Using Python</H2>
 | |
| 
 | |
| The WATSUP toolkit is designed to allow the automated test of Windows applications. 
 | |
| The system uses the "object-based" mechanism for identifying and invoking actions 
 | |
| on controls and menu items. 
 | |
| <P>So much has been written about the scope, robustness, scalability and outstanding 
 | |
|   usability of Python that I'll go no further with it here, only to say that if 
 | |
|   you haven't yet had a chance to use this comprehensive, open source language, 
 | |
|   don't miss out on the opportunity to take a serious look at it!</P>
 | |
|  
 | |
|  <p>The examples in this document assume a basic familiarity with Python.</p>
 | |
|  
 | |
| 
 | |
| 
 | |
| <H2>Functional Tests</H2>
 | |
| 
 | |
| Testers/developers write automated functional tests which follow a prescriptive, 
 | |
| possibly branching, possibly dynamic "user workflow". The script can check for 
 | |
| changes in the gui itself, operating system environment, file system, database 
 | |
| table and records, network, internet or extranet urls/pages/web services ... - in 
 | |
| fact anywhere that there could be changes. 
 | |
| <p>
 | |
| Examination of the functions in module autoWinGui.py within the watsup package shows the variety of windows control items
 | |
| that can be checked/modified. These include:
 | |
| <ul>
 | |
| <li>Get and set text in editable controls</li>
 | |
| <li>Edit and select items from controls supporting lists</li>
 | |
| <li>Click and double-click controls to invoke their actions</li>
 | |
| <li>Determine the state of menu items and invoke them</li>
 | |
| </ul>
 | |
| <p> The system also provides tools for finding windows by caption and/or class, 
 | |
|   controls by text/caption and/or class, and menu items by text or position. (One 
 | |
|   of the aspirations of this project is to continue to extend the list to include 
 | |
|   as many controls as possible) .</p>
 | |
|   
 | |
| <H3>Example 1 - automated writing on Notepad</H3>  
 | |
| <p>Here's a simple example of the control over applications that you can have with watsup. 
 | |
| First, launch notepad from:</p> <p>Windows Start Menu - All Programs - Accessories - Notepad</p>
 | |
| Then run the following script (<a href="code/example1.py">Example 1</a>)
 | |
| <code>
 | |
| <pre>
 | |
| from watsup.winGuiAuto import findTopWindow,findControl,setEditText
 | |
| from time import sleep  
 | |
| # Locate notepad's edit area, and enter various bits of text.
 | |
| 
 | |
| notepadWindow = findTopWindow(wantedClass='Notepad')
 | |
| editArea = findControl(notepadWindow,wantedClass="Edit") 
 | |
| setEditText(editArea, "Hello, again!")  
 | |
| sleep(0.8) 
 | |
| setEditText(editArea, " You still there?",True) 
 | |
|  </pre></code> 
 | |
|  Finally, close notepad.<p></p> 
 | |
|  
 | |
|  <h3>Example 2 - testing a simple example </h3>
 | |
|  
 | |
| In functional tests, the tester wants to ensure that 
 | |
| the cause - invoking a sequence of windows events (button clicks, menu item activation) 
 | |
| has the predicted effect of, for example, a change in a value of a sindows control,
 | |
| the creation of a file, or the entry of a new database record. 
 | |
| See the directory watsup/examples/simple directory for the executable simple.exe.
 | |
| 
 | |
| If you run the application, you see a button and a text box. 
 | |
| Enter a valid filename into the box, say xyz, and
 | |
| click the button; 
 | |
| after the file is created, a message box appears containing a success message,
 | |
| and investigation of the directory watsup/examples/simple will show a file
 | |
| called 'xyz.txt' has been created (or overwritten).
 | |
| 
 | |
| <p>Now let's script a test to automate this functionality. </p>
 | |
| 
 | |
| First find and launch the application. 
 | |
| Then run the following script (<a href="code/example2.py">Example 2</a>)
 | |
| <code><pre>
 | |
| from watsup.winGuiAuto import findControl,setEditText, findTopWindow,clickButton
 | |
| import os
 | |
| import os.path
 | |
| 
 | |
| FILENAME='atestfile.txt'
 | |
| 
 | |
| def main():
 | |
|     # delete any occurrence of this file from the disk
 | |
|     if os.path.exists(FILENAME):
 | |
|         os.remove(FILENAME)
 | |
|         
 | |
|     form=findTopWindow(wantedText='Simple Form')
 | |
|     button=findControl(form,wantedText='Create file')
 | |
|     editbox=findControl(form,wantedClass='TEdit')
 | |
|     
 | |
|     # enter a filename:
 | |
|     setEditText(editbox,[FILENAME])
 | |
|     print 'clicking button to create file'
 | |
| 		clickButton(button)
 | |
|     
 | |
|     # now check that the file is there
 | |
|     if os.path.exists(FILENAME):
 | |
|         print 'file %s is present' %FILENAME
 | |
|     else:
 | |
|         print "file %s isn't there" % FILENAME     
 | |
| 
 | |
| if __name__=='__main__':
 | |
|     main()    
 | |
| 
 | |
| 
 | |
| </pre>
 | |
| </code>
 | |
| 
 | |
|  
 | |
| <h3>Example 3 - automating program launch and termination</h3>
 | |
| 
 | |
| <p>It's a bit tedious having to start and close the application each time. 
 | |
| <a href="code/example3.py">Example 3</a> launches the application, 
 | |
| if it isn't already running, and terminates it on 
 | |
| completion of the test</p>
 | |
| 
 | |
| <code><pre>
 | |
| from watsup.launcher import launchApp,terminateApp
 | |
| from watsup.winGuiAuto import findTopWindows                            
 | |
| import example2
 | |
| 
 | |
| # find an instance of SimpleForm. If one isn't there, launch it
 | |
| 
 | |
| forms=findTopWindows(wantedText='Simple Form')
 | |
| if forms:
 | |
|     form=forms[0]
 | |
| else:
 | |
|     form=launchApp('simple.exe',wantedText='Simple Form')    
 | |
| 
 | |
| example2.main()
 | |
| 
 | |
| # and terminate the form
 | |
| terminateApp(form)    
 | |
| 
 | |
| </pre></code>
 | |
| 
 | |
| launchApp starts the application in a separate thread, 
 | |
| and looks for a window with caption containing "Simple Form", 
 | |
| returning the window handle of the recovered form.
 | |
| 
 | |
| terminateApp attempts to close the form, by trying to activate menu item File-Exit, or, failing that,
 | |
| sending Alt + F4.
 | |
| 
 | |
| <H3>Example 4 - finding windows and controls</H3>
 | |
| 
 | |
| <p>In building scripts, we need to be able to find the class and/or text of the many windows and controls 
 | |
| to be investigated or invoked.</p>
 | |
| 
 | |
| <p>In the tools directory, there's a tool - ShowWindows.bat - to assist us with this.</p>
 | |
| <img src="images/ShowWindows1.jpg" alt="Show Windows 1" />
 | |
| 
 | |
| 
 | |
|  <p>Clicking the "Register" button persists information about 
 | |
| the existing windows running on the system (and it tells you how many, FYI). 
 | |
| Clicking the "Find new" button will report all non-trivial windows and all their 
 | |
| constituent controls which have appeared in the windows environment 
 | |
| since the last "Register" click.
 | |
| 
 | |
| So to test our program simple.exe, launch ShowWindows, click Register. 
 | |
| Then launch simple.exe and 
 | |
| and click the Find New button. 
 | |
| The associated text box shows n/m, where m is the total number of new windows found,
 | |
| and n is the number of those which are significant (ie have any controls) and are reported. </p>
 | |
| 
 | |
| <img src="images/ShowWindows2.jpg" alt="Show Windows 2" />
 | |
| 
 | |
| 
 | |
| <H2>Performance Tests</H2>
 | |
| 
 | |
| <p>Performance tests, in this definition, are single-client scripts, 
 | |
| which are similar in operation to the functional tests above, 
 | |
| but for which certain steps of the tests 
 | |
| must either be done within an acceptable timeframe ("CHECKING") 
 | |
| and/or the time taken for those steps 
 | |
| should be recorded for subsequent analysis ("RECORDING").</p>
 | |
| 
 | |
| <p>WATSUP provides a simple mechanism to add such tests to existing functional tests. 
 | |
| In examples 4a & 4b, we launch almost identical applications, 
 | |
| perform.exe and perform2.exe respectively.
 | |
|  When the button is clicked, the text "Finished" is written
 | |
| to the edit box. The difference between the programs is that the former is
 | |
|  coded to wait for 1/2 second before "Finished"
 | |
| appears; in the latter case, the delay is 1.5 seconds.</p>
 | |
| 
 | |
| <p>In both cases, we are setting a performance test that the process 
 | |
| take no more than 1 second. 
 | |
| Clearly, we should expect example 4a to be ok and example 4b to fail.
 | |
| </p>
 | |
| 
 | |
| 
 | |
| <p>So we have <a href="code/example4a.py">Example 4a</a></p>
 | |
| <code><pre>
 | |
| from example4 import main
 | |
| 
 | |
| main('perform.exe','Performance Form 1')
 | |
| 
 | |
| </pre></code>
 | |
| <p>and <a href="code/example4b.py">Example 4b</a></p>
 | |
| <code><pre>
 | |
| from example4 import main
 | |
| 
 | |
| main('perform2.exe','Performance Form 2')
 | |
| 
 | |
| </pre></code>
 | |
| 
 | |
| <p>which reference <a href="code/example4.py">Example 4</a>:</p>
 | |
| <code><pre>
 | |
| 
 | |
| from watsup.launcher import launchApp,terminateApp 
 | |
| from watsup.winGuiAuto import findTopWindows, findControl,getEditText,clickButton  
 | |
| from watsup.performance import PerformanceCheck,PerformanceCheckError  
 | |
| from time import sleep,time                      
 | |
| 
 | |
| def main(myExecutable,myWantedText):
 | |
|     # find an instance of SimpleForm. If one isn't there, launch it
 | |
|     forms=findTopWindows(wantedText=myWantedText)
 | |
|     if forms:
 | |
|         form=forms[0]
 | |
|     else:
 | |
|         form=launchApp(myExecutable,wantedText=myWantedText)       
 | |
|     
 | |
|     button=findControl(form,wantedText='Click me')
 | |
|     editbox=findControl(form,wantedClass='TEdit')
 | |
| 
 | |
|     #start a performance check instance
 | |
|     p=PerformanceCheck()
 | |
|     
 | |
|     clickButton(button)    
 | |
|     
 | |
|     # belts and braces to avoid infinite waiting!
 | |
|     maxWaitTime=2.0
 | |
|     startTime=time()
 | |
|     
 | |
|     while time()-startTime<maxWaitTime:
 | |
|         t=getEditText(editbox)
 | |
|         if t:
 | |
|             break
 | |
|         else:
 | |
|             sleep(0.1)
 | |
|     else:
 | |
|         raise Exception,'Failed to get value after maxWaitTime of %s secs' % maxWaitTime        
 | |
|     
 | |
|     try:
 | |
|         try:
 | |
|            #do the check/recording step, identifying this step with the wantedtext
 | |
|             p.check(myWantedText,1.0)    
 | |
|         except PerformanceCheckError,e:
 | |
|             print '** Failed: %s' % e
 | |
|             
 | |
|     # and terminate the form
 | |
|     finally:
 | |
|         terminateApp(form)      
 | |
| 
 | |
| if __name__=='__main__':
 | |
|     print ' please run example4a or 4b'
 | |
| 
 | |
| </pre></code>
 | |
| 
 | |
| <h4>Key points in example4.py</h4>
 | |
| 
 | |
| <p>Immediately prior to clicking the button, we establish a PerformanceCheck instance, 
 | |
| which, among other things, establises a timer. 
 | |
| Every time the check() method is called on a PerformanceCheck instance, the following occurs:</p>
 | |
| 
 | |
| <p>Also if we are CHECKING (the default condition), 
 | |
| that time is checked against the number of seconds added as the
 | |
| second parameter. 
 | |
| If the elapsed time exceeds the value of the 2nd parameter, an exception is raised.</p>
 | |
| 
 | |
| <p>So in example4.py, we see that the check method requires a 1 second acceptance.
 | |
| Hence example4a succeeds and example4b fails.
 | |
| </p>
 | |
| 
 | |
| <H2>Regression Testing</H2>
 | |
| 
 | |
| <p>Functional and performance scripts such as those above are 
 | |
| immediately useable within a test framework -
 | |
| the excellent python unit test framework is highly recommended
 | |
| (unittest.py, which comes along with your python installation). 
 | |
| This then enables the tester to develop complete regression tests
 | |
| involving any combination of Functional & Performance testing. </p>
 | |
| <p>For an example of the use of functional and performance tests within 
 | |
| the unit test framework, run the program framework.bat in the tools subdirectory. 
 | |
| This will launch a nice user interface from which sets of test cases can be run: 
 | |
| </p>
 | |
| <img src="images/framework1.jpg">
 | |
| 
 | |
| <p>Select the File menu option, and you have the option to load files which 
 | |
| contain testcases, or directories/directory trees. If select the "Load files"
 | |
| option, and navigate up one directory, and then down through examples and the unittests 
 | |
| directories, you should find, and select, exampleTests.py. 
 | |
| The Framework program examines the selected file(s) and extracts the TestCases, 
 | |
| presents in the top frame</p>
 | |
| 
 | |
| <img src="images/framework2.jpg"> 
 | |
| 
 | |
| <p>Selecting Menu option Testing - Run Tests (or the long button labelled Run Tests),
 | |
|  will cause each test to be run; success or failure is shown in the lower frame</p>
 | |
| 
 | |
| <img src="images/framework3.jpg"> 
 | |
| <p>For more information on python's unit test module, refer to the python documentation.
 | |
| Finally, it is worth noting that this framework will work with any unit tests, not just 
 | |
| those specifically testing windows application, so you can test 
 | |
| elements of the logic that you have written in other applications. 
 | |
| 
 | |
| <H2>Downloads</H2>
 | |
| 
 | |
| Download <a href="../downloads/watsup-0.4.zip">watsup</a> here
 | |
| <p>(This package should be unzipped in the site-packages directory in your python installation)</p>
 | |
| 
 | |
| <H3> Dependencies</H3>
 | |
|  <ul>
 | |
|  <li><a href="http://www.python.org/download"> Python</a> (version at least 2.3)</li>
 | |
|  <li><a href="http://sourceforge.net/projects/pywin32">pywin32 </a></li>
 | |
| <li><a href="http://sourceforge.net/project/showfiles.php?group_id=71702">ctypes </a></li>
 | |
| <li><a href="http://www.rutherfurd.net/python/sendkeys/#binaries" >SendKeys </a></li>
 | |
| <li><a href="http://www.wxpython.org/download.php" >wxPython library</a>  (for tools)</li>
 | |
|  </ul>
 | |
|  
 | |
|  <h3>Credits</h3>
 | |
|  
 | |
| <p> The framework test tool was built from parts in unittestgui.py by Chris Liechti.
 | |
| Much of the core functionality in WATSUP derives from the important work by 
 | |
|   <a href="http://www.brunningonline.net/simon/python/index.html">Simon Brunning</a>, 
 | |
|   who, in his module winGuiAuto.py provided concise, accessible mechanisms to 
 | |
|   access and "click" windows controls & menus; Simon's work recognises the huge 
 | |
|   contribution that Mark Hammond has made to the python community (and wider) 
 | |
|   in providing pywin32, comprehensive win32 API modules for python. 
 | |
| 	</p>
 | |
| 	 
 | |
| Dr Tim Couper<br/>
 | |
| <a href="mailto:timc@tizmoi.net">timc@tizmoi.net</a>
 | |
| 
 | |
| </body>
 | |
| </html>
 |