Difference between revisions of "Doctests"

From Apache OpenOffice Wiki
Jump to: navigation, search
(To conclude)
Line 30: Line 30:
  
 
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:
 
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:
<code>[python]
+
<source lang=python>
import uno, unohelper </code>
+
import uno, unohelper </source>
  
 
We will also need some specific services and nammed constants from OpenOffice.org API. They can be imported as any regular python module:
 
We will also need some specific services and nammed constants from OpenOffice.org API. They can be imported as any regular python module:
<code>[python]
+
<source lang=python>
 
  from com.sun.star.connection import NoConnectException
 
  from com.sun.star.connection import NoConnectException
 
  from com.sun.star.beans import PropertyValue
 
  from com.sun.star.beans import PropertyValue
  from com.sun.star.text.ControlCharacter import PARAGRAPH_BREAK</code>
+
  from com.sun.star.text.ControlCharacter import PARAGRAPH_BREAK</source>
  
 
This last value is a named constant we will need when dealing with Writer. Let's verify its value:
 
This last value is a named constant we will need when dealing with Writer. Let's verify its value:
<code>[python]
+
<source lang=python>
 
  PARAGRAPH_BREAK
 
  PARAGRAPH_BREAK
 
0
 
0
</code>
+
</source>
  
 
Now, we load an helper module that provides some usefull shortcuts whendealing with pyUNO Bridge:
 
Now, we load an helper module that provides some usefull shortcuts whendealing with pyUNO Bridge:
<code>[python]
+
<source lang=python>
 
from oootools import OOoTools
 
from oootools import OOoTools
</code>
+
</source>
  
 
We now are ready to start our tests. The first action to do is to connect to a listening OOo instance we launched.
 
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 ...
 
Let's define the listening host we have to reach and the port ...
<code>[python]
+
<source lang=python>
 
HOST = 'localhost'
 
HOST = 'localhost'
 
PORT = 11111
 
PORT = 11111
</code>
+
</source>
  
 
We now call out helper connecting class:
 
We now call out helper connecting class:
<code>[python]
+
<source lang=python>
 
ooo = OOoTools(HOST, PORT)
 
ooo = OOoTools(HOST, PORT)
 
ctx = ooo.ctx
 
ctx = ooo.ctx
 
desktop = ooo.desktop
 
desktop = ooo.desktop
</code>
+
</source>
  
 
So, we are now connected to the listen OpenOffice.org instance
 
So, we are now connected to the listen OpenOffice.org instance
  
 
We open a new blank writer document:
 
We open a new blank writer document:
<code>[python]
+
<source lang=python>
 
doc = desktop.loadComponentFromURL("private:factory/swriter",'_blank',0,())
 
doc = desktop.loadComponentFromURL("private:factory/swriter",'_blank',0,())
 
doc.Text.String
 
doc.Text.String
</code>
+
</source>
  
 
To populate the document, a cursor is needed:
 
To populate the document, a cursor is needed:
<code>[python]
+
<source lang=python>
 
cursor = doc.Text.createTextCursor()
 
cursor = doc.Text.createTextCursor()
 
cursor.ParaStyleName
 
cursor.ParaStyleName
 
u'Standard'
 
u'Standard'
</code>
+
</source>
  
 
The default paragraph style is 'Standard', but we plan to write our outline. So we change the ParaStyleName under the cursor:
 
The default paragraph style is 'Standard', but we plan to write our outline. So we change the ParaStyleName under the cursor:
<code>[python]
+
<source lang=python>
 
cursor.ParaStyleName = "Heading 1"
 
cursor.ParaStyleName = "Heading 1"
 
cursor.ParaStyleName
 
cursor.ParaStyleName
 
u'Heading 1'
 
u'Heading 1'
</code>
+
</source>
  
 
It is now time to insert a first text:
 
It is now time to insert a first text:
<code>[python]
+
<source lang=python>
 
doc.Text.insertString(cursor, "Title Level 1", False)
 
doc.Text.insertString(cursor, "Title Level 1", False)
 
doc.Text.String
 
doc.Text.String
 
u'Title Level 1'
 
u'Title Level 1'
</code>
+
</source>
  
 
We insert a paragraph break and some other sentences with various styles:  
 
We insert a paragraph break and some other sentences with various styles:  
<code>[python]
+
<source lang=python>
 
doc.Text.insertControlCharacter(cursor, PARAGRAPH_BREAK, False)
 
doc.Text.insertControlCharacter(cursor, PARAGRAPH_BREAK, False)
 
cursor.ParaStyleName = "Heading 2"
 
cursor.ParaStyleName = "Heading 2"
Line 103: Line 103:
 
doc.Text.String
 
doc.Text.String
 
u'Title Level 1\nTitle Level 2\nA normal sentence !!!'
 
u'Title Level 1\nTitle Level 2\nA normal sentence !!!'
</code>
+
</source>
  
 
We did not affect any paragraph style for the last sentence, relying on the 'following style' property of the "Heading 2" style.
 
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:
 
So the paragraph style of this new paragraph should be different:
<code>[python]
+
<source lang=python>
 
cursor.ParaStyleName == "Heading 2"
 
cursor.ParaStyleName == "Heading 2"
 
False
 
False
 
cursor.ParaStyleName
 
cursor.ParaStyleName
u'Text body'</code>
+
u'Text body'</source>
  
 
To verify our outline, we generate a content index at the start of the document:
 
To verify our outline, we generate a content index at the start of the document:
<code>[python]
+
<source lang=python>
 
cursor = doc.Text.createTextCursorByRange(doc.Text.Start)
 
cursor = doc.Text.createTextCursorByRange(doc.Text.Start)
 
anIndex =doc.createInstance("com.sun.star.text.ContentIndex")
 
