Doctests
Introduction on testing pyUNO programs with doctests
OpenOffice.org is great, Python is great. Guess what ? PyUNO is great
Scripting Apache OpenOffice 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 Apache OpenOffice 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 Apache OpenOffice 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. Apache OpenOffice 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 named constants from Apache OpenOffice 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 Apache OpenOffice 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 Apache OpenOffice 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. Let's 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 Apache OpenOffice 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
Apache OpenOffice 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 Apache OpenOffice builds from the project with debian
- need to rebuild python due to UCS options
- Using debian Apache OpenOffice 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 Apache OpenOffice 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 Apache OpenOffice Extensions.