3.1+52.5
Implementierung eigener Checks mit dem Checker Interface

'Checks' gehören zu QF-Test's nützlichsten Features. Ohne die Fähigkeit, die Ergebnisse von simulierten Aktionen zu verifizieren, wäre Testautomatisierung weitgehend nutzlos. Allerdings beschränkt sich das Repertoire von Checks in QF-Test natürlich auf die gängigen Attribute der standard Komponenten. Für besondere, selten genutzte Attribute oder für eigene Komponenten können Sie auf 'SUT-Skript' Knoten ausweichen, dort den benötigten Wert auslesen und über die Methode rc.checkEqual() mit dem erwarteten Wert vergleichen. So ein 'SUT-Skript' Knoten funktioniert schnell und zuverlässig und lässt sich in einer 'Prozedur' modularisieren. Er hat aber zwei Nachteile: Er lässt sich nicht aufzeichnen und er ist abschreckend für Nicht-Entwickler.

Mit Hilfe des in diesem Abschnitt beschriebenen API ist es möglich, die standard Checks von QF-Test zu erweitern. QF-Test's eigene Checks basieren sogar selbst darauf. Durch Implementieren und Registrieren eines Checkers für eine Klasse von GUI Elementen können Sie Ihre eigenen Checks erstellen, die genauso aufgenommen und wiedergegeben werden können wie die standard Checks.

Um dies so einfach wie möglich zu gestalten kümmert sich QF-Test selbst um alle Details, von der Darstellung im Check-Popupmenü, Abholen der Checkdaten, Aufnahme des entsprechenden 'Check' Knotens um die Daten zu speichern, Schicken der Daten an das SUT zur Wiedergabe, Abholen der dann gültigen Checkdaten bis hin zum Vergleichen der beiden Werte und Darstellung der Ergebnisse im Protokoll. Alles was Sie dazu beitragen müssen, ist QF-Test mitzuteilen, welche Checks Ihr Checker implementiert und für diese auf Anfrage die Checkdaten zu ermitteln.

Illustrative Beispiele finden Sie am Ende des Kapitels sowie in der Testsuite carconfigSwing_de.qft im Verzeichnis demo/carconfigSwing Ihrer QF-Test Installation.

Das Checker Interface

Das Interface de.qfs.apps.qftest.extensions.checks.Checker muss implementiert werden, um eigene Checks für Ihre Anwendung bereitzustellen. Die damit verbundenen Hilfsklassen und Interfaces werden in den folgenden Abschnitten beschrieben.

 
 
CheckData getCheckData(Object element, Object item, CheckType type)
Liefert die Checkdaten für den aktuellen Zustand eines GUI Elements oder Unterelements.
Parameter
element Das GUI Element, für das die Checkdaten ermittelt werden sollen.
item Ein optionales Unterelement, das geprüft werden soll. Sein Typ hängt von der Art des GUI Elements und der dafür registrierten ItemResolver ab, wie in Abschnitt 52.4.5 beschrieben.
type Die Art des auszuführenden Checks.
Rückgabewert Die Checkdaten für den aktuellen Zustand des GUI Elements selbst, falls item null ist, oder des Unterelements. Der gewünschte Checktyp wird über den Parameter type definiert, der normalerweise einer der vorher von getSupportedCheckTypes zurückgelieferten Checktypen sein sollte. Falls Sie den gewünschten Check für das angegebene Ziel nicht ausführen können, liefern Sie null.
 
Pair getCheckDataAndItem(Object element, Object item, CheckType type)
Liefert die Checkdaten für den aktuellen Zustand eines GUI Elements oder Unterelements und zusätzlich das Unterelement, auf das sich der Check bezieht. Diese Methode wird bei der Aufnahme aufgerufen, wo QF-Test nicht weiß, ob sich die aufgenommenen Daten nun auf das GUI Element selbst oder ein Unterelement beziehen. Um diese Methode ohne Duplizierung von Code zu implementieren, sollten Sie zum Ermitteln der Daten Ihre eigene getCheckData Methode aufrufen.
Parameter
element Das GUI Element, für das die Checkdaten ermittelt werden sollen.
item Ein optionales Unterelement, das geprüft werden soll. Sein Typ hängt von der Art des GUI Elements und der dafür registrierten ItemResolver ab, wie in Abschnitt 52.4.5 beschrieben.
type Die Art des auszuführenden Checks.
Rückgabewert Ein Pair bestehend aus den Checkdaten für das GUI Element oder Unterelement sowie das Unterelement, auf das sich der Check bezieht. Letzteres kann null sein.
 
