3.1+54.4
Implementing custom checks with the Checker interface
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.
54.4.1 The Checker interface
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.
|
|||||||||||||||||||||||||||||||||||||||||||||||||
54.4.2 The class Pair
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:
|
|||||||||||||||||||||||||||||||
54.4.3
The CheckType interface and its implementation
DefaultCheckType
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:
|
|||||||||||||||||
For completeness sake, following are the methods of the CheckType
interface:
|
|||||||||||||||||||||||||||
54.4.4 The class CheckDataType
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.
54.4.5 The class CheckData and its subclasses
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:
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Furthermore you can define an optional algorithm for an ImageCheckData.
|
|||||||||
54.4.6 The CheckerRegistry
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:
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
54.4.7 Custom checker example
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()
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()