3.1+52.5
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.

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.

 
 
CheckData getCheckData(Object element, Object item, CheckType type)
Get the check data for the current state of the GUI element or item. This method is used during replay.
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 52.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)
Get the check data for the current state of the GUI element or item and also the item that the check actually applies to. This method is used during recording, where QF-Test not only needs to know, which data to record, but also whether to record the check for the GUI element as a whole or for an item. To implement this method without duplicating any code, call your own getCheckData method to retrieve the check data.
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 52.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)
Get the types of checks supported for the given GUI element and optional 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 52.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 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:

 
 
Pair Pair(Object first, Object second)
Create a new Pair.
Parameters
firstThe first object. May be null.
secondThe second object. May be null.
 
Object getFirst()
Get the first object of the Pair.
ReturnsThe first object.
 
Object getSecond()
Get the second object of the Pair.
ReturnsThe second object.
 
 

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:

 
 
DefaultCheckType(String identifier, CheckDataType dataType, String description)
Create a new DefaultCheckType.
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()
Get the CheckDataType for the check type.
Returns The data type for the check type.
 
String getDescription()
Get the localized description to show for this check type in the check popup menu.
Returns The description for the check type.
 
String getIdentifier()
Get the identifier for the check type.
Returns The identifier for the check type.
 
 

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.

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:

 
 
BooleanCheckData BooleanCheckData(String identifier, boolean value)
Create a new BooleanCheckData.
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)
Create a new GeometryCheckData.
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)
Create a new ImageCheckData.
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 52.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)
Create a new SelectableItemsCheckData.
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)
Create a new StringCheckData.
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)
Create a new StringItemsCheckData.
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)

Sets an algorithm. A detailed description can be found in Details about the algorithm for image comparison.

 
 

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:

 
 
static CheckerRegistry instance()
There can only ever be one CheckerRegistry object and this is the method to get hold of this singleton instance.
ReturnsThe singleton CheckerRegistry instance.
 
void registerChecker(Object element, Checker checker)
Register a Checker for an individual GUI element.
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.
checkerThe Checker to register.
 
void registerChecker(String clazz, Checker checker)
Register a Checker for a class of GUI elements.
Parameters
clazzThe class of GUI element to register for.
checkerThe Checker to register.
 
void unregisterChecker(Object element, Checker checker)
Unregister a Checker for an individual GUI element.
Parameters
element The GUI element to unregister for.
checkerThe Checker to unregister.
 
void unregisterChecker(String clazz, Checker checker)
Unregister a Checker for a class of GUI elements.
Parameters
clazzThe class of GUI element to unregister for.
checkerThe Checker to unregister.
 
 

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()
Example 52.44:  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 52.45:  Remove the label checker