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

Scripting 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 in listen mode
  • write the doctest
  • run the doctest

Starting 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


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 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 API through pyUNO

We are going to test some basics aspect of
OOo can be scripted using Python though pyUNO bridge

First, we need to import some classical python modules:

>>> import sys, os, tempfile, time

Then, the pyUNO modules that are delivered within OOo:

>>> import uno, unohelper

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

>>> from import NoConnectException
>>> from import PropertyValue
>>> from import PARAGRAPH_BREAK

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


Now, we load an helper module that provides some usefull shortcuts when
dealing 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

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 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

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 1nTitle Level 2nA 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"
>>> cursor.ParaStyleName
u'Text body'

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

>>> cursor = doc.Text.createTextCursorByRange(doc.Text.Start)
>>> anIndex =doc.createInstance("")
>>> anIndex.supportsService("")

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
>>> doc.Text.insertTextContent(cursor, anIndex, False)
>>> anIndex.update()
>>> doc.DocumentIndexes.Count

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 indexnTitle Level 1t1nTitle Level 2t1'
>>> doc.Text.String
u'The testing indexnTitle Level 1t1nTitle Level 2t1nTitle Level 1nTitle Level 2nA 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
(({ Name = (string)"FilterName", Handle = (long)0x0, Value = (any){ (string)"writer_pdf_Export" }, State = ( },)

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)
>>> os.path.getsize(pdf_filename) != 0

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

Run the doctest 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
    with a newer (be carefull, this will not work if you use builds from the project with debian - need
    to rebuild python due to UCS options - Using debian build works, of course)
  • quick and dirty : copy a file from your python 2.4 to

Once doctest available, you can launch Python and test your

$ /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
pyUNO programmers can use doctest to evaluate their programs and perform regression tests. This will lead to high quality and robust Extensions

(Post originally written by Laurent Godard on the old Nuxeo blogs.)