Revision History | |||
---|---|---|---|
18 April 2014 | Modified the SWTBot test so that it can be reused also in a test suite (see the comments to this post). |
I happened to give a lecture at the University of Florence on Test Driven Development; besides the standard Junit tests I wanted to show the students also some functional tests with SWTBot. However, I did not want to introduce Eclipse views or dialogs, I just wanted to test a plain SWT application with SWTBot.
In the beginning, it took me some time to understand how to do that (I had always used SWTBot in the context of an Eclipse application); thanks to Mickael Istria, who assisted me via Skype, it ended up being rather easy.
You can find this example here: https://github.com/LorenzoBettini/junit-swtbot-example.
The SWT application is a simple dialog that computes the factorial of the given input (nothing fancy, its code can be seen here).
If we now want to test this SWT application with SWTBot, we can write an abstract base class that we use for our tests (see also the online code)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 |
package mathutils.ui.tests; import java.util.concurrent.BrokenBarrierException; import java.util.concurrent.CyclicBarrier; import mathutils.ui.MathUtilsWindow; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Shell; import org.eclipse.swtbot.swt.finder.SWTBot; import org.eclipse.swtbot.swt.finder.SWTBotTestCase; import org.junit.After; import org.junit.Before; import org.junit.BeforeClass; public abstract class AbstractMathUtilsWindowTest extends SWTBotTestCase { protected SWTBot bot; protected static Thread uiThread; protected static Shell shell; private final static CyclicBarrier swtBarrier = new CyclicBarrier(2); @BeforeClass public static void setupApp() { if (uiThread == null) { uiThread = new Thread(new Runnable() { @Override public void run() { try { while (true) { // open and layout the shell MathUtilsWindow window = new MathUtilsWindow(); window.open(); shell = window.getShell(); // wait for the test setup swtBarrier.await(); // run the event loop window.eventLoop(Display.getDefault()); } } catch (Exception e) { e.printStackTrace(); } } }); uiThread.setDaemon(true); uiThread.start(); } } @Before public final void setupSWTBot() throws InterruptedException, BrokenBarrierException { // synchronize with the thread opening the shell swtBarrier.await(); bot = new SWTBot(shell); } @After public void closeShell() throws InterruptedException { // close the shell Display.getDefault().syncExec(new Runnable() { public void run() { shell.close(); } }); } protected void assertResultGivenInput(String input, String expectedResult) { bot.textWithLabel("input").setText(input); bot.button("Compute").click(); assertEquals(expectedResult, bot.textWithLabel("result").getText()); } } |
And we use this base class in our tests, for instance
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
package mathutils.ui.tests; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.BlockJUnit4ClassRunner; @RunWith(BlockJUnit4ClassRunner.class) public class MathUtilsNonValidInputWindowTest extends AbstractMathUtilsWindowTest { @Test public void testEmptyInput() { assertResultGivenInput(" ", "Empty input"); } @Test public void testNonValidIntegerInput() { assertResultGivenInput("foo", "Not a valid input"); } @Test public void testNonValidInput() { assertResultGivenInput("-1", "Not a valid input"); } } |
There are a few things to note in the abstract base class:
- You need to spawn the application in a new thread (the bot will run in a different thread)
- You must start the application before creating the bot (otherwise the Display will be null)
- after that you can simply use SWTBot API as you’re used to.
Note that the thread will create our window and then it will enter the event loop; this thread synchronizes with the @Before method (executed before each test), which creates the SWTBot (using the shell created by the thread). The @After method (executed after each test), will close our window, so that each test is independent from each other. The thread executes in an infinite loop, thus as soon as the shell is closed it will create a new one, etc.
Of course, this must be executed as a “Junit test”, NOT as a “Plug-in Junit test”, neither as a “SWTBot Test”, since we do not want any Eclipse application while running the test:
In the sources of the example you can find also the files to run the tests headlessly with Buckminster or with Maven/Tycho. Just enter the directory mathutils.build and
1 |
ant -f build.ant |
for Buckminster or
1 |
mvn clean verify |
for Maven.
For Buckminster, you just need to save the launch configuration you used to run the test, and use the junit command:
1 |
junit -l "mathutils.ui.tests/mathutils.ui.tests.launch" --flatXML -o "${buckminster.output.root}/test.results/mathutils.ui.tests.launch.result.xml" |
For Tycho, you must specify <packaging>eclipse-test-plugin</packaging>, but without further configuration. This will default useUIHarness to false.
During the headless run, first the Junit tests for the implementation of the factorial will be executed (these are not interesting in the context of SWTBot) and then the SWTBot tests will be executed.
Thank you for this. Great example.
Have you tried running multiple tests using swtbot from a test suite?
I always run my SWTBot tests from a test suite, without problems. Are you experiencing problems with test suites?
I’m running the test suite from Eclipse Juno and I’ve had issues during the tear down phase. When I attempt to close the application I am testing with SWTBot I receive the following exception: org.eclipse.swt.SWTException: Device is disposed. I was wondering if you had any insight into whether this was an issue with Eclipse, SWTBot, or something that I should look deeper into in my own project?
Can you share an example? Or you could try to post in the SWTBot forum with a reproducible example.
Thanks for the example, just what I needed to get going!
Works great! (Eclipse Kepler with OpenJDK6)
Thanks.
I tried simply copying the MathUtilsWindowTest class -> MathUtilsWindow2ndTest.
(as it would be pleasant to have several classes with tests in them, of course).
When I run both these classes (from Eclipse), the tests pass; However, in the console output, I get:
Exception in thread “Thread-2” org.eclipse.swt.SWTException: Invalid thread access
at org.eclipse.swt.SWT.error(SWT.java:4397)
at org.eclipse.swt.SWT.error(SWT.java:4312)
at org.eclipse.swt.SWT.error(SWT.java:4283)
at org.eclipse.swt.widgets.Widget.error(Widget.java:481)
at org.eclipse.swt.widgets.Shell.(Shell.java:263)
at org.eclipse.swt.widgets.Shell.(Shell.java:254)
at org.eclipse.swt.widgets.Shell.(Shell.java:205)
at org.eclipse.swt.widgets.Shell.(Shell.java:141)
at mathutils.ui.MathUtilsWindow.createContents(MathUtilsWindow.java:57)
at mathutils.ui.MathUtilsWindow.open(MathUtilsWindow.java:43)
at mathutils.ui.tests.MathUtilsWindowTest$1.run(MathUtilsWindowTest.java:30)
at java.lang.Thread.run(Thread.java:701)
Did anyone else see a similar behavior ?
Do you get the exception when the second test starts? It might be the case to close the current application in an @AfterClass method…
Yes, the exception is thrown from the second test’s run().
I tried a few ideas, but so far didn’t succeed in getting rid of the exception.
The problem seems to be, that SWT has registered a lock by the first thread, and the lock isn’t yet released, when the second thread starts ?
I tried things like, in @AfterClass, to stop() the first thread (yes, I know, that’s deprecated – I just did it for the experiment); Also tried to dispose() the shell from the first instance – but got another “Invalid thread access” exception, probably for the same reason as the original one.
I will still try a few other ideas (assuming I get any 🙂 )
I’m experiencing this problem too, when I tried to implement to tests (after refactoring the main test class in an abstract base class); I’m investigating this problem… probably we should not use static fields and methods and we should try to use @Before and @After methods… I’ll post updates if I find a solution.
Ejner, I’ve updated the post and the source code; now you can run several tests 🙂
That’s really cool stuff. Now I am impressed!
Hi,
Do you have any example of SWT RCP based application which can be executed via commandline ?.
I am struggling to get it work.
Please refer http://stackoverflow.com/questions/23490327/swtbot-commandline-headless-failed-to-invoke-application .
Not yet: I never had to run SwtBot tests from the command line since I run them using headless build technologies… if I have some spare time I’ll try to dig into that and let you know 🙂
Thanks for your instructions. I have an SWT application and successfully launch it, wrap its shell with SWTBot, and issue actions. However, I can’t figure out how to use the SWT Recorder to generate action code. Can you give some advice? Does the recorder work for RPC apps only? Do I need to convert my SWT app into an Eclipse RPC app?
I think the recorder requires RCP.. Or maybe it’s just a matter of adding the recorder bundles to your application.. You may want to ask in the forum..
Hi!
On MacOSX / Cocoa, there seems to be a restriction, in that the SWT GUI has to run in the main application thread. This leads to exceptions being thrown, if we run the code there.
Likely the code can be rewritten, to let the GUI run in the main thread, while all SWTBot and other code is pushed to other thread(s) – if MacOS has to be supported, of course.
Hi Ejner
I don’t have any Mac to try it on… if you have one and can fix this problem so that it works on Mac as well, please submit a pull request to the github repository. 🙂
Please keep me posted!
Hi Lorenzo
I have – will try fixing it over the weekend and let you know.
Hi Lorenzo,
Thanks for the detailed example. I tried to get the tests running on OS X and it seems I run into the mentioned Cocoa restriction. I cloned from GitHub and die mvn clean verify in mathutils.build. See a part of the output below. I just wanted to let you know. So far I have not found an elegant solution.
Cheers,
Bernhard
——————————————————-
T E S T S
——————————————————-
Running mathutils.ui.tests.MathUtilsNonValidInputWindowTest
***WARNING: Display must be created on main thread due to Cocoa restrictions.
org.eclipse.swt.SWTException: Invalid thread access
at org.eclipse.swt.SWT.error(SWT.java:4441)
at org.eclipse.swt.SWT.error(SWT.java:4356)
at org.eclipse.swt.SWT.error(SWT.java:4327)
at org.eclipse.swt.widgets.Display.error(Display.java:1094)
at org.eclipse.swt.widgets.Display.createDisplay(Display.java:846)
at org.eclipse.swt.widgets.Display.create(Display.java:829)
at org.eclipse.swt.graphics.Device.(Device.java:130)
at org.eclipse.swt.widgets.Display.(Display.java:720)
at org.eclipse.swt.widgets.Display.(Display.java:711)
at org.eclipse.swt.widgets.Display.getDefault(Display.java:1414)
at mathutils.ui.MathUtilsWindow.open(MathUtilsWindow.java:39)
at mathutils.ui.tests.AbstractMathUtilsWindowTest$1.run(AbstractMathUtilsWindowTest.java:35)
at java.lang.Thread.run(Thread.java:745)
Tests run: 3, Failures: 0, Errors: 3, Skipped: 0, Time elapsed: 1.134 sec <<< FAILURE! – in mathutils.ui.tests.MathUtilsNonValidInputWindowTest
testEmptyInput(mathutils.ui.tests.MathUtilsNonValidInputWindowTest) Time elapsed: 0.002 sec <<< ERROR!
java.lang.IllegalStateException: Could not find a display
at org.eclipse.swtbot.swt.finder.utils.SWTUtils.display(SWTUtils.java:250)
at org.eclipse.swtbot.swt.finder.finders.ControlFinder.(ControlFinder.java:83)
at org.eclipse.swtbot.swt.finder.finders.ControlFinder.(ControlFinder.java:73)
at org.eclipse.swtbot.swt.finder.SWTBot.(SWTBot.java:115)
at org.eclipse.swtbot.swt.finder.SWTBotTestCase.(SWTBotTestCase.java:41)
at mathutils.ui.tests.AbstractMathUtilsWindowTest.(AbstractMathUtilsWindowTest.java:16)
at mathutils.ui.tests.MathUtilsNonValidInputWindowTest.(MathUtilsNonValidInputWindowTest.java:8)
testNonValidInput(mathutils.ui.tests.MathUtilsNonValidInputWindowTest) Time elapsed: 0 sec <<< ERROR!
java.lang.IllegalStateException: Could not find a display
at org.eclipse.swtbot.swt.finder.utils.SWTUtils.display(SWTUtils.java:250)
at org.eclipse.swtbot.swt.finder.finders.ControlFinder.(ControlFinder.java:83)
at org.eclipse.swtbot.swt.finder.finders.ControlFinder.(ControlFinder.java:73)
at org.eclipse.swtbot.swt.finder.SWTBot.(SWTBot.java:115)
at org.eclipse.swtbot.swt.finder.SWTBotTestCase.(SWTBotTestCase.java:41)
at mathutils.ui.tests.AbstractMathUtilsWindowTest.(AbstractMathUtilsWindowTest.java:16)
at mathutils.ui.tests.MathUtilsNonValidInputWindowTest.(MathUtilsNonValidInputWindowTest.java:8)
testNonValidIntegerInput(mathutils.ui.tests.MathUtilsNonValidInputWindowTest) Time elapsed: 0 sec <<< ERROR!
java.lang.IllegalStateException: Could not find a display
at org.eclipse.swtbot.swt.finder.utils.SWTUtils.display(SWTUtils.java:250)
at org.eclipse.swtbot.swt.finder.finders.ControlFinder.(ControlFinder.java:83)
at org.eclipse.swtbot.swt.finder.finders.ControlFinder.(ControlFinder.java:73)
at org.eclipse.swtbot.swt.finder.SWTBot.(SWTBot.java:115)
at org.eclipse.swtbot.swt.finder.SWTBotTestCase.(SWTBotTestCase.java:41)
at mathutils.ui.tests.AbstractMathUtilsWindowTest.(AbstractMathUtilsWindowTest.java:16)
at mathutils.ui.tests.MathUtilsNonValidInputWindowTest.(MathUtilsNonValidInputWindowTest.java:8)
Running mathutils.ui.tests.MathUtilsValidWindowTest
Tests run: 2, Failures: 0, Errors: 2, Skipped: 0, Time elapsed: 0.007 sec <<< FAILURE! – in mathutils.ui.tests.MathUtilsValidWindowTest
testBaseCase(mathutils.ui.tests.MathUtilsValidWindowTest) Time elapsed: 0 sec <<< ERROR!
java.lang.IllegalStateException: Could not find a display
at org.eclipse.swtbot.swt.finder.utils.SWTUtils.display(SWTUtils.java:250)
at org.eclipse.swtbot.swt.finder.finders.ControlFinder.(ControlFinder.java:83)
at org.eclipse.swtbot.swt.finder.finders.ControlFinder.(ControlFinder.java:73)
at org.eclipse.swtbot.swt.finder.SWTBot.(SWTBot.java:115)
at org.eclipse.swtbot.swt.finder.SWTBotTestCase.(SWTBotTestCase.java:41)
at mathutils.ui.tests.AbstractMathUtilsWindowTest.(AbstractMathUtilsWindowTest.java:16)
at mathutils.ui.tests.MathUtilsValidWindowTest.(MathUtilsValidWindowTest.java:8)
testValidInput(mathutils.ui.tests.MathUtilsValidWindowTest) Time elapsed: 0.003 sec <<< ERROR!
java.lang.IllegalStateException: Could not find a display
at org.eclipse.swtbot.swt.finder.utils.SWTUtils.display(SWTUtils.java:250)
at org.eclipse.swtbot.swt.finder.finders.ControlFinder.(ControlFinder.java:83)
at org.eclipse.swtbot.swt.finder.finders.ControlFinder.(ControlFinder.java:73)
at org.eclipse.swtbot.swt.finder.SWTBot.(SWTBot.java:115)
at org.eclipse.swtbot.swt.finder.SWTBotTestCase.(SWTBotTestCase.java:41)
at mathutils.ui.tests.AbstractMathUtilsWindowTest.(AbstractMathUtilsWindowTest.java:16)
at mathutils.ui.tests.MathUtilsValidWindowTest.(MathUtilsValidWindowTest.java:8)
Results :
Tests in error:
MathUtilsNonValidInputWindowTest.:8->AbstractMathUtilsWindowTest.:16->SWTBotTestCase.:41 » IllegalState
MathUtilsNonValidInputWindowTest.:8->AbstractMathUtilsWindowTest.:16->SWTBotTestCase.:41 » IllegalState
MathUtilsNonValidInputWindowTest.:8->AbstractMathUtilsWindowTest.:16->SWTBotTestCase.:41 » IllegalState
MathUtilsValidWindowTest.:8->AbstractMathUtilsWindowTest.:16->SWTBotTestCase.:41 » IllegalState
MathUtilsValidWindowTest.:8->AbstractMathUtilsWindowTest.:16->SWTBotTestCase.:41 » IllegalState
Tests run: 5, Failures: 0, Errors: 5, Skipped: 0
[INFO] ————————————————————————
[INFO] Reactor Summary:
[INFO]
[INFO] mathutils.build ……………………………… SUCCESS [ 22.842 s]
[INFO] mathutils.core ………………………………. SUCCESS [ 1.512 s]
[INFO] mathutils.core.tests …………………………. SUCCESS [ 2.968 s]
[INFO] mathutils.ui ………………………………… SUCCESS [ 0.172 s]
[INFO] mathutils.ui.tests …………………………… FAILURE [ 4.349 s]
[INFO] ————————————————————————
[INFO] BUILD FAILURE
[INFO] ————————————————————————
Hi Bernhard
as I said in previous answer I don’t have a Mac so I can’t try it.
Can you run the ui.tests from Eclipse?
As for the Maven/Tycho build, I think I should have added JVM flags to be enabled when run on Mac in the mathutils.ui.tests/pom.xml; something like the profile you find here: https://github.com/LorenzoBettini/xtext-maven-example/blob/master/language/2-full/org.xtext.builddsl.tests/pom.xml#L30-L42
Could you please try in your fork?
thanks
Lorenzo
Thank you for the ultrafast response! I added the JVM flags (see my fork). However, the results are the same when running mvn clean verify from the command line.
I tried importing the projects into Eclipse Luna as Maven projects. (It installed some Maven plugin connectors for tyco). I did Maven > Update Project. However, I am stuck with the following build problem in mathutils.ui.tests:
Description Resource Path Location Type
Bundle ‘org.eclipse.swtbot.go’ cannot be resolved MANIFEST.MF /mathutils.ui.tests/META-INF line 7 Plug-in Problem
The other projects build fine. Is there anything I have to do in addition?
I really appreciate your help.
Cheers,
Bernhard
It is possible to rewrite the tests in a systematic way, ugly but perhaps automatizable. You can see an example in my project https://sourceforge.net/projects/chessshellforpc/ .
In the ‘source_snapshot’ folder, look in ‘test_mac’ dir.
It shows that it’s possible to make it work on Mac / Cocoa by rewriting to take the mentioned restriction there into account.
To make the test code less ugly, I don’t know how – I didn’t have time to try for that.
Thank your for the ultrafast response as well, Ejner! I downloaded your code. I have to run now but will have a look at it later.
Cheers,
Bernhard
The URL seems to be broken…
but could you please try to add the lines I mentioned in the pom.xml?