CheckType[] getSupportedCheckTypes(Object element, Object item)
Liefert die Arten von Checks, die für ein GUI Element und ein optionales Unterelement unterstützt werden.
Parameter
element Das GUI Element, für das die möglichen Checks ermittelt werden.
item Ein optionales Unterelement, das geprüft werden soll. Sein Typ hängt von der Art des GUI Elements und der dafür registrierten ItemResolver ab, wie in Abschnitt 52.4.5 beschrieben.
Rückgabewert Ein Array mit den CheckType Objekten, die Ihr Checker unterstützt. Das erste Element ist der Standard für die Check-Aufnahme mit einem Linksklick. Falls item null ist, liefern Sie nur die Checks zurück, die sich auf das GUI Element als ganzes beziehen. Ansonsten ist es am besten, alle verfügbaren Checks mit oder ohne Unterelement zu liefern, da der Anwender zwar vielleicht auf ein Unterelement geklickt hat, aber trotzdem einen Check für das ganze GUI Element aufnehmen möchte.
 
 

Die Pair Klasse

Die Klasse de.qfs.lib.util.Pair für den Rückgabewert von getCheckDataAndItem ist eine einfache Hilfsklasse, die praktisch für die Gruppierung von zwei Objekten ist. Sie müssen nur ihren Konstruktor kennen, können aber natürlich auch ihre Werte auslesen:

 
 
Pair Pair(Object first, Object second)
Erstellt ein neues Pair.
Parameter
firstDas erste Objekt. Kann null sein.
secondDas zweite Objekt Kann null sein.
 
Object getFirst()
Liefert das erste Objekt des Pair.
RückgabewertDas erste Objekt.
 
Object getSecond()
Liefert das zweite Objekt des Pair.
RückgabewertDas zweite Objekt.
 
 

Das CheckType Interface und seine Implementierung DefaultCheckType

Ein de.qfs.apps.qftest.extensions.checks.CheckType kapselt die Definition einer spezifischen Art von Check. Er kombiniert einen CheckDataType mit einer Bezeichnung und stellt eine anwenderfreundliche Repräsentation für das Checkmenü bereit. Sofern Sie nicht mehrsprachige Darstellungen im Checkmenü benötigen, sollten Sie dieses Interface nie selbst implementieren, sondern einfach einen de.qfs.apps.qftest.extensions.checks.DefaultCheckType instanziieren:

 
 
DefaultCheckType(String identifier, CheckDataType dataType, String description)
Erzeugt einen neuen DefaultCheckType.
Parameter
identifier Der Bezeichner des Checks. Die standard Checks von QF-Test benutzen hierfür ausschließlich Kleinbuchstaben. Um Konflikte zu vermeiden, beginnen Sie Ihre Bezeichner einfach groß.
dataType Der CheckDataType für Ihre Checkdaten.
description Die Beschreibung des Checks für das Checkmenü.
 
 

Der Vollständigkeit halber führen wir auch die Methoden des CheckType Interfaces auf:

 
 
CheckDataType getDataType()
Liefert den CheckDataType für den Check.
Rückgabewert Der Datentyp des Checks.
 
String getDescription()
Liefert die lokalisierte Beschreibung für diesen Checktyp im Checkmenü.
Rückgabewert Die Beschreibung für den Checktyp.
 
String getIdentifier()
Liefert den Bezeichner des Checktyps.
Rückgabewert Der Bezeichner des Checktyps.
 
 

Die Klasse CheckDataType

Die Klasse de.qfs.apps.qftest.extensions.checks.CheckDataType ist vergleichbar zu Enum. Sie definiert einige Konstanten von CheckDataType Instanzen, die dazu dienen, die Art der Daten zu definieren, auf denen ein Check beruht. Jede Konstante entspricht dabei einem der verfügbaren 'Check'-Knoten von QF-Test.

