50.8 Das Assertions-Modul: Sprechende Prüfausdrücke
Inspiriert von Chai.js haben wir eine eigene API für sprechende Asserts erstellt. Damit können Prüfausdrücke (Assertions) in einer formalisierten Sprache, die normale Wörter aus dem Englischen verwendet, formuliert werden. Sie kann in Groovy, JavaScript und Jython Skripten genutzt werden.
50.8.1 Motivation
Das Ziel ist, Prüfausdrücke, die in Skripten verwendet werden, besser lesbar
zu machen und auch komplexe Datenstrukturen prüfen zu können.
Aktuell geschieht die Überprüfung
von Daten in Server und SUT Skripten im Normalfall über rc.check()
oder über assert von Java. Das funktioniert gut für Basisdatentypen
wie Zeichenketten. Es ist jedoch mühsam, komplexe Datenstrukturen wie strukturierte Objekte,
die zum Beispiel aus einem JSON-String erzeugt wurden,
damit zu prüfen. Die QF-Test Assertions API macht hier das Leben deutlich leichter.
Nachfolgend finden Sie zwei Groovy Skripte, die exemplarisch den Unterschied
zwischen sprechenden Prüfausdrücken und rc.check() beziehungsweise assert() zeigen.
def foo = 'bar'
def beverages = [ tea: [ 'chai', 'matcha', 'oolong' ] ]
expect(foo).to.be.a('String')
foo.should.be.equal('bar')
expect(foo).to.have.lengthOf(3)
expect(beverages).to.have.property('tea').with.lengthOf(3)
def foo = 'bar'
def beverages = [ tea: [ 'chai', 'matcha', 'oolong' ] ]
rc.check(foo instanceof String,"")
rc.checkEqual(foo,'bar',"")
rc.checkEqual(foo.length(),3,"")
assert(beverages.tea!=null)
assert(beverages.tea.size()==3)
rc.check und Java assert
50.8.2 API-Dokumentation
Die QF-Test Assertions API bietet die Schnittstellen Assert und expect. In Groovy-Skripten
steht auch zusätzlich should zur Verfügung. expect und should
unterstützen Wort- bzw. Aufrufketten. Die Syntax von Assert ist
eher die klassische Assert-Syntax wie in Java.
Das Prüfergebnis wird als erfolgreicher bzw. fehlgeschlagener Check oeder optional als Exception im Protokoll gespeichert. Zusätzlich kann das Ergebnis auch als boolescher Wert abgefragt und in einer Variable geschrieben werden. Details hierzu finden Sie in Ergebnisbehandlung.
Die API-Dokumentation steht in doc/javadoc/qfaa.zip zur Verfügung.
Darin sind alle verfügbaren Methoden für Assert enthalten. Diese
können ebenfalls bei expect oder should (in Groovy) als Teil der
Wortketten verwendet werden.
Da die QF-Test Assertions API sehr ähnlich zu Chai.js ist, funktionieren die meisten Beispiele von
https://www.chaijs.com/api/ auch in QF-Test.
Die Methoden, die in Chai.js verfügbar sind, aber noch nicht für QF-Test implementiert
wurden, sind in "Nicht verfügbare Prüfausdrücke" aufgelistet.
Für Assert kann eine Autovervollständigung mit Dokumentation der
verfügbaren Methoden aktivieren werden, indem Sie Assert. eintippen
und anschließend Strg+Leertaste gleichzeitig drücken.
Reguläre Ausdrücke - Regexps können auch genutzt werden. Diese sind über das Modul java.util.regex.Pattern implementiert.
Hinweis Eine Einführung in die Erweiterung der Assertions API mit eigenen Assertions finden Sie in unserem Blogbeitrag Die QF-Test Assertion API erweitern – Eine praktische Einführung.
50.8.2.1 Wortketten
Der größte Vorteil ist die gute Lesbarkeit, die über Wortketten erreicht wird.
Diese können bei expect() und should() verwendet
werden. Die folgenden Ausdrücke können aneinander gekettet werden:
.to .be .been .is .that .which .and .has .have .with .at .of .same .but
.does .still .also
def testObj = [
"name": "test",
"sub": [
"name": 'test sub'
],
"numbers": [1, 2, 3, 4],
"hasNumbers" : true
];
expect(testObj).to.be.an('Object').and.is.ok
expect(testObj).to.have.property('sub').that.is.an('Object').and.is.ok
expect(testObj.sub).to.have.property('name').that.is.a('String').and.to.equal('test sub')
expect(testObj).to.have.property('numbers').that.deep.equals([1, 2, 3, 4])
expect(testObj).to.have.property('hasNumbers', true)
expect
rc.setLocal("jsonData", """
{
"Actors": [
{
"name": "Tom Cruise",
"age": 56,
"Born At": "Syracuse, NY",
"Birthdate": "July 3, 1962",
"photo": "https://jsonformatter.org/img/tom-cruise.jpg",
"wife": null,
"weight": 67.5,
"hasChildren": true,
"hasGreyHair": false,
"children": [
"Suri",
"Isabella Jane",
"Connor"
]
},
{
"name": "Robert Downey Jr.",
"age": 53,
"Born At": "New York City, NY",
"Birthdate": "April 4, 1965",
"photo": "https://jsonformatter.org/img/Robert-Downey-Jr.jpg",
"wife": "Susan Downey",
"weight": 77.1,
"hasChildren": true,
"hasGreyHair": false,
"children": [
"Indio Falconer",
"Avri Roel",
"Exton Elias"
]
}
]
}""")
def data = rc.getJson("jsonData")
data.Actors.should.be.a("ArrayList")
expect(data.Actors[0]).to.be.a("LinkedHashMap")
Assert.instanceOf(data.Actors[0], "LinkedHashMap", "Bla")
data.Actors[0].name.should.be.a("String")
data.Actors[0].age.should.be.a("Long")
data.Actors[0].weight.should.be.a("Double")
data.Actors[0].hasChildren.should.be.a("Boolean")
rc.setGlobalJson("gData",data)
should
Die Dokumentation hierzu finden sie in https://www.chaijs.com/api/bdd.
50.8.2.2 Unterschiede zwischen dem QF-Test Assertions API und Chai.js
Durch die Implementierung in Java ergeben sich ein paar Unterschiede zu Chai.js.
-
Weil
assertin Java und Groovy ein reserviertes Schlüsselwort ist, muss dasAssertvon QF-Test anders (nämlich groß) geschrieben werden. Dies gilt ebenfalls für die PrüfausdrückeTRUE,FALSEundNULL, welche vollständig in Großbuchstaben geschrieben werden müssen. -
Alle Methoden, die mit dem Präfix
strictbeginnen, verwenden bei Vergleichen den Gleichheitsoperator==, ansonsten wird die Java-Methodeequal()genutzt. Eine Liste der verfügbarenstrict-Methoden erhalten Sie, wenn Sie im Skript-EditorAssert.stricteintippen und anschließend Strg+Leertaste drücken. -
Assert.testwird anstelle vonassertverwendet.Assert.test('foo' !== 'bar', 'foo is not bar') Assert.test({true}, 'Closures can return true')Beispiel 50.13: Assert.test(...)
50.8.2.3 Nicht verfügbare Prüfausdrücke
Einige der Prüfungen, die unter Chai.JS zu verfügung stehen, können nicht direkt von Javascript nach Java übertragen werden, und einige Assertions sind noch nicht implementiert. Dazu gehören unter anderem:
Assert
-
isAbove(), isAtLeast(), isBelow(), isAtMost() -
isNaN(), isNotNan() -
isUndefined() -
isFinite() -
throws(), doesNotThrow() -
operator() -
closeTo()
Expect/Should
-
.to.be.above(), .to.be.least(), .to.be.below(), .to.be.most() -
.to.be.NaN, .not.to.be.NaN -
.to.be.undefined -
.to.be.finite -
.to.throw(), .to.not.throw() -
.to.be.closeTo()
50.8.3 Ergebnisbehandlung
- Ergebnisbehandlung mit Assert, expect() und should() (System)
-
Server (automatisch weiter an SUT) Skript-Name:
OPT_PLAY_HANDLE_ASSERTION
Mögliche Werte: VAL_PLAY_HANDLE_ASSERTION_AS_CHECK, VAL_PLAY_HANDLE_ASSERTION_WITH_EXCEPTION, VAL_PLAY_HANDLE_ASSERTION_SILENTLY
Mit dieser Option beeinflusst man den Rückgabewert und die Protokollierung.
-
“Als Check behandeln” - VAL_PLAY_HANDLE_ASSERTION_AS_CHECK
Im Fehlerfall wird ein Fehler protokolliert, im Erfolgsfall ein erfolgreicher Check. -
“Als Exception” - VAL_PLAY_HANDLE_ASSERTION_WITH_EXCEPTION
Es wird im Fehlerfall eine Exception geworfen. Sie kann in Skripten als Throwable und in Try-Catch-Knoten alsScriptExceptiongefangen werden -
“Als Rückgabewert” - VAL_PLAY_HANDLE_ASSERTION_SILENTLY
Die Prüfung wird ausgeführt, aber nicht als Fehler gekennzeichnet. Es wird auch keine Exception geworfen. Das Ergebnis der Prüfung wird als Wert zurückgegeben, entwedertrueoderfalse. Wennexpectodershouldverwendet wird, muss.getResult()angehängt werden, um an den Rückgabewert zu gelangen. -
VAL_PLAY_HANDLE_ASSERTION_AUTOMATICALLY (Standardwert)
EntsprichtVAL_PLAY_HANDLE_ASSERTION_AS_CHECK, außer bei der Verwendung der QF-Test Assertions API in einem Unit-Test-Knoten. Dann wird automatischVAL_PLAY_HANDLE_ASSERTION_WITH_EXCEPTIONverwendet um kompatibel mit JUnit zu sein.
-
“Als Check behandeln” - VAL_PLAY_HANDLE_ASSERTION_AS_CHECK
Die Option wird am Anfang des Skripts gesetzt:
rc.setOption(Options.OPT_PLAY_HANDLE_ASSERTION, Options.VAL_PLAY_HANDLE_ASSERTION_SILENTLY)
def a = 54
def b = 55
isEqual = Assert.test(a==b, "")
if (isEqual) {
...
}
Wenn die Assertion in natürlicher Sprache formuliert wird, muss .getResult() angehängt werden, um
an den Rückgabewert zu gelangen:
rc.setOption(Options.OPT_PLAY_HANDLE_ASSERTION, Options.VAL_PLAY_HANDLE_ASSERTION_SILENTLY)
a = 54
b = 55
isEqual = expect(a).to.equal(b).getResult()
if isEqual.getResult():
...