anIndex =doc.createInstance("com.sun.star.text.ContentIndex")
 
anIndex.supportsService("com.sun.star.text.ContentIndex")
 
anIndex.supportsService("com.sun.star.text.ContentIndex")
 
True
 
True
</code>
+
</source>
  
 
Setup some properties:
 
Setup some properties:
<code>[python]
+
<source lang=python>
 
anIndex.CreateFromOutline = True
 
anIndex.CreateFromOutline = True
 
anIndex.CreateFromLevelParagraphStyles = True
 
anIndex.CreateFromLevelParagraphStyles = True
 
anIndex.CreateFromChapter = False
 
anIndex.CreateFromChapter = False
 
anIndex.IsProtected=False
 
anIndex.IsProtected=False
</code>
+
</source>
  
 
and insert this first index at the cursor:
 
and insert this first index at the cursor:
<code>[python]
+
<source lang=python>
 
doc.DocumentIndexes.Count
 
doc.DocumentIndexes.Count
 
0
 
0
Line 136: Line 136:
 
anIndex.update()
 
anIndex.update()
 
doc.DocumentIndexes.Count
 
doc.DocumentIndexes.Count
1</code>
+
1</source>
  
 
We change the title of this index:
 
We change the title of this index:
<code>[python]
+
<source lang=python>
 
anIndex.HeaderSection.Anchor.String = "The testing index"
 
anIndex.HeaderSection.Anchor.String = "The testing index"
 
anIndex.HeaderSection.Anchor.String
 
anIndex.HeaderSection.Anchor.String
 
u'The testing index'
 
u'The testing index'
</code>
+
</source>
  
 
and verfiy its content:
 
and verfiy its content:
<code>[python]
+
<source lang=python>
 
anIndex.Anchor.String
 
anIndex.Anchor.String
 
u'The testing index\nTitle Level 1\t1\nTitle Level 2\t1'
 
u'The testing index\nTitle Level 1\t1\nTitle Level 2\t1'
 
doc.Text.String
 
doc.Text.String
u'The testing index\nTitle Level 1\t1\nTitle Level 2\t1\nTitle Level 1\nTitle Level 2\nA normal sentence !!!'</code>
+
u'The testing index\nTitle Level 1\t1\nTitle Level 2\t1\nTitle Level 1\nTitle Level 2\nA normal sentence !!!'</source>
  
 
So we have an outline with its index. Lets save this to PDF.
 
So we have an outline with its index. Lets save this to PDF.
  
 
We first define the export filter property:
 
We first define the export filter property:
<code>[python]
+
<source lang=python>
 
  args = (ooo.makePropertyValue('FilterName','writer_pdf_Export'),)
 
  args = (ooo.makePropertyValue('FilterName','writer_pdf_Export'),)
 
  args
 
  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 },)</code>
+
((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 },)</source>
  
 
We define a temp file and verify it is not already in use:
 
We define a temp file and verify it is not already in use:
  
<code>[python]
+
<source lang=python>
 
pdf_filename = os.path.join(tempfile.gettempdir() ,str(time.time()) + 'testooo-writer.pdf' )
 
pdf_filename = os.path.join(tempfile.gettempdir() ,str(time.time()) + 'testooo-writer.pdf' )
  
 
if os.path.isfile(pdf_filename):
 
if os.path.isfile(pdf_filename):
   os.remove(pdf_filename)</code>
+
   os.remove(pdf_filename)</source>
  
 
But OOo only deals with URL notations. We use the helper function and the store the file to this URL:
 
But OOo only deals with URL notations. We use the helper function and the store the file to this URL:
  
<code>[python]
+
<source lang=python>
 
url = unohelper.systemPathToFileUrl(pdf_filename )
 
url = unohelper.systemPathToFileUrl(pdf_filename )
doc.storeToURL(url,args)</code>
+
doc.storeToURL(url,args)</source>
  
 
We check briefly the file has been created and that it is not empty:
 
We check briefly the file has been created and that it is not empty:
<code>[python]
+
<source lang=python>
 
os.path.isfile(pdf_filename)
 
os.path.isfile(pdf_filename)
 
True
 
True
 
os.path.getsize(pdf_filename) != 0
 
os.path.getsize(pdf_filename) != 0
True</code>
+
True</source>
  
 
and finally delete the PDF file:
 
and finally delete the PDF file:
  
<code>[python]
+
<source lang=python>
 
if os.path.isfile(pdf_filename):
 
if os.path.isfile(pdf_filename):
     os.remove(pdf_filename)</code>
+
     os.remove(pdf_filename)</source>
  
 
Close our Writer file and all remaining documents:
 
Close our Writer file and all remaining documents:
<code>[python]
+
<source lang=python>
 
doc.close(False)
 
doc.close(False)
 
ooo.closeAll()
 
ooo.closeAll()
  
That's the end of our test !!!!!</code>
+
That's the end of our test !!!!!</source>
  
 
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.
 
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.
Line 204: Line 204:
  
 
Once doctest available, you can launch OpenOffice.org Python and test your file
 
Once doctest available, you can launch OpenOffice.org Python and test your file
<code>[python]
+
<source lang=python>
 
$ /opt/openoffice.org2.0/program/python
 
$ /opt/openoffice.org2.0/program/python
 
Python 2.3.4 (#1, Feb  1 2006, 21:07:49)
 
Python 2.3.4 (#1, Feb  1 2006, 21:07:49)
Line 211: Line 211:
 
import doctest
 
import doctest
 
doctest.testfile('my_test.txt')
 
doctest.testfile('my_test.txt')
(0, 156)</code>
+
(0, 156)</source>
  
 
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)
 
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)

Revision as of 23:03, 11 December 2009

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