Mithilfe der NEAP-API, können Testfälle für bestehende Flows geschrieben und ausgeführt werden.
Dafür wird Postman 👨🚀 genutzt.
Um einen neuen Test zu erstellen, sind folgende Schritte notwendig:
- Auf Postman einen neuen Request erstellen
- Den Request Namen setzen, die HTTP Methode auf POST setzen und die Ziel URL auf
https://[WORKSPACE].neohelden.com/auth
setzen. Wobei[WORKSPACE]
das Ziel Workspace ist. - Das Pre-Request Template(In
./lib/postman-templates/pre-request.js
) kopieren und in der Pre-request Script Leiste des Postman Requests einfügen - Das Test Template(In
./lib/postman-templates/test-template.js
) kopieren und in der Tests Leiste des Postman Requests einfügen - In der Testvorlage aus Schritt 4. die
TODO
's mit den fehlenden Daten ergänzen - Wenn ein Login (Nutzer-Authentifizierung) notwendig ist, müssen die Anmeldedaten im Body als JSON hinzugefügt werden. Siehe dazu den nächsten Abschnitt Authentifizierung.
// Wir beginnen den Test mit einem "Handshake"
//
// Wir benutzen "await", um auf die Antwort von Neo zu warten
await sendAction('handshake')
// Neo antwortet nun mit einem gesprochenen Text ("says")
// und zeigt mehrere AdaptiveCard-Inhalte an
//
// Die Antwort überprüfen wir mithilfe der Hilfsfunktionen
says('Guten Tag, was kann ich für dich tun?')
showsAdaptiveCard('Hier eine Auswahl unserer Möglichkeiten', {
position: 2, // hier prüfen wir bewusst, die 2. Position der Antwort
})
showsAdaptiveCard('Weitere Optionen', { position: 3 })
// Nun senden wir eine normale Nachricht
// Neo antwortet mit einer Text-Nachricht
await sendMessage('Ich möchte ein Zimmer buchen')
showsText('Welche Stadt?')
// Wir nutzen `sendReply`, um den Kontext der Konversation zu behalten
await sendReply('Karlsruhe')
showsText('Welches Datum?')
await sendReply('1. April 2020')
showsText('Vielen Dank für die Buchung.')
// Wir können mithilfe von sendMessage auch Commands auslösen
await sendMessage('/image')
isContentType('image')
await sendMessage('Zeige mir ein Video von den Neohelden' )
showsMedia('https://youtu.be/I2waThpOfrc')
return 'Done'
Bei einem Workspace mit anonymous
-Authentifizierung kann dieser Abschnitt übersprungen werden. Falls aber ein Login für den Assistenten genutzt wird, müssen die folgenden Schritte zur Authentifizierung durchgeführt werden:
- Erstelle einen neuen App-User für den Workspace über die Benutzerverwaltung und lege ein Passwort fest
- Im Body-Tab der Postman-Anfrage den
Username
des App-Users undPassword
einfügen:
{
"username": "[USERNAME]",
"password": "[PASSWORD]"
}
- Sicherheitshinweis: Synchronisierte Testfälle sollten die Zugangsdaten für den Workspace nicht im Klartext speichern. Die Zugangsdaten sollten separat über einen Passwort-Manager ausgetauscht werden. Die gespeicherten und synchronisierten Testfälle sollten mit Platzhaltern (s. Beispiel oben) arbeiten.
Um die Funktionstüchtigkeit des Assistenten sicherzustellen, gibt es unterschiedliche Anfrage-Arten (request-types
), die als Anwender ausgelöst werden können. Je nach Art der Anfrage können dabei zusätzliche Daten übermittelt werden.
Eine Message
beinhaltet einen Text, der von der Plattform weiterverarbeitet wird. Beginnt die Nachricht mit einem /
(Slash), wird die Nachricht als Command
verarbeitet. Alle anderen Nachrichten werden mithilfe der NLU auf Intents und Slots geprüft.
// Sendet eine beliebige Nachricht ab
await sendMessage('Ein Intent auslösen')
// Löst ein Kommando aus (aufgrund des beginnenden Slash)
await sendMessage('/kommando')
// Löst ein Kommando mit Argumenten aus (aufgrund des beginnenden Slash)
await sendMessage('/resetUser Seraph')
Eine Reply
bezieht sich immer auf die vorhergehende Nachricht und stellt bspw. eine Antwort auf eine Rückfrage dar. Flows, die einen Reprompt
einsetzen, erfordern im Testfall des Einsatz von sendReply
. Auch eine Reply
wird mithilfe der NLU verarbeitet und beinhaltet in der Antwort die Metadaten über die erkannten Intents und Slots.
// Keine Antwort ohne vorhergehende Nachricht
// Daher begrüßen wir Neo vorab
await sendMessage('Hallo Neo')
// Neo antwortet: Hallo, wie geht es dir?
// Im Flow wird hier nun ein Reprompt eingesetzt
await sendReply('Gut und wie geht es dir?')
Eine Action
wird über unterschiedliche Komponenten ausgelöst. Das kann bspw. der initiale (digitale) handshake
sein. Die AdaptiveCard-, Camera- oder Upload-Komponenten lösen ebenfalls Action
-Anfragen aus.
// Löst die Handshake-Action aus
await sendAction('handshake')
// Optional können auch Daten als Payload (data-Attribut) mitgesendet werden
await sendAction('processForm', {
foo: 'bar',
})
Es ist zudem möglich, eine Action auszuführen und dabei das data
Attribut einer AdaptiveCard zu nutzen. Durch diese Methode ist es möglich, einen "Button in einer AdaptiveCard auszuführen". Mit der sendAdaptiveCardAction
werden die Daten aus der AdaptiveCard als Action
-Anfrage gesendet. Somit kann ein bestimmter Kontext beim interagieren mit einer AdaptiveCard erhalten bleiben.
// Mache eine Action Anfrage mit der "dieAction" Action und den dazugehörigen Daten
await sendAdaptiveCardAction('processForm')
// Optional kann die Position der AdaptiveCard definiert werden
await sendAdaptiveCardAction('processForm', 2)
Zur Auswertung der erwarteten Antwort können alle Bestandteile des Particles und der Antwort (response
) verwendet werden. Diese können im Particle Schema oder in den Docs nachgelesen werden.
Um das Testen des Particle Response so einfach wie möglich zu gestalten, gibt es eine Reihe von Hilfsfunktionen. Diese erleichtern die Überprüfung einzelner Inhalte mithilfe einer einfachen Syntax. Die Einzelfunktionen können dabei auch aufeinanderfolgen und unterschiedliche Aspekte der Antwort validieren.
Eine Liste aller vorhandenen Funktionen gibt es in der ausführlichen Test-API Dokumentation. In der Template-Datei ist eine Kurzversion der gängisten Funktionen hinterlegt:
// Tipp: Man kann diese Referenzen am Editor "einklappen" :)
const {
sendMessage,
sendReply,
sendReply,
sendRequest,
isResponseOk,
isReprompt,
isContentType,
showsAdaptiveCard,
//... -> Siehe Postman Template Import
Hinweis: Die Werte in geschweifter Klammer sind optionale Parameter. Mehr dazu im nächsten Abschnitt.
- Auf Reprompt testen:
isReprompt({ hintToCheck: 'Ein Hint' })
- Inhaltstypen (Contents): Auf Merkmal in einer AdaptiveCard oder Text testen:
Inhaltstypen haben ein
shows
Präfix
showsAdaptiveCard('Ein Text in einer Adaptive Card')
// ...
showsText('Ein text in einer plain node')
- Bedienungselemente (Controls): Auf Audio URL testen:
Bedienungselemente haben ein
triggers
Präfix
triggersSuggestion({ label: 'Suggestion label', value: 'Suggestion value' })
- Directives: Auf E-mail erstellen testen:
Directives haben ein
does
Präfix
doesComposeAnEmail({ recipients: '[email protected]', subject: 'Flow test Docs' })
- Sticky testen:
isSticky({ dataToCheckFor: 'Ein text in einer Sticky' })
- Intent confidence testen:
// confidence für neo.hello muss mindestens 0.79 betragen
isIntent('neo.hello', 0.79)
Viele Funktionen haben optionale Parameter. Zum Beispiel kann mit position
auf eine bestimmte Nachricht getestet werden, wenn wir mehrere Nachrichten erwarten.
Die Liste der optionalen Parameter für die jeweilige Funktion, kann in den JS Docs nachgelesen werden: ausführliche Dokumentation. Hier werden optionale Parameter mit eckigen Klammern dargestellt([]
). Beispiel von Doku und Anwendung im Test:
// ... Hier anfrage
// Jetzt verarbeiten/test
// jsdoc: triggersSuggestion([label], [value], [style])
triggersSuggestion({ label: 'Ein Label einer Suggestion' })
Wenn kein Pflichtparameter (Nicht in eckigen Klammern) existiert, wird bei leerer Parameterübergabe lediglich getestet, ob es sich um den jeweiligen Content type oder Direktive handelt.
Werden mehrere Nachrichten bei einer Antwort verschickt, kann man auf eine bestimmte Nachricht testen. Der optionale Parameter position
ermöglicht einen solchen Test. Dieser gibt an, welche Nachricht ausgewählt werden soll. Ein Beispiel, mit Erklärungen in den Kommentaren:
await sendAction('handshake')
// Die erste Nachricht soll ein Text sein
showsText('Wie kann ich dir helfen', { position: 1 })
// Die Zweite Nachricht soll eine Adaptive Card sein
showsAdaptiveCard('Tests für den letzten Release', {
position: 2,
})
// Die Dritte Nachricht soll eine Adaptive Card sein
showsAdaptiveCard('Alle Tests', { position: 3 })
Generell, kann man folgendermaßen jeden Eintrag im Particle Testen:
pm.test('[TEST-BESCHREIBUNG]', () => {
pm.expect(particle.response[RESPONSE_PART]).to.contain('[ERWARTERTER-INHALT]')
})
Siehe die Postman Dokumentation. Die Assertions basieren auf dem ChaiJS Framework.
Es ist nicht nötig den Particle zu verarbeiten. Wenn dies jedoch gewünscht ist, kann über den Rückgabewert darauf zugegriffen werden.
const particle = await sendMessage('Ein Intent auslösen')
// Particle verarbeiten
Zur Versionierung kommt Semantische Versionierung zum Einsatz.
Die einzelnen Versionen werden mittels Git Tags gesammelt und können über Postman zugegriffen werden.
- Release erstellen:
npm version [<newversion> | major | minor | patch | premajor | preminor | prepatch | prerelease [--preid=<prerelease-id>] | from-git]
Um von Postman bestimmte Hilfsfunktionen von einer bestimmten Version zu bekommen wird auf die raw
Version der Funktionsdatei verlinkt.
Dies funktioniert folgendermaßen:
- Auf GitHub die
raw
Version der Funktionsdatei(In./lib
, bspw../lib/neo-test-functions.js
) holen. - URL der
raw
Datei kopieren - In Postman im
Pre-Request
Tab beim Request als Ziel URL einsetzen:
Siehe in ./lib/pre-request.js
:
// ...
return new Promise((res, rej) => {
pm.sendRequest('[RAW_NEO_TESTING_LINK]', (error, response) => {
if (error) {
// ...