Außer seiner Funktion als Identifikator hat ein CheckDataType keine public Attribute oder Methoden und Sie können keine neuen CheckDataType Objekte erstellen. Wenn Sie einen Check implementieren wollen, der nicht zu den verfügbaren Datentypen passt, müssen Sie Ihre Daten entsprechend konvertieren, z.B. in einen String. Folgende CheckDataType Konstanten sind definiert:

STRING
Ein einzelner String. Entspricht dem 'Check Text' Knoten.
STRING_LIST
Eine Liste von Strings, wie die Zellen einer Tabellenspalte. Entspricht dem 'Check Elemente' Knoten.
SELECTABLE_STRING_LIST
Eine Liste von selektierbaren Strings, wie die Elemente einer Liste. Entspricht dem 'Check selektierbare Elemente' Knoten.
BOOLEAN
Ein Boolean Zustand, entweder true oder false. Entspricht dem 'Check Boolean' Knoten.
GEOMETRY
Ein Satz von 4 Integer Werten für X und Y-Koordinaten, Breite und Höhe. Nicht alle müssen definiert sein. Entspricht dem 'Check Geometrie' Knoten.
IMAGE
Ein Abbild einer ganzen Komponente, eines Unterelements oder einer Region darin. Entspricht dem 'Check Abbild' Knoten.

Die Klasse CheckData und ihre Unterklassen

Die Klasse de.qfs.apps.qftest.shared.data.check.CheckData und ihre Unterklassen, alle aus demselben Package, komplettieren das Checker API. Ein CheckData Objekt kapselt die eigentlichen Daten für einen Check und muss von der Methode Checker.getCheckData() zurückgeliefert werden. Hiermit werden die Daten zwischen QF-Test und dem SUT ausgetauscht. Für jeden CheckDataType gibt es eine zugehörige Unterklasse von CheckData. Sie müssen nur deren Konstruktoren kennen, also führen wir auch nur diese auf:

 
 
BooleanCheckData BooleanCheckData(String identifier, boolean value)
Erzeugt ein neues BooleanCheckData Objekt.
Parameter
identifier Der Bezeichner des Checktyps. Sollte normalerweise dem Bezeichner des type Arguments entsprechen, das an Checker.getCheckData übergeben wurde.
value Der Wert des Checks, ein Boolean Zustand.
 
GeometryCheckData GeometryCheckData(String identifier, int x, int y, int width, int height)
Erzeugt ein neues GeometryCheckData Objekt.
Parameter
identifier Der Bezeichner des Checktyps. Sollte normalerweise dem Bezeichner des type Arguments entsprechen, das an Checker.getCheckData übergeben wurde.
x Die X-Koordinate für den Check.
y Die Y-Koordinate für den Check.
width Die Breite für den Check.
height Die Höhe für den Check.
 
ImageCheckData ImageCheckData(String identifier, ImageRep image, int xOffset, int yOffset, int subX, int subY, int subWidth, int subHeight)
Erzeugt ein neues ImageCheckData Objekt.
Parameter
identifier Der Bezeichner des Checktyps. Sollte normalerweise dem Bezeichner des type Arguments entsprechen, das an Checker.getCheckData übergeben wurde.
image Das Abbild für den Check. Näheres hierzu unter Abschnitt 52.10.
xOffset Ein optionaler X-Offset.
yOffset Ein optionaler Y-Offset.
subX Die X-Koordinate einer optionalen Check-Region.
subY Die Y-Koordinate einer optionalen Check-Region.
subWidth Die Breite einer optionalen Check-Region.
subHeight Die Höhe einer optionalen Check-Region.
 
SelectableItemsCheckData SelectableItemsCheckData(String identifier, Object[][] values)
Erzeugt ein neues SelectableItemsCheckData Objekt.
Parameter
identifier Der Bezeichner des Checktyps. Sollte normalerweise dem Bezeichner des type Arguments entsprechen, das an Checker.getCheckData übergeben wurde.
values Der Wert des Checks, ein Array von Arrays mit einem String und einem Boolean für "regexp" und einem Boolean für "selected".
 
StringCheckData StringCheckData(String identifier, String value)
Erzeugt ein neues StringCheckData Objekt.
Parameter
identifier Der Bezeichner des Checktyps. Sollte normalerweise dem Bezeichner des type Arguments entsprechen, das an Checker.getCheckData übergeben wurde.
value Der Wert des Checks, ein String.
 
StringItemsCheckData StringItemsCheckData(String identifier, String[] values)
Erzeugt ein neues StringItemsCheckData Objekt.
Parameter
identifier Der Bezeichner des Checktyps. Sollte normalerweise dem Bezeichner des type Arguments entsprechen, das an Checker.getCheckData übergeben wurde.
values Der Wert des Checks, ein Array von Strings.
 
 

Darüber hinaus kann für ein ImageCheckData optional ein Algorithmus definiert werden.

 
 
void setAlgorithm(String algorithm)

Definiert einen Algorithmus. Eine genaue Beschreibung finden Sie in Details des Algorithmus zum Bildvergleich.

 
 

Die CheckerRegistry

Wenn Ihr Checker implementiert und instanziiert ist, muss er bei der CheckerRegistry registriert werden. Die Klasse de.qfs.apps.qftest.extensions.checks.CheckerRegistry bietet hierzu folgende Methoden:

 
 
static CheckerRegistry instance()
Es gibt immer nur ein einziges CheckerRegistry Objekt und diese Methode ist der einzige Weg, Zugriff auf diese Singleton Instanz erlangen.
RückgabewertDie CheckerRegistry Singleton Instanz.
 
void registerChecker(Object element, Checker checker)
Registriert einen Checker für ein spezifisches GUI Element. Der Checker beeinträchtigt nicht die Garbage-Collection und wird automatisch entfernt, wenn die Komponente nicht mehr erreichbar ist.
Parameter
element Das GUI Element für das registriert wird.
checker Der zu registrierende Checker.
 
void registerChecker(String clazz, Checker checker)
Registriert einen Checker für eine spezifische Klasse von GUI Elementen.
Parameter
clazz Der Name der Klasse für die registriert wird.
checker Der zu registrierende Checker.
 
void unregisterChecker(Object element, Checker checker)
Entfernt einen Checker für ein spezifisches GUI Element.
Parameter
element Das GUI Element für das entfernt wird.
checker Der zu entfernende Checker.
 
void unregisterChecker(String clazz, Checker checker)
Entfernt einen Checker für eine spezifische Klasse von GUI Elementen.
Parameter
clazz Der Name der Klasse für die entfernt wird.
checker Der zu entfernende Checker.
 
 

Beispiel für einen Checker

Das folgende Jython 'SUT-Skript' zeigt, wie man alles zu einem eigenen Checker zusammenfügt. Nehmen wir an, Sie haben eine Java-Swing-Anwendung und möchten alle Labels in einem Panel gleichzeitig überprüfen. Dazu müssen Sie über alle Komponenten im Panel und deren Kind-Komponenten iterieren, dabei die Label-Komponenten identifizieren und eine Liste mit den enthaltenen Texten generieren. In QF-Test Notation heißt das, Sie brauchen einen CheckDataType.STRING_LIST Checktyp und müssen die Daten in einem StringItemsCheckData Objekt zurückliefern:

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,
    "Alle Labels im 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()
Beispiel 52.44:  Alle Labels in einem Panel checken

Nach Ausführung des Skripts findet man einen neuen Eintrag "Alle Labels im Panel" im Checktyp Menü, sobald man im Checkaufnahmemodus mit der rechten Maustaste auf eine JPanel Komponente klickt (cf. Abschnitt 4.3). Wenn Sie den allLabelsChecker überall in Ihrer Clientanwendung verwenden möchten, können Sie das obige 'SUT-Skript' hinter den 'Warten auf Client' Knoten in der 'Vorbereitung' stellen. Ansonsten können Sie den Checker auch nach Bedarf registrieren und später in einem anderen 'SUT-Skript' wieder entfernen:

from de.qfs.apps.qftest.extensions.checks import CheckerRegistry

global allLabelsChecker

def unregister():
    try:
        CheckerRegistry.instance().unregisterChecker(
            "javax.swing.JPanel", allLabelsChecker)
    except:
        pass

unregister()
Beispiel 52.45:  Den Label Checker entfernen