Saturday, September 13, 2008

SWTBot for dialogs

I started writing JUnit test cases to test the GUI of Matrex using SWTBot.

Since I did not have any experience with GUI testing, I wanted to start with something simple.
The simplest GUI items to test in Matrex are the dialogs, since they do something specific and limited.
So I started writing test cases to test dialogs.

It turned out that testing dialogs is not so easy as it seems (and this is a general problem, not limited to SWTBot).
With SWTBot you simulate the actions of your application's user: press buttons, write in text boxes, select from list and check boxes...
But you cannot simply open a dialog and simulate the user's actions: dialogs are modal, which means that the function that opens a dialog cannot continue until it is closed.
So, simulating the actions before the dialog is open does not work because there is no dialog; simulating the actions after the dialog is closed does not work because there is no dialog.

There are two practical solutions to this problem:
  1. make the dialog not modal
  2. open the dialog in the main thread and simulate the user's action in a secondary thread.
The first option is not possible in my case. The dialogs are opened by factory methods and I cannot change the code in these methods only to test them.
Moreover, if a second dialog gets opened by the first dialog we have the same problem again.

So I used threads.
I wrote a class for this purpose, SWTBotDialogTestThread. An example of use of this class is in
the test case InputDialogHandlerTest.

The right way to use a thread that acts on the SWT GUI is to call the function Display.asyncExec on the dialog's display, otherwise the GUI becomes unstable.
This is what I do in SWTBotDialogTestThread.workWithDialog.

Also if the actions on the dialog result in an exception, the exception must be caught by the test case in the main thread. I solved this problem running the actions on the dialog in a FutureTask instance (wrapped in the ITestAndWait interface). When the main thread calls the FutureTask.get method (ITestAndWait.waitForWorkDone) this throws the exception that was raised by the thread started by asyncexec, if something went wrong.

In synthesis, when I write a JUnit test case to test a dialog I:
  • create an instance of SWTBotDialogTestThread, which opens the shell used as starting point for the dialog.
  • call SWTBotDialogTestThread.workWithDialog, which starts the thread with the actions to do on the dialog. The thread waits until the dialog is available.
  • open the dialog. At this point the actions are done on the dialog. One of the actions (click on the Ok button) closes the dialog.
  • Call ITestAndWait.waitForWorkDone that re-throws the exception thrown by the other thread, if that thread thrown exceptions.
  • check the result of the dialog.
  • call SWTBotDialogTestThread.close to close the shell opened at the start.
I don't know if this is the best way to solve the problem, but it works, even if several test cases are run together.