'Checks' are one of QF-Test's most useful features. Test automation would be mostly
useless without the ability to verify the results of simulated actions. However, the
default set of 'Checks' available in QF-Test is naturally limited to checking the most
common attributes of standard components. For special attributes or custom components
you can resort to read the value in an 'SUT script' and use the
method rc.checkEqual()
to compare it against the expected value. Such an
'SUT script' is perfectly fine, it performs and integrates well, is flexible and
can be modularized by placing it inside a 'Procedure'. It has two major
disadvantages however: It cannot be recorded and it is daunting for non-programmers.
With the help of the API described in this section the default set of 'Checks' in
QF-Test can be extended. In fact, QF-Test's own new-style checks are implemented exactly this
way. By implementing and registering a Checker
for a given type of GUI
element and possibly item you can create your own checks that can be recorded and
replayed just like the standard ones.
To make this as simple as possible, QF-Test handles everything from showing the check in
the check popup menu, fetching the check data, recording the respective 'Check' node
to store that data, sending the data back to the SUT upon replay, fetching the then
current check data, comparing it to the expected value and reporting success or
mismatch. All that is left for you to do is tell QF-Test which checks your
Checker
implements and for each of these provide the check data on request.
Illustrative examples are provided at the end of the chapter and in the test suite
carconfigSwing_en.qft
, located in the directory demo/carconfigSwing
in
your QF-Test installation.
The interface de.qfs.apps.qftest.extensions.checks.Checker
must be
implemented in order to add custom checks for your application. The associated helper
classes and interfaces are documented in the subsequent sections.
|
| | CheckData getCheckData(Object element, Object item, CheckType type) | |
Parameters | element |
The GUI element for which to get the check data.
| item |
An optional item within the element to check. Its type depends on the GUI element
and the registered ItemResolvers as described in subsection 53.4.5.
| type |
The type of check to perform.
| Returns |
The check data for the current state of the GUI element itself, in case item is
null, or the given item within the element. The kind of check to perform is
specified via the type parameter, which should normally be one of those formerly
returned by getSupportedCheckTypes . If you cannot perform the
requested check for the given type and target, return null.
| | Pair getCheckDataAndItem(Object element, Object item, CheckType type) | |
Parameters | element |
The GUI element for which to get the check data.
| item |
An optional item within the element to check. Its type depends on the GUI element
and the registered ItemResolvers as described in subsection 53.4.5.
| type |
The type of check to perform.
| Returns |
A Pair with the check data for the current state of the GUI element,
and the item this check should be performed on, which may be null.
| | CheckType[] getSupportedCheckTypes(Object element, Object item) | |
Parameters | element |
The GUI element for which to get the available checks.
| item |
An optional item within the element to check. Its type depends on the GUI element
and the registered ItemResolvers as described in subsection 53.4.5.
| Returns |
An array of CheckTypes supported by your checker. The first element
is the default for recording a check with a left-click. If the item is null,
return only those kinds of checks that can be applied to the whole GUI element.
Otherwise it is best to provide all available checks because even though the user
may have right-clicked on an item, he may still want to record a check on the
whole GUI element.
| |
|
|
The class de.qfs.lib.util.Pair
for the return value of
getCheckDataAndItem
is a simple utility class that often comes in handy
for grouping two values. You'll only need its constructor, but of course you can also
read its values:
|
| | Pair Pair(Object first, Object second) | |
Parameters | first | The first object. May be null. | second | The second object. May be null. | |
| Object getFirst() | |
Returns | The first object. | | Object getSecond() | |
Returns | The second object. | |
|
|
A de.qfs.apps.qftest.extensions.checks.CheckType
encapsulates information
for a specific kind of check. It combines a CheckDataType
with an
identifier and provides a user-friendly representation of the check for the check
popup menu. Unless you need to provide multi-lingual representations of the check you
should never implement this interface yourself, but simply instantiate a
de.qfs.apps.qftest.extensions.checks.DefaultCheckType
instead:
|
| | DefaultCheckType(String identifier, CheckDataType dataType, String description) | |
Parameters | identifier |
The identifier for the check. The QF-Test standard checks all use lower case
identifiers. To prevent conflicts, simply start your custom check identifiers with a
capital letter.
| dataType |
The CheckDataType required for your check data.
| description |
The description to show in the check popup menu for this check.
| |
|
|
|
For completeness sake, following are the methods of the CheckType
interface:
|
| | CheckDataType getDataType() | |
Returns |
The data type for the check type.
| | String getDescription() | |
Returns |
The description for the check type.
| | String getIdentifier() | |
Returns |
The identifier for the check type.
| |
|
|
The class de.qfs.apps.qftest.extensions.checks.CheckDataType
is similar
to an Enum
. It defines a number of constant CheckDataType
instances that simply serve to identify the kind of data that a check operates on.
Each constant corresponds to one or more of the available 'Check' nodes of QF-Test.
Besides serving as a constant identifier, a CheckDataType
has no public
attributes or methods and you cannot add any new CheckDataTypes
. If you
want to implement a check of a kind that does not fit the existing data types you'll
need to convert your data so that it does, for example by using a string
representation. The following CheckDataType
constants are defined:
- STRING
-
A single string. Used by the 'Check text' node.
- STRING_LIST
-
A list of string items, like the cells in a table column. Used by the
'Check items' node.
- SELECTABLE_STRING_LIST
-
A list of selectable string items, like the elements of a list. Used by the
'Check selectable items' node.
- BOOLEAN
-
A boolean state, either true of false. Used by the 'Boolean check' node.
- GEOMETRY
-
A set of four integer values for X and Y coordinates, width and height. Not all have
to be defined. Used by the 'Check geometry' node.
- IMAGE
-
An image of a whole component or item or a sub-region thereof. Used by the
'Check image' node.
The class de.qfs.apps.qftest.shared.data.check.CheckData
and its
subclasses, all from the same package, complete the Checker
API. A
CheckData
encapsulates the actual data for a check, must be returned from
Checker.getCheckData()
and is used to exchange this check data between
the SUT and QF-Test. There is one concrete CheckData
subclass corresponding
to each CheckDataType
. You'll only ever need to use their constructors,
so that's what we'll list here. Only two of these classes are publicly available so
far:
|
| | BooleanCheckData BooleanCheckData(String identifier, boolean value) | |
Parameters | identifier |
The identifier for the check type. Should normally match the identifier of the
type argument passed to Checker.getCheckData .
| value |
The actual value for the check, a boolean state.
| |
| GeometryCheckData GeometryCheckData(String identifier, int x, int y, int width, int height) | |
Parameters | identifier |
The identifier for the check type. Should normally match the identifier of the
type argument passed to Checker.getCheckData .
| x |
The x-coordinate for the check.
| y |
The y-coordinate for the check.
| width |
The width for the check.
| height |
The height for the check.
| |
| ImageCheckData ImageCheckData(String identifier, ImageRep image, int xOffset, int yOffset, int subX, int subY, int subWidth, int subHeight) | |
Parameters | identifier |
The identifier for the check type. Should normally match the identifier of the
type argument passed to Checker.getCheckData .
| image |
The image for the check. See section 53.10.
| xOffset |
An optional x-offset.
| yOffset |
An optional y-offset.
| subX |
The X-coordinate of an optional check region.
| subY |
The Y-coordinate of an optional check region.
| subWidth |
The Width of an optional check region.
| subHeight |
The Height of an optional check region.
| |
| SelectableItemsCheckData SelectableItemsCheckData(String identifier, Object[][] values) | |
Parameters | identifier |
The identifier for the check type. Should normally match the identifier of the
type argument passed to Checker.getCheckData .
| values |
The actual value for the check, an array of arrays with a String, a Boolean for
the regexp flag and a Boolean for the selected flag.
| |
| StringCheckData StringCheckData(String identifier, String value) | |
Parameters | identifier |
The identifier for the check type. Should normally match the identifier of the
type argument passed to Checker.getCheckData .
| value |
The actual value for the check, a String.
| |
| StringItemsCheckData StringItemsCheckData(String identifier, String[] values) | |
Parameters | identifier |
The identifier for the check type. Should normally match the identifier of the
type argument passed to Checker.getCheckData .
| values |
The actual value for the check, an array of Strings.
| |
|
|
|
Furthermore you can define an optional algorithm for an ImageCheckData.
|
| | void setAlgorithm(String algorithm) | |
|
|
|
|
Once implemented and instantiated, your Checker
must be registered
with the CheckerRegistry
. The class
de.qfs.apps.qftest.extensions.checks.CheckerRegistry
has the following
interface:
|
| | static CheckerRegistry instance() | |
Returns | The singleton CheckerRegistry instance. | | void registerChecker(Object element, Checker checker) | |
Parameters | element |
The GUI element to register for. The checker does not prevent garbage collection
and will be removed automatically when the element becomes unreachable.
| checker | The Checker to register. | |
| void registerChecker(String clazz, Checker checker) | |
Parameters | clazz | The class of GUI element to register for. | checker | The Checker to register. | |
| void unregisterChecker(Object element, Checker checker) | |
Parameters | element |
The GUI element to unregister for.
| checker | The Checker to unregister. | |
| void unregisterChecker(String clazz, Checker checker) | |
Parameters | clazz | The class of GUI element to unregister for. | checker | The Checker to unregister. | |
|
|
|
The following Jython 'SUT script' illustrates how to put everything together.
Let's say you have a Java Swing application and want to check all labels which
reside in a panel at once. To this end, your custom checker needs to iterate over
all components contained in the panel and its children respectively, identify the
labels and generate a list of all their text contents. In QF-Test notation, this means
you need to create a CheckDataType.STRING_LIST
check type and return
the data in an StringItemsCheckData
object:
| from de.qfs.apps.qftest.extensions import ResolverRegistry
from de.qfs.apps.qftest.extensions.checks import CheckerRegistry, \
Checker, DefaultCheckType, CheckDataType
from de.qfs.apps.qftest.extensions.items import ItemRegistry
from de.qfs.apps.qftest.shared.data.check import StringItemsCheckData
from de.qfs.lib.util import Pair
from java.lang import String
import jarray
componentClass = "javax.swing.JPanel"
allLabelsCheckType = DefaultCheckType("AllLabels",
CheckDataType.STRING_LIST,
"All labels in the panel")
class AllLabelsChecker(Checker):
def __init__(self):
pass
def getSupportedCheckTypes(self, com, item):
return jarray.array([allLabelsCheckType], DefaultCheckType)
def getCheckData(self, com, item, checkType):
if allLabelsCheckType.getIdentifier() == checkType.getIdentifier():
labels = self._findLabels(com)
labels = map(lambda l: l.getText(), labels)
values = jarray.array(labels, String)
return StringItemsCheckData(checkType.getIdentifier(), values)
return None
def getCheckDataAndItem(self, com, item, checkType):
data = self.getCheckData(com, item, checkType)
if data is None:
return None
return Pair(data, None)
def _findLabels(self, com, labels=None):
if labels is None:
labels = []
if ResolverRegistry.instance().isInstance(com, "javax.swing.JLabel"):
labels.append(com)
for c in com.getComponents():
self._findLabels(c, labels)
return labels
def unregister():
try:
CheckerRegistry.instance().unregisterChecker(
componentClass, allLabelsChecker)
except:
pass
def register():
unregister()
global allLabelsChecker
allLabelsChecker = AllLabelsChecker()
CheckerRegistry.instance().registerChecker(
componentClass, allLabelsChecker)
register() |
|
| | Example 53.33: Check all labels in a panel | |
After running that script once, you'll find a new entry "All labels in the panel"
among the entries in the check type menu as soon as you right click on a
JPanel
component while being in recording mode (cf. section 4.3). If you want to use the allLabelsChecker
all
over your client application, you can put the above 'SUT script' behind your
'Wait for client to connect' node in the 'Setup' sequence. Otherwise, you may register the
checker only when it is actually needed as shown above and remove it afterwards by
means of another 'SUT script':
|
from de.qfs.apps.qftest.extensions.checks import CheckerRegistry
global allLabelsChecker
def unregister():
try:
CheckerRegistry.instance().unregisterChecker(
"javax.swing.JPanel", allLabelsChecker)
except:
pass
unregister() |
|
| | Example 53.34: Remove the label checker | |