Author:
Alexandre Iline
Last modified: 21 December 2004
Writing Jelly Tests in NetBeans 3.6 Guide
Related modules:
Jemmy |
Test Tools |
Jemmy Support |
XTest
Table of Contents:
Introduction
This document describes how to write GUI tests for NetBeans IDE using
Jelly library. Jelly contains classes to cover most of the
functionality
necessary to work with NetBeans IDE core GUI components and windows.
See Writing
Operators Guide for instructions how to create classes covering
non-core (i.e. module specific) NetBeans GUI parts.
Jelly is based on Jemmy
library. Jelly provides direct access to Jemmy functionality, so you
can
use Jemmy in your tests directly. (See more info about Jemmy using below). However, it'd be highly recommended to
put as much functionality as possible into module specific operators.
During this document we will create test (WholeTest.java)
which
- creates a Java class - HelloWorld.java under
<netbeans.user>/sampledir directory
- types main(String[]) method into it
- executes it
- checks compilation status and output
Parts of this test used
in tutorial below as examples. Any of these parts is kept in separate
test which can be executed alone. Some of them require HelloWorld to
exist, another create it - this all mentioned in each test comments.
How to execute test
During the tutorial you migh want to execute the tests described above.
Steps to do it:
- Launch NetBeans
- Mount <netbeans home>/sampledir directory
- Mount jars: jemmy.jar, jelly2-nb.jar, junit.jar, junit-ext.jar.
For other tests you might also need jars like core.jar, openide.jar...
- Mount directory containing org/netbeans/jellytools/examples/*.java
- Make sure there is no HelloWorld exists (or does exist,
depending on test comments) in <netbeans home>/sampledir
filesystem.
- Select a test you are about to execute.
- Change its "Executor" property to "Internal Executor"
- Execute it.
How to create test
Test case should extend
JellyTestCase.
Tests in Jelly are supposed to be executed in
XTest harness which gives a
possibility to have test as NbTestCase(TestCase) subclass, however
JellyTestCase is
strongly recommended
as test superlass, because it performs correct Jemmy initialization and
handles test case exceptions.
Example:
package org.netbeans.jellytools.examples;
import org.netbeans.jellytools.JellyTestCase;
import org.netbeans.junit.NbTestSuite;
//does nothing
//it's just an example how test could look.
public class EmptyTest extends JellyTestCase {
//constructor required by JUnit
public EmptyTest(java.lang.String testName) {
super(testName);
}
//method allowing to execute test directly
//from IDE for debugging
public static void main(java.lang.String[] args) {
junit.textui.TestRunner.run(suite());
}
//method required by JUnit
public static junit.framework.Test suite() {
return new NbTestSuite(EmptyTest.class);
}
//method to define test scenario
public void testScenario() {
//test scenario
}
}
This test does not really do anything - working code should be in
testScenario() method. Pay attention, though, to the
main(String[])
method. It's optional, however, it's good idea to have this method - it
allows to debug the test directly from IDE (see
above).
You can use a template
to create a test.
You can also take a look on Jelly
internal tests.
JellyTestCase
JellyTestCase is an extention of NbTestCase intended for Netbeans GUI
testing explicitly.
JellyTestCase initialises Jemmy. Contents of that initialization can
be changed any time. As for now, it initialises output so test output
is
really going to log provided by NbTestCase and set some timeouts
necessary for NetBeans GUI testing.
Any JemmyException (which is normally thrown as a result
of
unsuccessful operation in Jemmy) going from a test is treated by
JellyTestCase as a test failure, any other exception - as a test error.
Using its public switches you can define whether test should
- close all modal dialog at the end of test case (switch jemmy.close.modal
- default true)
- generate component dump (XML file containing components
information) in case of test failure (switch jemmy.screen.xmldump
- default false)
- capture screen into PNG file in case of test failure (switch jemmy.screen.capture
- default true)
- wait at least 1000 ms between test cases (switch jelly.wait.no.event - default
true)
What to use
Jelly classes could be divided on four structural parts:
Operators
All Jelly operators are subclasses of Jemmy operators. Jemmy operators
is a set of classes which are test-side agents for application
components. Operators provide all possible methods simulating user
action with components, methods to find and wait components and
windows.
Also operators map all components methods through the event queue used
for event dispatching.
However, there are some specific operators which are written to
cover specific NetBeans IDE GUI features. Some of these operators are
described in Low level Jelly operators section
of
this tutorial.
Jelly provides all high level operators
necessary for NetBeans core GUI.
All of the operators provide access to their subcomponents by
"getters" methods. These methods are implemented in "lazy
initialization"
technique, so real suboperator instances are not initialized until it's
necessary. All of the suboperators are initialized by verify()
method invocation, so this method using guarantee that all
subcomponents are already loaded.
Getters method names usually has prefix showing component type, the
rest of the names usually repeats component text or a text of
corresponded label: JButtonOperator btCancel(), JRadioButtonOperator
rbSubString(), JTextFieldOperator txtName(). You can see
a full list of prefixes in a table
showed in Writing Operators Guide.
Some of the operators hase invoke() method which causes
component displaying using an action. You can use these actions
directly
- their names are usually look like *ViewAction (RuntimeViewAction
for RuntimeTabOperator).
Actions
Action classes mission is to replace menu and popup calls.
Most of the actions inside NetBeans IDE could be done in some
different modes. org.netbeans.jellytools.actions.Action class
designed to describe all the modes action could be performed:
- by menu
- by popup
- by shortcut
- by direct API call
Not all of the actions can be performed by all modes above, however,
most of the menu/popup operations in Netbeans can also be done by
shortcuts and direct API calls. Keeping that in mind, try to use
propertly created Action instance whenever you want to perform menu or
popup operations. Even if you do care to perform operation exactly by
menu (popup), create an action. The very same code could be used in
different place in another mode.
Using perform() method of any action you will perform
action by the first available possible mode it could be performed.
Sequence of modes to be tried is defined by Action.setDefaultMode(int)
value.
Example (from TestShowFilesystems.java):
//create an action which knows how to show Filesystems
FilesystemsViewAction viewAction = new FilesystemsViewAction();
//show Filesystems by main menu
viewAction.perform();
Example above is performed in default mode. Knowing that explorer can
also be showed by direct API call we can also use
performMenu()
and
performAPI() methods directly.
Example (from TestShowFilesystems.java):
//show Filesystems by main menu
viewAction.performMenu();
//show Filesystems by API call
viewAction.performAPI();
Actions can also be performed for
node, array of
nodes, and component. If action is performed for a node (nodes), node
is
selected first.
Action classes are in org.netbeans.jellytools.actions
package and module specific org.netbeans.jellytools.modules.<modulename>.actions
packages.
Nodes
Node objects are designed to make tree operations easier. Node
incapsulates information about tree it's displayed in and about tree
path. Node classes can be used to perform applicable operations to the
nodes in Explorer window. Nodes can also be passed into
Action.perform*(Node) methods.
There are some useful node types defined in Jelly, such as: ClassNode,
FilesystemNode, FolderNode, HTMLNode,
ProjectRootNode, RepositoryRootNode.
Example (from TestNodes.java):
//create a node for the sampledir directory
FilesystemNode tmpNode = new FilesystemNode(
System.getProperty("netbeans.user") +
File.separator +
"sampledir");
//create a node for the source
JavaNode clNode = new JavaNode(tmpNode, "HelloWorld");
//select it
clNode.select();
//start text editing
clNode.open();
//delete node
clNode.delete();
Nodes classes are in
org.netbeans.jellytools.nodes package and
org.netbeans.jellytools.modules.<modulename>.nodes
packages.
Properties
org.netbeans.jellytools.properties package contains classes to
work with properties. Besides
PropertySheetOperator
it contains classes - inheritors of
org.netbeans.jellytools.properties.Property
class.
Property value can be changed by inline editors (text field for
string values, check box for boolean values - setValue(String) method and
combo box for list values - setValue(int)
method.
Example (from TestProperties.java):
//find "Encoding" property
Property encodingProperty = new Property(propertiesSheet, "Encoding");
//change value directly
encodingProperty.setValue("iso-8859-1");
Any property may or may not have "..." button invoking a property
editor. If property has this button, it can have any number of set*Value(*)
methods using the editor for property editing.
Some property might be not editable directly (other than by property
editor). They extend Property and override setValue(*)
methods
to use property editor. But if property is editable directly, which
most of them are, setValue(*) methods work directly.
Property editors are another class of operators which are located
inside org.netbeans.jellytools.properties.editors package and
org.netbeans.jellytools.modules.<modulename>properties.editors
packages.
Examples of property editors are: ColorCustomEditorOperator,
DimensionCustomEditorOperator, FileCustomEditorOperator,
PointCustomEditorOperator, StringCustomEditorOperator.
Property editor can be invoked by Property.openEditor()
method.
Example (from TestProperties.java):
//find "Encoding" property
Property encodingProperty = new Property(propertiesSheet, "Encoding");
//show property editor
encodingProperty.openEditor();
//create an operator for the property editor
StringCustomEditorOperator encodingEditor = new StringCustomEditorOperator("Encoding");
//set a different value
encodingEditor.setStringValue("iso-8859-2");
//push OK
encodingEditor.ok();
Wizards
There are some operators in Jelly providing functionality covering
NetBeans wizards:
WizardOperator, NewWizardOperator,
ChooseTemplateStepOperator,
TargetLocationStepOperator.
Example (from TestWizard.java):
//invoke wizard on the node, select template and go next
NewWizardOperator wizard = NewWizardOperator.invoke(tmpNode, "Java Classes|Class");
//type name
new NewObjectNameStepOperator().setName("HelloWorld");
//push finish
wizard.finish();
Example (from TestMainWindow.java):
//create an operator for the wizard
NewWizardOperator wizard = new NewWizardOperator();
//select template
new ChooseTemplateStepOperator().selectTemplate("Java Classes|Class");
//push "Next >" button
wizard.next();
//type name
new TargetLocationStepOperator().setName("HelloWorld");
//push "Finish" button
wizard.finish();
Low level Jelly operators
This section contains description of some most important low-levels
jelly operators which are designed to use all NetBeans GUI components
functionality for testing. They also hide some specific details from
user.
NbFrameOperator
NbFrameOperator hides the difference between MDI and SDI mode. It's
common ancestor for all Jelly frames operator except
MainWindowOperator.
A test using frame operators based on this class is able to work in
both modes.
NbDialogOperator
Like NbFrameOperator, NbDialogOperator is common superclass for all
dialog operators in Jelly. It has some common methods pushing most used
buttons on dialog: "OK", "Cancel", "Close", "Help".
TopComponentOperator
TopComponentOperator is an operator for
org.openide.windows.TopComponent component. It provides shortcuts to
docking operations.
High level Jelly operators
MainWindowOperator
MainWindowOperator represents main NetBeans IDE window. The operator
has shorcuts to workspaces tabs and toolbar.
The operator does not have any menu shortcuts because in Jelly menu
operations supposed to be done by actions.
Example (from TestMainWindow.java):
//get a reference to main window
MainWindowOperator mainWindow = MainWindowOperator.getDefault();
//push "New" toolbar button to invoke a wizard
mainWindow.getToolbarButton(mainWindow.getToolbar("System"), "New").push();
OutputWindowOperator
The operator (together with TermOperator) provides access to output
window components. Their methods allow to work with output pages and
text.
Example (from TestOutput.java):
//create an operator for output window
OutputWindowOperator output = OutputWindowOperator.invoke();
//select "HelloWorld" page and get a terminal from it
//wait "Hello, world!" to be displayed
output.getTerm("HelloWorld").waitText("Hello, world!");
//select "Compiler" page
output.selectCompilerPage();
//check that "Finished" text displayed
if(output.getText().indexOf("Finished ") == -1) {
fail();
}
ExplorerOperator
Most of the functionality to work with Explorer window is in nodes, so
the only support necessary from ExplorerOperator is switching pages and
and tree containers searching.
Example (from TestExplorer.java):
//show explorer
ExplorerOperator explorer = ExplorerOperator.invoke();
//select "Runtime" page
explorer.selectPageRuntime();
//select "Repository" page and get an operator from there
RepositoryTabOperator repository = explorer.repositoryTab();
ecause the Explorer is only a logical group of top components, it is
recommended to use operators of such components directly. For example:
// opens Runtime tab
RuntimeTabOperator.invoke();
// finds Filesystems tab and returns its root node instance
Node rootNode = new RepositoryTabOperator().getRootNode();
EditorOperator
EditorOperator has shortcuts for editing operations. It also gives
reference to JEditorPaneOperator for a JEditorPane using for text
editing.
Example (from TestEditor.java):
//find editor window
EditorWindowOperator editorWindow = new EditorWindowOperator("HelloWorld");
//find editor
EditorOperator editor = editorWindow.selectPage("HelloWorld");
//get reference to editing component
JEditorPaneOperator txtOper = editor.txtEditorPane();
//put caret at the beginning of "public class HelloWorld" line
txtOper.setCaretPosition(
txtOper.getPositionByText("public class HelloWorld"));
//move caret 1 line down
editor.setCaretPositionToLine(editor.getLineNumber() + 1);
//push Enter
txtOper.pushKey(KeyEvent.VK_ENTER);
//push up arrow
editor.pushUpArrowKey();
//pust Tab
editor.pushTabKey();
//type first line
txtOper.typeText("public static void main(String[] argv) {\n");
//type second line
txtOper.typeText("System.out.println(\"Hello, world!\");\n");
WholeTest execution.
Let execute the test we finally have -
WholeTest.java.
As was said above, tests are supposed to be executed by XTest
harness, but let's run it from IDE directly. Just perform all the steps
described above.
If everithing went right, at the end you should see that HelloWorld
item removed and "HelloWorld - I/O" page in output window contains
"Hello, world!" text.
Using bundles.
Moving most of functionality into operators makes tests live longer
with no changes in tests itself, because most of the changes in tested
code can be covered by operators changing. However, some of
functionality still could be uncovered.
One of the places where tests are tied to tested code version is
string resources. Again, most of the resources will be used in
operators. But the rest of them should be gotten from NetBeans bundles,
so even if a resource is changed for NetBeans, tests uses right
resource value anyway.
It can be done by Bundle class using. Class Bundle is able to get
resource value by package and resource names.
Example (from I18NTest.java):
//invoke wizard on the node
NewWizardOperator wizard = NewWizardOperator.invoke(tmpNode, "Java Classes|" +
Bundle.getString("org.openide.src.nodes.Bundle", "CTL_Class"));
...
//wait for a status in main window footer
MainWindowOperator.getDefault().waitStatusText(
Bundle.getStringTrimmed("org.netbeans.core.compiler.Bundle", "MSG_CompilationSuccessful"));
Using Bundle class automatically makes possible of I18N application
testing, because localized resource values will be used instead.
Using Jemmy in tests
Classes provided by Jelly have most methods necessary to perform
operations in "usual" way. So, if you do not need to do something
"unusual", methods you need are probably there already.
As was mentioned above Jelly operators extend JemmyOperators, so you
can use them as regular jemmy operators. Additionly, most of the Jelly
operators have shortcuts to get operators for most of their
subcomponents. These operators are, again, either Jemmy operators or
Jelly operators. So there is no need to create any operator yourself.