Doctests

From Apache OpenOffice Wiki
Revision as of 23:08, 28 March 2010 by B michaelsen (Talk | contribs)

Jump to: navigation, search

Introduction on testing pyUNO programs with doctests

OpenOffice.org is great, Python is great. Guess what ? PyUNO is great

Scripting OpenOffice.org with Python is an efficient way to produce high quality additional functionalities to your favourite office suite.

But programs has to be tested to ensure reliability and so Python addons and pyUNO scripts. Python comes with various test tools and one of them is doctest.

Here is an example that illustrate a doctest use on a pyUNO script. We have 3 main steps

  • start OpenOffice.org in listen mode
  • write the doctest
  • run the doctest

Starting OpenOffice.org in listen mode

As we plan to use pyUNO for external scripting, we have to start OOo in listen mode as explained in the Developer's guide

 /opt/openoffice.org2.0/program/soffice -accept="socket,host=localhost,port=11111;urp;StarOffice.ServiceManager"

Write the doctest

A doctest is a plain text file mixing comments and Python code from the interpreter. The idea is to write a story, describing what happens while testing. Then we end with both a documentation and a testing program. The idea is to mimic what happens in the Python editor as if we typed directly the command. The expression is evaluated and compared to the given value

a+1 2

If the answer is not 2 while performing the test, it will fail and the error will be reported with the expected value against the current value

Here is a rather long (but not complex) example illustrating some basic OpenOffice.org Python scripting command. A more complete test (dealing also with calc and file export) is provided at the end. These basic commands can be usefull to start dealing with the OpenOffice.org API through pyUNO

We are going to test some basics aspect of Openoffice.org. OOo can be scripted using Python though pyUNO bridge First, we need to import some classical python modules:

import uno, unohelper

We will also need some specific services and nammed constants from OpenOffice.org API. They can be imported as any regular python module:

 from com.sun.star.connection import NoConnectException
 from com.sun.star.beans import PropertyValue
 from com.sun.star.text.ControlCharacter import PARAGRAPH_BREAK

This last value is a named constant we will need when dealing with Writer. Let's verify its value:

 PARAGRAPH_BREAK
0

Now, we load an helper module that provides some usefull shortcuts whendealing with pyUNO Bridge:

from oootools import OOoTools

We now are ready to start our tests. The first action to do is to connect to a listening OOo instance we launched.

Let's define the listening host we have to reach and the port ...

HOST = 'localhost'
PORT = 11111

We now call out helper connecting class:

ooo = OOoTools(HOST, PORT)
ctx = ooo.ctx
desktop = ooo.desktop

So, we are now connected to the listen OpenOffice.org instance

We open a new blank writer document:

doc = desktop.loadComponentFromURL("private:factory/swriter",'_blank',0,())
doc.Text.String

To populate the document, a cursor is needed:

cursor = doc.Text.createTextCursor()
cursor.ParaStyleName
u'Standard'

The default paragraph style is 'Standard', but we plan to write our outline. So we change the ParaStyleName under the cursor:

cursor.ParaStyleName = "Heading 1"
cursor.ParaStyleName
u'Heading 1'

It is now time to insert a first text:

doc.Text.insertString(cursor, "Title Level 1", False)
doc.Text.String
u'Title Level 1'

We insert a paragraph break and some other sentences with various styles:

doc.Text.insertControlCharacter(cursor, PARAGRAPH_BREAK, False)
cursor.ParaStyleName = "Heading 2"
doc.Text.insertString(cursor, "Title Level 2", False)
doc.Text.insertControlCharacter(cursor, PARAGRAPH_BREAK, False)
doc.Text.insertString(cursor, "A normal sentence !!!", False)
doc.Text.String
u'Title Level 1\nTitle Level 2\nA normal sentence !!!'

We did not affect any paragraph style for the last sentence, relying on the 'following style' property of the "Heading 2" style. So the paragraph style of this new paragraph should be different:

cursor.ParaStyleName == "Heading 2"
False
cursor.ParaStyleName
u'Text body'

To verify our outline, we generate a content index at the start of the document:

cursor = doc.Text.createTextCursorByRange(doc.Text.Start)
anIndex =doc.createInstance("com.sun.star.text.ContentIndex")
anIndex.supportsService("com.sun.star.text.ContentIndex")
True

Setup some properties:

anIndex.CreateFromOutline = True
anIndex.CreateFromLevelParagraphStyles = True
anIndex.CreateFromChapter = False
anIndex.IsProtected=False

and insert this first index at the cursor:

doc.DocumentIndexes.Count
0
doc.Text.insertTextContent(cursor, anIndex, False)
anIndex.update()
doc.DocumentIndexes.Count
1

We change the title of this index:

anIndex.HeaderSection.Anchor.String = "The testing index"
anIndex.HeaderSection.Anchor.String
u'The testing index'

and verfiy its content:

anIndex.Anchor.String
u'The testing index\nTitle Level 1\t1\nTitle Level 2\t1'
doc.Text.String
u'The testing index\nTitle Level 1\t1\nTitle Level 2\t1\nTitle Level 1\nTitle Level 2\nA normal sentence !!!'

So we have an outline with its index. Lets save this to PDF.

We first define the export filter property:

 args = (ooo.makePropertyValue('FilterName','writer_pdf_Export'),)
 args
((com.sun.star.beans.PropertyValue){ Name = (string)"FilterName", Handle = (long)0x0, Value = (any){ (string)"writer_pdf_Export" }, State = (com.sun.star.beans.PropertyState)DIRECT_VALUE },)

We define a temp file and verify it is not already in use:

pdf_filename = os.path.join(tempfile.gettempdir() ,str(time.time()) + 'testooo-writer.pdf' )
 
if os.path.isfile(pdf_filename):
   os.remove(pdf_filename)

But OOo only deals with URL notations. We use the helper function and the store the file to this URL:

url = unohelper.systemPathToFileUrl(pdf_filename )
doc.storeToURL(url,args)

We check briefly the file has been created and that it is not empty:

os.path.isfile(pdf_filename)
True
os.path.getsize(pdf_filename) != 0
True

and finally delete the PDF file:

if os.path.isfile(pdf_filename):
     os.remove(pdf_filename)

Close our Writer file and all remaining documents:

doc.close(False)
ooo.closeAll()
 
That's the end of our test !!!!!

This code is written in a my_test.txt file. There is to be noticed that taking all the line starting with >>> leads to a workable code.

Run the doctest

OpenOffice.org is shipped with Python 2.3.4. Doctest is available on this version but lacks the testfile method only available in python 2.4. There are some workaround like

  • replacing python with a newer (be carefull, this will not work if you use OpenOffice.org builds from the project with debian
  • need to rebuild python due to UCS options
  • Using debian OpenOffice.org build works, of course)
  • quick and dirty : copy a doctest.py file from your python 2.4 to /opt/openoffice.org2.0/program/python-core-2.3.4/lib

Once doctest available, you can launch OpenOffice.org Python and test your file

$ /opt/openoffice.org2.0/program/python
Python 2.3.4 (#1, Feb  1 2006, 21:07:49)
[GCC 3.4.1] on linux2
Type "help", "copyright", "credits" or "license" for more information.
import doctest
doctest.testfile('my_test.txt')
(0, 156)

The doctest returns a couple of values : the first is the number of failed tests (0 here), the second the total number of tests (156 here) More details is given on errors. Options can be added to the doctest.testfile() method.

To conclude

Python is an efficient language that can be used for OpenOffice.org. pyUNO programmers can use doctest to evaluate their programs and perform regression tests. This will lead to high quality and robust Openoffice.org Extensions.


Nuxeo Original blog entry

The complete test

Helper for OOo PyUNO

Personal tools