Convert Java unit test to Python

see also: Development/Python_Unit_Tests







In order to simplify our unit test setup it was decided to convert all the Java-based unit tests to be Python-based. The workflow roughly is:

Create a new python test Create new make file or extend already existing one Add new test to this Makefile Remove old Java unit test.

Check: https://gerrit.libreoffice.org/4294 and https://gerrit.libreoffice.org/6017 for inspiration.

Developers who want to learn about the UNO interface (the universal interface to LibreOffice) are recommended to submit a couple of patches.

There are a couple of pitfalls when converting, this page tries to provide some common help. For specific help please talk with us on freenode or mail.

Where to find the unit tests

All our tests can be found in <module>/qa/*

This is an example of a succesful patch: gerrit patch

Corresponding makefiles

There are 2 makefiles you need to modify:

<module>/JunitTest_<module>_<foo>.mk remember to remove reference to the java file

remember to remove reference to the java file <module>/PythonTest_<module>_<foo>.mk normally you need to add the new file in 2 places

Delete the Java file:

<module>/qa/*/<foo>.java

Add the Python file

<module>/qa/python/<foo>.py

Code translation hints

A few things are actually a lot easier to do with the Python unit tests than with the Java test framework, because:

Python is dynamically typed

Python tests are run in process instead of connecting from an outside process

the Python bindings are provide a deeper integration into the language that Java (they are "pythonic")

When translationg code that might lead to the wonders on "How to do this Java code in Python?", when the Java code is indeed superficial in Python -- there is no explicit code needed to do what the Java code does.

Dynamically typed (python) versus strongly typed (java) languages

You would _not_ need most of the following in dynamically typed languages like Python or StarBasic, only in statically typed languages like Java or C++.

A typical java construct:

m_xMsf = UnoRuntime . queryInterface ( XMultiServiceFactory . class , connection . getComponentContext (). getServiceManager ());

Generally in UNO you are communicating by calling function over references to interfaces, not references to services or implementations.

Björn made an excellent talk at a LibreOffice conference to this theme, please watch the first 5 minutes of conference video.

Lets take the other java example:

XTextCursor xParaCrsr = xText . createTextCursor (); XTextRange xParaCrsrAsRange = UnoRuntime . queryInterface ( XTextRange . class , xParaCrsr ); xText . insertControlCharacter ( xParaCrsrAsRange , com . sun . star . text . ControlCharacter . PARAGRAPH_BREAK , false );

The first line:

XTextCursor xParaCrsr = xText . createTextCursor ();

according the documentation at: doc

com::sun::star::text::XTextCursor.createTextCursor() returns a new instance of a TextCursor service which can be used to travel in the given text context.

So this gives you a reference to the com::sun::star::text:XTextCursor interface of a com::sun::star::text::TextCursor service. Note that there is a difference between a service and an interface.

However, from: XSimpleText

we know we need to give insertControlCharacter(..) a com::sun::star::text::XTextRange reference as first parameter, not a com::sun::star::text::XTextCursor reference, which is different. We know from the documentation of createTextCursor(..), that the com::sun::star::text::XTextRange reference holds a com::sun::star::text::TextCursor service and we can see from: TextCursor text that the com::sun::star::text::TextCursor service extends a com::sun::star::text::TextRange service: TextCursor text which implements a com::sun::star::text::XTextRange interface: textrange

This can be rather confusing, but try to take it step by step then it hopefully makes sense. There are a lot of UNO Documentation and it is not always easy to match head and tail.

So:

- we have a ...text::XTextCursor reference to a ...text::TextCursor service

- we know that the ...text::TextCursor service also implements the ...text::XTextRange interface, that we need.

Thus (in Java) we need have a way to get a reference to a com::sun::star::text::XTextRange interface out of our reference to a com::sun::star::text::XTextCursor , which we know holds a com::sun::star::text::TextCursor and:

XTextRange xParaCrsrAsRange = UnoRuntime . queryInterface ( XTextRange . class , xParaCrsr );

does that: xParaCrsrAsRange is now a reference to a com::sun::star::text::XTextRange interface of the com::sun::star::text::XTextCursor. Thus we can call insertControlCharacter(..) with that.

xText . insertControlCharacter ( xParaCrsrAsRange , com . sun . star . text . ControlCharacter . PARAGRAPH_BREAK , false );

So how does that work in dynamically typed languages like StarBasic or Python? Well, there you just hold a reference, with no static typing attached to it. Thus you can use _any_ of the interfaces implemented by the service behind it without doing any confusing conversions. If you do:

XTextCursor xParaCrsr = xText . createTextCursor ();

in Java, you can _only_ use that as a XTextCursor without conversions. If you do:

xParaCrsr = xText . createTextCursor ()

in Python, you can use that as XTextCursor, XWordCursor, XSentenceCursor, XParagraphCursor, XPropertySet, XPropertyState, XMultiPropertyStates, XDocumentInsertable, XSortable. And thus for the Java code the equivalent in Python is simply:

xParaCrsr = xText . createTextCursor () xText . insertControlCharacter ( xParaCrsr , PARAGRAPH_BREAK , False )

because there is no need at all for conversion or even a xParaCrsrAsRange variable. So, in Python (or StarBasic) you never need to use queryInterface() at all!

Out-of-process UNO (Java tests) vs. In-process UNO (Python tests)

There is often a lot of boilerplate code in Java tests to connect to LibreOffice and create a test document, e.g.:

public class SomeTest { private static final OfficeConnection connection = new OfficeConnection (); @BeforeClass public static void setUpConnection () throws Exception { connection . setUp (); } @AfterClass public static void tearDownConnection () throws InterruptedException , com . sun . star . uno . Exception { connection . tearDown (); } private XMultiServiceFactory m_xMSF = null ; private XComponentContext m_xContext = null ; private XTextDocument m_xDoc = null ; @Before public void before () throws Exception { m_xMSF = UnoRuntime . queryInterface ( XMultiServiceFactory . class , connection . getComponentContext (). getServiceManager ()); m_xContext = connection . getComponentContext (); assertNotNull ( "could not get component context." , m_xContext ); m_xDoc = util . WriterTools . createTextDoc ( m_xMSF ); } ... }

in Python the equivalent is simply:

class SomeTest ( unittest . TestCase ): _uno = None @classmethod def setUpClass ( cls ): cls . _uno = UnoInProcess () cls . _uno . setUp () self . m_xDoc = cls . _uno . openEmptyWriterDoc () @classmethod def tearDownClass ( cls ): cls . _uno . tearDown ()

Pythonic syntax simplifcations

Since commit af8143bc it is possible to use native Python language features to write code that looks more natural. For example in most cases, there is no need to write:

xSomething = xNamedAccess . getElementByName ( "foo" ) xSomethingElse = xIndexAccess . getElementByIndex ( 2 )

because a simple:

xSomething = xNamedAccess [ "foo" ] xSomethingElse = xIndexAccess [ 2 ]

does just as well. More lots of additional examples can be found in the commit message of af8143bc.

handle Sequence<PropertyValue>

Currently the python implementation only handles Sequence<any> and not Sequence<PropertyValue>, therfore a rather clumpsy workaround need to used: