Hinweis
Resolver haben in QF-Test bereits eine lange Historie.
Seit QF-Test Version 2.0 wird das Testen von SWT basierten
Anwendungen unterstützt. Das ComponentNameResolver Interface der älteren
QF-Test Versionen basierte auf der AWT/Swing Klasse Component und war daher
nicht portabel. Es wurde durch das generische NameResolver Interface und
die entsprechenden Methoden in der ResolverRegistry ersetzt.
Mit QF-Test Version 3.1 wurden NameResolver und FeatureResolver
durch elegantere Versionen ersetzt, die mangels Alternativen NameResolver2
und FeatureResolver2 heißen. Diese moderneren Versionen reichen den
standard Namen oder das standard Feature, welche QF-Test selbst ohne Resolver ermittelt,
direkt an die Methoden der Resolver weiter, was deren Implementierung deutlich
vereinfacht.
Ebenso wurde der alte ItemNameResolver, der nur für Swing verfügbar war,
durch den neuen ItemNameResolver2 plus zugehörigem
ItemValueResolver2 ersetzt, welche mit allen GUI Engines funktionieren. Zur
Rückwärtskompatibilität werden die alten Methoden weiterhin unterstützt. Sie sollten
allerdings nicht mehr verwendet werden und sind hier nicht dokumentiert.
Mit diesem Erweiterungs API können Sie Einfluss darauf nehmen, wie QF-Test Komponenten
und Unterelemente erkennt und aufzeichnet. Dies ist ein sehr mächtiges Feature, mit
dem Sie volle Kontrolle über das Komponenten-Management von QF-Test erhalten. Und es ist
wesentlich einfacher als es aussieht. Mit dem resolvers Modul, welches in
Abschnitt 39.1.10 beschrieben wird, können Sie einen kompletten
Resolver mit wenigen Zeilen Code implementieren und installieren.
Folgende Arten von Resolvern können verwendet werden:
-
Ein
ClassNameResolver kann die Klasse bestimmen, die QF-Test für eine
Komponente aufzeichnet. Bei Swing und SWT ist sein Einsatz limitiert, da er nur eine
Basisklasse der tatsächlichen Klasse der Komponente liefern darf. Für Webanwendungen
ist dies dagegen vielleicht der wichtigste Resolver überhaupt, da er DOM Knoten
beliebige Klassennamen zuweisen kann und somit tief verschachtelte Strukturen von
<DIV> Knoten, wie sie bei vielen AJAX Toolkits vorkommen,
in etwas brauchbares verwandelt werden können. Bitte lesen Sie diesbezüglich auch
Abschnitt 6.4 über die pseudo Klassenhierarchie von
Webanwendungen.
-
Ein
NameResolver2 kann den Namen einer Komponente, der normalerweise mit
setName() bei AWT/Swing, setData() bei SWT oder dem "id"
Attribut eines DOM Knotens bei einer Webanwendung gesetzt wird, verändern (oder
überhaupt erst definieren). Dies kann äusserst nützlich sein, wenn der Sourcecode
nicht geändert werden kann, sei es weil fremder Code eingesetzt wird oder weil auf
Bestandteile von komplexen Komponenten kein Zugriff besteht. Ein Beispiel für
letzteren Fall ist der JFileChooser Dialog von Swing. Für diesen bringt
QF-Test einen eigenen NameResolver mit, über den Sie im Kapitel "Die
Standardbibliothek" des Tutorials weitere Informationen finden.
-
Web
Ein
IdResolver das "id" Attribut eines DOM Knotens zu einem frühen
Zeitpunkt modifizieren oder unterdrücken, was ihn sehr effizient macht.
-
Ein
FeatureResolver2 kann das Merkmal einer Komponente
festlegen.
-
Ein
ExtraFeatureResolver kann weitere Merkmale einer Komponente
festlegen.
-
Ein
ItemNameResolver2 kann die Textdarstellung des
Index zur Adressierung eines Unterelements einer komplexen Komponente verändern
(oder überhaupt erst definieren).
-
Ein
ItemValueResolver2 kann die Textdarstellung des
Wertes eines Unterelements einer komplexen Komponente verändern
(oder überhaupt erst definieren), der für einen 'Check Text' oder
'Text auslesen' Knoten verwendet wird.
-
Ein
TreeTableResolver hilft QF-Test TreeTable Komponenten als solche zu
erkennen. Eine TreeTable ist eine Mischung aus einer Tabelle und einem Baum. Sie ist
keine standard Swing Komponente, allerdings werden die meisten TreeTables ähnlich
implementiert, indem ein Baum als Renderer für eine Spalte der Tabelle verwendet wird.
Wenn QF-Test eine TreeTable identifiziert, behandelt es die Zeilenindizes aller Zellen
der Tabelle wie Baumindizes, was in diesem Zusammenhang wesentlich bessere Ergebnisse
liefert. Außerdem werden Geometrie Informationen für Zellen in der Spalte des Baums
basierend auf Baumknoten statt auf Tabellenzellen ermittelt.
Es können verschiedene Resolver mit unterschiedlichen Aufgaben
erstellt und zur Laufzeit registriert werden. Die jeweiligen
Interfaces können direkt durch Java Klassen implementiert werden.
Diese Methode würde allerdings dazu führen, dass ein Teil Ihrer
Applikation von den QF-Test Klassen abhängig wird. Daher ist es
vorzuziehen, die Resolver Interfaces in Jython oder Groovy zu implementieren, ab
besten über das in Abschnitt 39.1.10 beschriebene
resolvers Modul.
Dadurch kann der gesamte Mechanismus strikt vom SUT getrennt bleiben
und hat keinerlei Einfluss auf dessen Entwicklungsprozess.
Bevor wir darauf eingehen, wie Resolver registriert (und entfernt)
werden, werfen wir zunächst einen Blick auf deren Interfaces.
Das Interface
de.qfs.apps.qftest.extensions.ClassNameResolver
besteht aus einer einzigen Methode:
| |
|
|
| |
String getClassName(Object element, String name) |
| Parameter |
element |
Das GUI Element dessen Klassenname ermittelt werden soll.
|
name |
Der ursprüngliche Klassenname, den QF-Test ohne Resolver verwenden würde.
|
| Rückgabewert |
Der zu verwendende Klassenname oder null, falls der Resolver das Element nicht
behandelt.
|
| |
|
| |
Nachdem QF-Test den Klassennamen einer Komponente ermittelt hat, erhalten die
registrierten ClassNameResolver die Chance, diesen zu überschreiben.
Der erste Resolver, der einen nicht-null Wert zurückliefert, bestimmt
das Ergebnis. Sind keine Resolver registriert oder liefern alle Resolver null, wird
der ursprüngliche Klassenname benutzt.
Bei Swing und SWT Anwendungen werden Klassen anhand der tatsächlichen
Klassenhierarchie überprüft, daher darf der ClassNameResolver nur die
tatsächliche Klasse des Elements, oder eine ihrer Basisklassen zurückliefern. Dies
kann trotzdem nützlich sein, wenn Sie z.B. die Option Nur Systemklassen aufnehmen nicht aktivieren, für bestimmte Komponenten aber dennoch
die Systemklasse oder eine andere generelle Basisklasse aufzeichnen wollen. Swing und
SWT Klassen werden nicht zwischengespeichert, der Resolver wird also jedes mal wieder
aufgerufen, wenn QF-Test den Klassennamen einer Komponente bestimmt.
Für Webanwendungen verwendet QF-Test eine pseudo Klassenhierarchie, die in Abschnitt 6.4 beschrieben ist. Der Resolver kann hier jeden beliebigen
Klassennamen vergeben, den QF-Test dann als eine Unterklasse von der Pseudoklasse des
Elements behandelt. Aus Performanzgründen werden bei Webanwendungen die Klassen
zwischengespeichert, daher wird der Resolver für jeden DOM Knoten höchstens einmal
aufgerufen.
Das Interface
de.qfs.apps.qftest.extensions.NameResolver2
besteht aus einer einzigen Methode:
| |
|
|
| |
String getName(Object element, String name) |
| Parameter |
element |
Das GUI Element dessen Name ermittelt werden soll.
|
name |
Der ursprüngliche Name, den QF-Test ohne Resolver verwenden würde.
|
| Rückgabewert |
Der zu verwendende Name oder null, falls der Resolver das Element nicht behandelt.
Durch Rückgabe des leeren Strings wird der ursprüngliche Name der Komponente
unterdrückt.
|
| |
|
| |
Nachdem QF-Test den Namen einer Komponente ermittelt hat, erhalten die
registrierten NameResolver2 die Chance, diesen zu überschreiben oder zu
unterdrücken. Der erste Resolver, der einen nicht-null Wert
zurückliefert, bestimmt das Ergebnis. Sind keine Resolver
registriert oder liefern alle Resolver null, wird der ursprüngliche Name benutzt.
In einzelnen Fällen kann es sinnvoll sein, den Namen einer
Komponente zu unterdrücken, z.B. wenn er nicht eindeutig ist, oder
- wesentlich schlimmer - wenn er während der Laufzeit variiert. Um
das zu erreichen muss getName den leeren
String zurückliefern.
Das Interface
de.qfs.apps.qftest.extensions.IdResolver
besteht aus einer einzigen Methode:
| |
|
|
| |
String getId(DomNode node, String id) |
| Parameter |
node |
Der DomNode Knoten dessen ID ermittelt werden soll.
|
id |
Das "id" Attribut, das QF-Test für diesen Knoten ermittelt hat, möglicherweise nach
Entfernen der darin enthaltenen Ziffern, abhängig von der Option Alle Ziffern aus 'id' Attributen eliminieren. Um den Resolver auf Basis des ursprünglichen "id"
Attributs zu implementieren, ermitteln Sie dieses einfach via
node.getAttribute("id").
|
| Rückgabewert |
Die ID oder null, falls keine ID festgelegt werden kann.
Durch Rückgabe des leeren Strings wird der eigentliche ID
der Komponente unterdrückt.
|
| |
|
| |
Wenn QF-Test die Knoten einer Webseite registriert, wird das "id" Attribut dieser Knoten
gespeichert. Da vieler Webseiten automatisch generierte IDs verwenden, ist oft eine
Modifikation dieser IDs nötig, z.B das Entfernen von Ziffern, welches QF-Test abhängig
von der Option Alle Ziffern aus 'id' Attributen eliminieren automatisch vornimmt.
Das "id" Attribut eines Knotens kann an verschiedenen Stellen wieder auftauchen, vor
allem im Attribut 'Name' des Knotens (abhängig von der Option
'Id' Attribut als Name verwenden falls "eindeutig genug"), im Attribut 'Merkmal' und im
Attribut 'Weitere Merkmale'. Daher ist es viel effizienter, einmalig
die ID mittels eines IdResolvers zu verändern, als getrennte
Name-, Feature- und ExtraFeatureResolvers zu
implementieren. Noch wichtiger ist der Umstand, dass die Veränderung der ID eines
Knotens großen Einfluss auf die Eindeutigkeit dieser ID haben kann. Der Mechanismus
zum Ermitteln von Namen auf Basis der ID nimmt darauf Rücksicht, so dass ein
IdResolver auch nicht eindeutige IDs liefern darf. Ein
NameResolver2 muss dagegen eindeutige Namen liefern.
Das Interface
de.qfs.apps.qftest.extensions.FeatureResolver2
besteht aus einer einzigen Methode:
| |
|
|
| |
String getFeature(Object element, String feature) |
| Parameter |
element |
Das GUI Element dessen Merkmal ermittelt werden soll.
|
feature |
Das ursprüngliche Merkmal, das QF-Test ohne Resolver verwenden würde.
|
| Rückgabewert |
Das zu verwendende Merkmal oder null, falls der Resolver das Element nicht
behandelt. Durch Rückgabe des leeren Strings wird das ursprüngliche Merkmal der
Komponente unterdrückt.
|
| |
|
| |
Nachdem QF-Test das Merkmal einer Komponente ermittelt hat, erhalten die
registrierten FeatureResolver2 die Chance, dieses zu überschreiben oder zu
unterdrücken. Der erste Resolver, der einen nicht-null Wert
zurückliefert, bestimmt das Ergebnis. Sind keine Resolver
registriert oder liefern alle Resolver null, wird das ursprüngliche Merkmal benutzt.
Um das Merkmal einer Komponente zu unterdrücken, muss
getFeature den leeren String zurückliefern.
Das Interface
de.qfs.apps.qftest.extensions.ExtraFeatureResolver
besteht aus einer einzigen Methode:
| |
|
|
| |
ExtraFeatureSet getExtraFeatures(Object element, ExtraFeatureSet features) |
| Parameter |
element |
Das GUI Element dessen weitere Merkmale ermittelt werden sollen.
|
features |
Ein Satz von weiteren Merkmale, die QF-Test selbst festgelegt hat, ein leerer Satz
falls es keine solchen Merkmale gibt. Dieser Satz kann modifiziert oder ignoriert
und durch einen anderen ersetzt werden.
|
| Rückgabewert |
Der modifizierte original Satz von Merkmalen oder ein neuer Satz, falls der
Resolver das GUI Element behandelt, andernfalls null. Die ursprünglichen Merkmale
können durch Rückgabe eines leeren Satzes unterdrückt werden.
|
| |
|
| |
Nachdem QF-Test die weiteren Merkmale eines GUI Elements ermittelt hat, erhalten die
registrierten ExtraFeatureResolver die Chance, diese zu modifizieren. Im
Gegensatz zu anderen Resolvern bricht QF-Test nicht ab, sobald ein
ExtraFeatureResolver einen nicht-null Wert zurückliefert, sondern
übernimmt diesen Wert als Parameter für den nächsten Resolver. Hierdurch können
mehrere ExtraFeatureResolver registriert werden, die jeweils ein
spezielles Merkmal behandeln. Sind keine Resolver registriert oder liefern alle
Resolver null, macht QF-Test mit dem ursprünglich ermittelten Satz von Merkmalen weiter.
Um die getExtraFeatures Methode implementieren zu können, müssen Sie natürlich das API
der Beteiligten Klassen ExtraFeature und ExtraFeatureSet
kennen. Diese werden im Folgenden Beschrieben. Ein Beispiel zur Implementierung finden
Sie in Beispiel 39.6.
Ein Objekt der Klasse de.qfs.apps.qftest.shared.data.ExtraFeature
repräsentiert ein weiteres Merkmal eines GUI Elements, bestehend aus Name und Wert,
sowie Informationen darüber, ob für das Feature ein Treffer erwartet wird, ob der Wert
ein regulärer Ausdruck ist und ob die Aussage negiert werden soll. Für mögliche Stati
definiert die Klasse die Konstanten STATE_IGNORE, STATE_SHOULD_MATCH und
STATE_MUST_MATCH.
| |
|
|
| |
ExtraFeature (String name, String value) |
| Parameter |
name | Der Name des ExtraFeatures. |
value | Der Wert des ExtraFeatures. |
| |
ExtraFeature (int state, String name, String value) |
| Parameter |
state |
Der Status des ExtraFeatures. Mögliche Werte sind STATE_IGNORE,
STATE_SHOULD_MATCH, STATE_MUST_MATCH.
|
name | Der Name des ExtraFeatures. |
value | Der Wert des ExtraFeatures. |
| |
int getState() |
| Rückgabewert | Der Status des ExtraFeatures. |
| |
void setState(int state) |
| Parameter |
state | Der zu setzende Status. |
| |
String getName() |
| Rückgabewert | Des Name des ExtraFeatures. |
| |
void setName(String name) |
| Parameter |
name | Der zu setzende Name. |
| |
String getValue() |
| Rückgabewert | Der Wert des ExtraFeatures. |
| |
void setValue(String value) |
| Parameter |
value | Der zu setzende Wert. |
| |
boolean getRegexp() |
| Rückgabewert | der regexp Status des ExtraFeatures. |
| |
void setRegexp(boolean regexp) |
| Parameter |
regexp | Der zu setzende regexp Status. |
| |
boolean getNegate() |
| Rückgabewert | der negate Status des ExtraFeatures. |
| |
void setNegate(boolean negate) |
| Parameter |
negate | Der zu setzende negate Status. |
| |
|
| |
Die Klasse de.qfs.apps.qftest.shared.data.ExtraFeatureSet bündelt
ExtraFeatures in einen Satz:
| |
|
|
| |
ExtraFeatureSet () |
| |
void add(ExtraFeature extraFeature) |
| Parameter |
extraFeature | Das hinzuzufügende ExtraFeature. |
| |
ExtraFeature get(String name) |
| Parameter |
name | Der Name des abzufragenden ExtraFeatures. |
| Rückgabewert |
Das ExtraFeature oder null, falls kein ExtraFeature unter diesem Namen im Satz
gespeichert ist.
|
| |
ExtraFeature remove(String name) |
| Parameter |
name | Der Name des zu entfernenden ExtraFeatures. |
| Rückgabewert |
Das entfernt ExtraFeature oder null, falls kein ExtraFeature unter diesem Namen im
Satz gespeichert war.
|
| |
ExtraFeature[] toArray() |
| Rückgabewert | Ein Array mit allen ExtraFeatures, nach Namen sortiert. |
| |
|
| |
Hinweis
Sofern Sie nicht das in Abschnitt 39.1.10 beschriebene
resolvers Modul nutzen, muss ein ItemNameResolver2
nicht bei der ResolverRegistry registriert werden, sondern bei der
ItemRegistry, wie in Abschnitt 39.2.4 beschrieben.
Das Interface
de.qfs.apps.qftest.extensions.ItemNameResolver2
besteht aus einer einzigen Methode:
| |
|
|
| |
String getItemName(Object element, Object item, String name) |
| Parameter |
element |
Das GUI Element zu dem das Unterelement gehört.
|
item |
Das Unterelement, dessen Name ermittelt wird. Sein Typ hängt von der Art des
GUI Elements und der registrierten ItemResolver ab, wie in Abschnitt 39.2.5 beschrieben.
|
name |
Der ursprüngliche Name, den QF-Test ohne Resolver verwenden würde.
|
| Rückgabewert | Der Name oder null, falls der Resolver dieses Element oder Unterelement
nicht behandelt.
|
| |
|
| |
Nachdem QF-Test einen Namen für den Index eines Unterelements ermittelt hat, erhalten die
registrierten ItemNameResolver2 die Chance, diesen zu überschreiben. Der
erste Resolver, der einen nicht-null Wert zurückliefert, bestimmt das Ergebnis. Sind
keine Resolver registriert oder liefern alle Resolver null, wird der ursprüngliche
Name benutzt.
Hinweis
Sofern Sie nicht das in Abschnitt 39.1.10 beschriebene
resolvers Modul nutzen, muss ein ItemValueResolver2
nicht bei der ResolverRegistry registriert werden, sondern bei der
ItemRegistry, wie in Abschnitt 39.2.4 beschrieben.
Das Interface
de.qfs.apps.qftest.extensions.ItemValueResolver2
besteht aus einer einzigen Methode:
| |
|
|
| |
String getItemValue(Object element, Object item, String value) |
| Parameter |
element |
Das GUI Element zu dem das Unterelement gehört.
|
item |
Das Unterelement, dessen Wert ermittelt wird. Sein Typ hängt von der Art des
GUI Elements und der registrierten ItemResolver ab, wie in Abschnitt 39.2.5 beschrieben.
|
value |
Der ursprüngliche Wert, den QF-Test ohne Resolver verwenden würde.
|
| Rückgabewert | Der Wert oder null, falls der Resolver dieses Element oder Unterelement
nicht behandelt.
|
| |
|
| |
Nachdem QF-Test einen Wert für ein Unterelements ermittelt hat, erhalten die
registrierten ItemValueResolver2 die Chance, diesen zu überschreiben. Der
erste Resolver, der einen nicht-null Wert zurückliefert, bestimmt das Ergebnis. Sind
keine Resolver registriert oder liefern alle Resolver null, wird der ursprüngliche
Wert benutzt.
Hinweis Dieses Interface ist für SWT irrelevant. Mehrspaltige Bäume
in SWT werden von QF-Test automatisch unterstützt.
Ein de.qfs.apps.qftest.extensions.TreeTableResolver
muss folgende beiden Methoden implementieren:
| |
|
|
| |
JTree getTree(JTable table) |
| Parameter |
table |
Die Tabelle für die der Baum ermittelt werden soll.
|
| Rückgabewert |
Der Baum oder null, falls es sich um eine normale Tabelle handelt.
|
| |
int getTreeColumn(JTable table) |
| Parameter |
table |
Die Tabelle für die der Spaltenindex des Baum ermittelt werden soll.
|
| Rückgabewert |
Der Spaltenindex oder -1, falls es sich um eine normale Tabelle handelt. Der
Spaltenindex muss immer in Model-Koordinaten geliefert werden, nicht in
View-Koordinaten.
|
| |
|
| |
Die meisten TreeTableResolver sind trivial zu implementieren.
Das folgende Beispiel in Jython genügt bereits für die
org.openide.explorer.view.TreeTable Komponente der populären netBeans
IDE, vorausgesetzt, dass der Resolver für die TreeTable Klasse registriert wird.
|
|
class TTResolver(TreeTableResolver):
def getTree(self, table):
return table.getCellRenderer(0,0)
def getTreeColumn(self, table):
return 0 |
|
|
| | Beispiel 39.1: TreeTableResolver für die netBeans IDE. | |
Alle Arten von Resolvern können in Java, Jython oder Groovy implementiert werden und
müssen dann bei der ResolverRegistry angemeldet werden. Bevor wir im
folgenden Abschnitt diesen relativ komplexen Prozess im Detail beschreiben, zeigen wir
zunächst an Hand von Beispielen, wie Resolver sehr einfach mit Hilfe des
resolvers Moduls erstellt werden können, welches mit QF-Test geliefert wird.
Diese Beispiele sollten für den normalen Gebrauch ausreichen. Nur wenn Sie im Detail
verstehen wollen wie das resolvers Modul selbst funktioniert, oder wenn
Sie Resolver in Java implementieren wollen, sollten Sie auch die folgenden Abschnitte
lesen.
Abgesehen von den komplexeren ItemResolver und Checker
Interfaces, die in den folgenden Abschnitten beschrieben werden, besteht ein Resolver
nur aus einer Methode, die implementiert werden muss (eineinhalb im Fall des
TreeTableResolvers). Auf Java Ebene werden Resolver dadurch kompliziert,
dass diese Methode als Interface definiert und von einer Klasse implementiert werden
muss. Von dieser Klasse muss eine Instanz erzeugt und bei QF-Test registriert werden.
Wenn es nicht auf Anhieb funktioniert, muss die Instanz deregistriert werden, bevor
eine neue Klasse erstellt und eine Instanz davon registriert werden kann. Andernfalls
könnte es zu Konflikten zwischen den beiden Versionen des Resolvers kommen. Dazu kommt
noch Code für die Fehlerbehandlung, insgesamt also ein vielfaches an Ballast im
Vergleich zur eigentlichen Substanz.
Zum Glück sind Methoden in Jython ebenso wie Closures in Groovy vollwertige Objekte
und der überschüssige Code ist im Prinzip für alle Resolver gleich. Dadurch ist es
möglich, die lästigen Teile aus dem Weg und in ein Modul zu verfrachten, und sich auf
die eigentliche Aufgabe zu konzentrieren. Das folgende Beispiel zeigt einen
NameResolver für JMenuItems der den Text eines Menüeintrags
als Name der Komponente definiert sofern es keinen eigenen Namen besitzt:
|
|
def getName(menuItem, name):
if not name:
return menuItem.getLabel()
resolvers.addNameResolver2("menuItems", getName,
"javax.swing.JMenuItem") |
|
|
| | Beispiel 39.2: Einfacher Jython NameResolver2 für JMenuItems | |
Wie Sie sehen bedarf es ganzer vier Zeilen Code, um das zu bewerkstelligen. Die ersten
drei Zeilen definieren die getName Methode für den Resolver. Die vierte
Zeile ruft die addNameResolver2 Funktion im resolvers Modul
auf, welches immer automatisch in Jython 'SUT Skript' Knoten verfügbar ist.
Die drei Parameter sind ein Name zur Identifikation des Resolvers, die eigentliche
Methode und ein oder mehrere optionale Ziele für den Resolver, in diesem Fall die
Klasse von Komponenten für die er registriert werden soll.
Probieren Sie es aus! Kopieren Sie das obige Beispiel in einen
'SUT Skript' Knoten und führen Sie diesen aus. Falls Ihre Anwendung auf
SWT basiert, ersetzen Sie getLabel() durch getText() und
javax.swing.JMenuItem durch org.eclipse.swt.MenuItem. Nehmen
Sie dann einige Menü Aktionen in eine neue, leere Testsuite auf. Sie werden sehen,
dass alle Komponenten ohne eigenen Namen für Menüeinträge nun einen Namen entsprechend
ihrer Beschriftung erhalten. Falls setName in Ihrer Anwendung nicht
verwendet wird und die Menü-Bezeichner weitgehend statisch sind, kann das sogar eine
recht nützliche Sache sein. Um den Resolver permanent zu installieren, verschieben Sie
den 'SUT Skript' Knoten direkt hinter den 'Warten auf Client' Knoten in
Ihrer Sequenz zum Start des SUT.
Das gleiche kann in Groovy mittels des resolvers Objekts erreicht werden,
welches ebenfalls immer automatisch verfügbar ist und das gleiche API besitzt. Es
erwartet als Parameter eine Closure um daraus den Resolver zu erstellen:
|
|
def getName = {menuItem, name ->
if (name == null) {
return menuItem.getLabel()
}
}
resolvers.addNameResolver2("menuItems", getName,
"javax.swing.JMenuItem") |
|
|
| | Beispiel 39.3: Einfacher Groovy NameResolver2 für JMenuItems | |
Ein Resolver kann gleichzeitig für mehrere Klassen von Elementen registriert werden:
|
|
def getName(com, name):
return com.getLabel()
resolvers.addNameResolver2("labels", getName, "javax.swing.JLabel",
"javax.swing.AbstractButton") |
|
|
| | Beispiel 39.4: Registrieren eines NameResolver2 für mehrere Klassen | |
Die anderen Resolver funktionieren genauso. Es folgt ein Beispiel für einen
ItemNameResolver2, der die ID einer JTable Spalte für den
|
|
def getColumnId(table, item, name):
if item[1] < 0: # whole column addressed
id = table.getColumnModel().getColumn(i).getIdentifier()
if id:
return str(id)
resolvers.addItemNameResolver("tableColumnId", getColumnId,
"javax.swing.table.TableColumn") |
|
|
| | Beispiel 39.5: Ein ItemNameResolver für JTable
Spalten | |
FeatureResolver und ExtraFeatureResolver werden genauso wie
NameResolver erstellt, wobei ExtraFeatureResolver ein wenig
komplexer sind, aber nicht viel, wie das folgende Beispiel zeigt:
|
|
from de.qfs.apps.qftest.shared.data import ExtraFeature, ExtraFeatureSet
def getExtraFeatures(node, features):
features.add(ExtraFeature(ExtraFeature.STATE_SHOULD_MATCH,
"myname", "myvalue"))
return features
resolvers.addExtraFeatureResolver("silly example", getExtraFeatures,
"INPUT") |
|
|
| | Beispiel 39.6:
ExtraFeatureResolver der ein weiteres Feature für INPUT Knoten erstellt
| |
TreeTableResolvers für TreeTables in Swing werden etwas anders
erstellt und registriert:
|
|
def getTree(table):
return table.getTree()
def getColumn(table):
return 0
resolvers.addTreeTableResolver("treeTable", getTree, getColumn,
"my.package.TreeTable") |
|
|
| | Beispiel 39.7: TreeTableResolver für Swing TreeTable mit optionaler
getColumn Methode | |
Das obige beispiel zeigt einen typischen TreeTableResolver. Da praktisch
alle TreeTables den Baum in der ersten Spalte der Tabelle darstellen, ist die
getColumn Methode optional. Wird keine übergeben, wird automatisch eine
default Implementierung für die erste Spalte erstellt:
|
|
def getTree(table):
return table.getTree()
resolvers.addTreeTableResolver("treeTable", getTree, None,
"my.package.TreeTable") |
|
|
| | Beispiel 39.8: Vereinfachter TreeTableResolver | |
Falls keine dedizierte getTree Methode vorhanden ist, hilft meist der CellRenderer
der Spalte, die den Baum enthält (typischerweise 0), da dieser oft von JTree abgeleitet ist.
|
|
def getTree(table):
return table.getCellRenderer(0,0)
resolvers.addTreeTableResolver("treeTable", getTree,
"my.package.TreeTable") |
|
|
| | Beispiel 39.9: Einfacher TreeTableResolver, der die getCellRenderer Methode nutzt | |
Es folgt eine vollständige Aufstellung der Funktionen des resolvers
Moduls:
| |
|
|
| |
addNameResolver2(String name, Method method, Object target=None, ...):
|
| Parameter |
name |
Der Name unter dem der Resolver registriert werden soll.
|
method |
Die Funktion, die die getName() Methode des Resolvers implementiert.
|
target |
Ein oder mehrere optionale Ziele für die der Resolver registriert werden soll. Für
jedes Ziel gibt es folgende Varianten:
- Eine individuelle Komponente
- Der Name einer Klasse
Ist kein Ziel angegeben, wird der Resolver global für alle Komponenten registriert.
|
| |
addIdResolver(String name, Method method, Object target=None, ...):
|
| Parameter |
name |
Der Name unter dem der Resolver registriert werden soll.
|
method |
Die Funktion, die die getId() Methode des Resolvers implementiert.
|
target |
Ein oder mehrere optionale Ziele für die der Resolver registriert werden soll. Für
jedes Ziel gibt es folgende Varianten:
- Eine individuelle Komponente
- Der Name einer Klasse
Ist kein Ziel angegeben, wird der Resolver global für alle Komponenten registriert.
|
| |
addFeatureResolver2(String name, Method method, Object target=None, ...):
|
| Parameter |
name |
Der Name unter dem der Resolver registriert werden soll.
|
method |
Die Funktion, die die getFeature() Methode des Resolvers
implementiert.
|
target |
Ein oder mehrere optionale Ziele für die der Resolver registriert werden soll. Für
jedes Ziel gibt es folgende Varianten:
- Eine individuelle Komponente
- Der Name einer Klasse
Ist kein Ziel angegeben, wird der Resolver global für alle Komponenten registriert.
|
| |
addExtraFeatureResolver(String name, Method method, Object target=None, ...):
|
| Parameter |
name |
Der Name unter dem der Resolver registriert werden soll.
|
method |
Die Funktion, die die getExtraFeatures() Methode des Resolvers
implementiert.
|
target |
Ein oder mehrere optionale Ziele für die der Resolver registriert werden soll. Für
jedes Ziel gibt es folgende Varianten:
- Eine individuelle Komponente
- Der Name einer Klasse
Ist kein Ziel angegeben, wird der Resolver global für alle Komponenten registriert.
|
| |
addItemNameResolver2(String name, Method method, Object target=None, ...):
|
| Parameter |
name |
Der Name unter dem der Resolver registriert werden soll.
|
method |
Die Funktion, die die getItemName() Methode des Resolvers
implementiert.
|
target |
Ein oder mehrere optionale Ziele für die der Resolver registriert werden soll. Für
jedes Ziel gibt es folgende Varianten:
- Eine individuelle Komponente
- Der Name einer Klasse
Ist kein Ziel angegeben, wird der Resolver global für alle Komponenten registriert.
|
| |
addItemValueResolver2(String name, Method method, Object target=None, ...):
|
| Parameter |
name |
Der Name unter dem der Resolver registriert werden soll.
|
method |
Die Funktion, die die getItemValue() Methode des Resolvers
implementiert.
|
target |
Ein oder mehrere optionale Ziele für die der Resolver registriert werden soll. Für
jedes Ziel gibt es folgende Varianten:
- Eine individuelle Komponente
- Der Name einer Klasse
Ist kein Ziel angegeben, wird der Resolver global für alle Komponenten registriert.
|
| |
addTreeTableResolver(String name, Method getTable, Method getColumn=None, Object target=None):
|
| Parameter |
name |
Der Name unter dem der Resolver registriert werden soll.
|
getTable |
Die Funktion, die die getTable() Methode des Resolvers
implementiert.
|
getColumn |
Die optionale Funktion, die die getColumn() Methode des Resolvers
implementiert.
|
target |
Ein optionales Ziel für das der Resolver registriert werden soll. Für
dieses Ziel gibt es folgende Varianten:
- Eine individuelle Komponente
- Der Name einer Klasse
Ist kein Ziel angegeben, wird der Resolver global für alle Komponenten registriert.
|
| |
addClassNameResolver(String name, Method method, Object target=None, ...):
|
| Parameter |
name |
Der Name unter dem der Resolver registriert werden soll.
|
method |
Die Funktion, die die getClassName() Methode des Resolvers implementiert.
|
target |
Ein oder mehrere optionale Ziele für die der Resolver registriert werden soll. Für
jedes Ziel gibt es folgende Varianten:
- Eine individuelle Komponente
- Der Name einer Klasse
Ist kein Ziel angegeben, wird der Resolver global für alle Komponenten registriert.
|
| |
removeResolver(String name)
|
| Parameter |
name | Der Name, unter dem der Resolver registriert war. |
| |
|
| |
Die Singleton Klasse
de.qfs.apps.qftest.extensions.ResolverRegistry
ist die zentrale Agentur für die Registrierung und das Entfernen
von Resolvern.
Alle Arten von Resolvern können wahlweise für individuelle
Komponenten oder für Klassen registriert werden. Resolver für
individuelle Komponenten werden nur aufgerufen, wenn ihre Information für genau diese
Komponente benötigt wird.
Resolver, die für eine Klasse registriert sind,
werden für alle Objekte dieser oder einer davon abgeleiteten Klasse aufgerufen.
NameResolver, FeatureResolver und
TreeTableResolver können auch global registriert werden, so dass sie für
jeden zu ermittelnden Namen und jedes Merkmal aufgerufen werden. Dies ist äquivalent
zu - aber effektiver als - eine Registrierung für die Klasse
java.lang.Object.
Sind mehrere Resolver global, für ein bestimmtes Objekt oder eine
bestimmte Klasse registriert, wird der zuletzt registrierte
Resolver zuerst aufgerufen.
Das API der ResolverRegistry enthält keine großen
Überraschungen:
| |
|
|
| |
static ResolverRegistry instance() |
| Rückgabewert | Die
ResolverRegistry Singleton Instanz. |
| |
static String getElementName(Object element) |
| Parameter |
element |
Das GUI Element dessen Name ermittelt werden soll.
|
| Rückgabewert |
Der Name der Komponente oder null, falls ein trivialer oder
spezieller Name unterdrückt wird.
|
| |
static boolean isInstance(Object object, String className) |
| Parameter |
object | Das zu prüfende Objekt. |
className | Der Name der Klasse auf die getestet wird. |
| Rückgabewert |
True falls das Objekt eine Instanz der Klasse ist.
|
| |
void registerNameResolver2(Object element, NameResolver2 resolver) |
| Parameter |
element |
Das GUI Element für das registriert wird.
|
resolver |
Der zu registrierende Resolver.
|
| |
void unregisterNameResolver2(Object element, NameResolver2 resolver) |
| Parameter |
element |
Das GUI Element für das entfernt wird.
|
resolver |
Der zu entfernende Resolver.
|
| |
void registerNameResolver2(String clazz, NameResolver2 resolver) |
| Parameter |
clazz |
Der Name der Klasse für die registriert wird.
|
resolver |
Der zu registrierende Resolver.
|
| |
void unregisterNameResolver2(String clazz, NameResolver2 resolver) |
| Parameter |
clazz |
Der Name der Klasse für die entfernt wird.
|
resolver |
Der zu entfernende Resolver.
|
| |
void registerNameResolver2(NameResolver2 resolver) |
| Parameter |
resolver |
Der zu registrierende Resolver.
|
| |
void unregisterNameResolver2(NameResolver2 resolver) |
| Parameter |
resolver |
Der zu entfernende Resolver.
|
| |
void registerIdResolver(Object element, IdResolver resolver) |
| Parameter |
element |
Das GUI Element für das registriert wird.
|
resolver |
Der zu registrierende Resolver.
|
| |
void unregisterIdResolver(Object element, IdResolver resolver) |
| Parameter |
element |
Das GUI Element für das entfernt wird.
|
resolver |
Der zu entfernende Resolver.
|
| |
void registerIdResolver(String clazz, IdResolver resolver) |
| Parameter |
clazz |
Der Name der Klasse für die registriert wird.
|
resolver |
Der zu registrierende Resolver.
|
| |
void unregisterIdResolver(String clazz, IdResolver resolver) |
| Parameter |
clazz |
Der Name der Klasse für die entfernt wird.
|
resolver |
Der zu entfernende Resolver.
|
| |
void registerIdResolver(IdResolver resolver) |
| Parameter |
resolver |
Der zu registrierende Resolver.
|
| |
void unregisterIdResolver(IdResolver resolver) |
| Parameter |
resolver |
Der zu entfernende Resolver.
|
| |
void registerFeatureResolver2(Object element, FeatureResolver2 resolver) |
| Parameter |
element |
Das GUI Element für das registriert wird.
|
resolver |
Der zu registrierende Resolver.
|
| |
void unregisterFeatureResolver2(Object element, FeatureResolver2 resolver) |
| Parameter |
element |
Das GUI Element für das entfernt wird.
|
resolver |
Der zu entfernende Resolver.
|
| |
void registerFeatureResolver2(String clazz, FeatureResolver2 resolver) |
| Parameter |
clazz |
Der Name der Klasse für die registriert wird.
|
resolver |
Der zu registrierende Resolver.
|
| |
void unregisterFeatureResolver2(String clazz, FeatureResolver2 resolver) |
| Parameter |
clazz |
Der Name der Klasse für die entfernt wird.
|
resolver |
Der zu entfernende Resolver.
|
| |
void registerFeatureResolver2(FeatureResolver2 resolver) |
| Parameter |
resolver |
Der zu registrierende Resolver.
|
| |
void unregisterFeatureResolver2(FeatureResolver2 resolver) |
| Parameter |
resolver |
Der zu entfernende Resolver.
|
| |
void registerExtraFeatureResolver(Object element, ExtraFeatureResolver resolver) |
| Parameter |
element |
Das GUI Element für das registriert wird.
|
resolver |
Der zu registrierende Resolver.
|
| |
void unregisterExtraFeatureResolver(Object element, ExtraFeatureResolver resolver) |
| Parameter |
element |
Das GUI Element für das entfernt wird.
|
resolver |
Der zu entfernende Resolver.
|
| |
void registerExtraFeatureResolver(String clazz, ExtraFeatureResolver resolver) |
| Parameter |
clazz |
Der Name der Klasse für die registriert wird.
|
resolver |
Der zu registrierende Resolver.
|
| |
void unregisterExtraFeatureResolver(String clazz, ExtraFeatureResolver resolver) |
| Parameter |
clazz |
Der Name der Klasse für die entfernt wird.
|
resolver |
Der zu entfernende Resolver.
|
| |
void registerExtraFeatureResolver(ExtraFeatureResolver resolver) |
| Parameter |
resolver |
Der zu registrierende Resolver.
|
| |
void unregisterExtraFeatureResolver(ExtraFeatureResolver resolver) |
| Parameter |
resolver |
Der zu entfernende Resolver.
|
| |
void registerTreeTableResolver(Object element, TreeTableResolver resolver) |
| Parameter |
element |
Das GUI Element für das registriert wird.
|
resolver |
Der zu registrierende Resolver.
|
| |
void unregisterTreeTableResolver(Object element, TreeTableResolver resolver) |
| Parameter |
element |
Das GUI Element für das entfernt wird.
|
resolver |
Der zu entfernende Resolver.
|
| |
void registerTreeTableResolver(String clazz, TreeTableResolver resolver) |
| Parameter |
clazz |
Der Name der Klasse für die registriert wird.
|
resolver |
Der zu registrierende Resolver.
|
| |
void unregisterTreeTableResolver(String clazz, TreeTableResolver resolver) |
| Parameter |
clazz |
Der Name der Klasse für die entfernt wird.
|
resolver |
Der zu entfernende Resolver.
|
| |
void registerTreeTableResolver(TreeTableResolver resolver) |
| Parameter |
resolver |
Der zu registrierende Resolver.
|
| |
void unregisterTreeTableResolver(TreeTableResolver resolver) |
| Parameter |
resolver |
Der zu entfernende Resolver.
|
| |
void unregisterResolvers(Object element) |
| Parameter |
element |
Das GUI Element für das entfernt wird.
|
| |
void unregisterResolvers(String clazz) |
| Parameter |
clazz |
Der Name der Klasse für die entfernt wird.
|
| |
|
| |
Alle Exceptions, die während der Ausführung eines Resolvers auftreten, werden von der
ResolverRegistry abgefangen. Es wird allerdings nur eine kurze Meldung
und kein Stacktrace ausgegeben, weil insbesondere globale Resolver sehr oft aufgerufen
werden können. Somit würde ein Resolver, der einen Bug hat, durch die Ausgabe von
Stacktraces für jeden Fehler das Client Terminal überfluten. Daher sollten Resolver
ihre eigenen Fehlerbehandlungsroutinen enthalten. Dabei können zwar immer noch extrem
viele Ausgaben erzeugt werden, aber speziell für Skripte sind diese wenigstens
hilfreicher als Java Stacktraces.