PDF-Dokumente werden durch Aktionen lebendig, interaktiv aber auch komplizierter. Und „kompliziert“ bedeutet, dass sie getestet werden müssen, zumal wenn interaktive Dokumente Teil einer Prozesskette sind, in der Aktionen die Teile sind, die richtig funktionieren müssen.
Eine „Aktion“ ist ein Dictionary-Objekt mit
u.a. den Elementen /S
und /Type
.
Das Element /Type
hat immer immer den Wert „Action“
und das Element /S
(Subtype) hat typabhängige Werte:
// Types of actions: GoTo: Set the focus to a destination in the current PDF document GoToR: Set the focus to a destination in another PDF document GoToE: Go to a destination inside an embedded file GoTo3DView: Set the view to a 3D annotation Hide: Set the hidden flag of the specified annotation ImportData: Import data from a file to the current document JavaScript: Execute JavaScript code Movie: Play a specified movie Named: Execute an action, which is predefined by the PDF viewer Rendition: Control the playing of multimedia content ResetForm: Set the values of form fields to default SetOCGState: Set the state of an OCG Sound: Play a specified sound SubmitForm: Send the form data to an URL Launch: Execute an application Thread: Set the viewer to the beginning of a specified article Trans: Update the display of a document, using a transition dictionary URI: Go to the remote URI
Für einige dieser Aktionen stellt PDFUnit Tags zur Verfügung:
<!-- Tags to test actions: --> <hasNumberOfActions /> <hasNumberOfJavaScriptActions /> <hasAnyAction> <containing /> (all nested tags ... <matchingComplete /> ... <matchingRegex /> ... are optional) </hasAnyAction> ... continued
... continuation: <hasChainedAction> <containing /> (all nested tags ... <matchingComplete /> ... <matchingRegex /> ... are optional) </hasChainedAction> <hasCloseAction> <containing /> (all nested tags ... <matchingComplete /> ... <matchingRegex /> ... are optional) </hasCloseAction> <hasImportDataAction> <matchingComplete filename=".." /> (tag optional, attribute required) </hasImportDataAction> <hasJavaScriptAction> <containing /> (all nested tags ... <matchingComplete /> ... <matchingRegex /> ... are optional) </hasJavaScriptAction> <hasLaunchAction> <toLaunch /> (optional) </hasLaunchAction> ... continue
... continuation <hasLocalGotoAction> <toDestination /> (optional) </hasLocalGotoAction> <hasNamedAction> <withName /> (optional) </hasNamedAction> <hasOpenAction> <containing /> (all nested tags ... <matchingComplete /> ... <matchingRegex /> ... <withDestinationTo /> ... are optional) </hasOpenAction> <hasPrintAction> <containing /> (all nested tags ... <matchingComplete /> ... <matchingRegex /> ... are optional) </hasPrintAction> ... continued
... continuation: <hasRemoteGotoActionTo file=".." (required) destination=".." (optional) page=".." (optional) /> <hasResetFormAction /> (no attributes and no nested tags!) <hasSaveAction> <containing /> (all nested tags ... <matchingComplete /> ... <matchingRegex /> ... are optional) </hasSaveAction> <hasSubmitFormAction> <withDestination /> (optional) </hasSubmitFormAction> <hasURIAction> <containing /> (all nested tags ... <matchingComplete /> ... <matchingRegex /> ... are optional) </hasURIAction> ... (end of list)
Die folgenden Abschnitte zeigen Beispiele für verschiedene Aktionen.
Close-Actions werden beim Schließen eines PDF-Dokumentes ausgeführt:
<!-- The fundamental way to compare actions with expected values: --> <testcase name="Principle_ComparingActionValues"> <assertThat testDocument="actions/documentCloseAction.pdf"> <hasCloseAction> <containing>app.alert('A sample for a DOCUMENT_CLOSE-action');</containing> </hasCloseAction> </assertThat> </testcase>
Der Inhalt einer Close-Action kann auch mit dem Inhalt einer Datei verglichen werden:
<testcase name="hasCloseAction_MatchingComplete_ContentFromFile"> <assertThat testDocument="actions/documentCloseAction.pdf"> <hasCloseAction> <matchingComplete filename="actions/documentCloseAction.js" whitespaces="IGNORE" /> </hasCloseAction> </assertThat> </testcase>
Das Attribut whitespaces=".."
ist optional.
Die Standard-Whitespace-Behandlung ist NORMALIZE
.
ImportData-Actions importieren Daten aus einer Datei. Sie benötigen den Dateinamen als Zusatzinformation:
<testcase name="hasImportDataAction_MatchingFilename"> <assertThat testDocument="actions/chainedActions.pdf"> <hasImportDataAction> <matchingComplete filename="build.xml" /> </hasImportDataAction> </assertThat> </testcase>
Es wird nur geprüft, ob die Aktion den erwarteten Dateiname enthält. Ob die Datei selber existiert, wird nicht überprüft.
Da JavaScript-Text gewöhnlich etwas umfangreicher ist, macht es Sinn, den Vergleichstext für eine JavaScript-Action aus einer Datei zu lesen:
<testcase name="hasJavaScriptAction_MatchingComplete_ContentFromFile"> <assertThat testDocument="javascript/bookmarkWithJavaScriptAction_OneSimpleAlert.pdf"> <hasJavaScriptAction> <matchingComplete filename="javascript/javascriptAction_OneSimpleAlert.js" /> </hasJavaScriptAction> </assertThat> </testcase>
Der vollständige Inhalt der JavaScript-Datei wird mit dem Inhalt der JavaScript-Action verglichen. Whitespaces werden dabei normalisiert.
Launch-Actions starten Anwendungen oder Skripte. Das kann getestet werden:
<testcase name="hasLaunchAction_Notepad_Print"> <assertThat testDocument="actions/launchActionToFile.pdf"> <hasLaunchAction> <toLaunch application="c:/windows/notepad.exe" operation="print" /> </hasLaunchAction> </assertThat> </testcase>
Während des Tests werden lediglich die Inhalte der Attribute application=".."
und operation=".."
mit denen der Launch-Action des PDF-Dokumentes verglichen.
Es wird nicht geprüft, ob die Applikation gestartet werden kann.
Named-Actions sollten auf ihren Operationsnamen hin überprüft werden:
<testcase name="hasNamedAction_WithName_NextPage"> <assertThat testDocument="actions/namedActionsNextPages.pdf"> <hasNamedAction> <withName> <matchingComplete>/NextPage</matchingComplete> </withName> </hasNamedAction> </assertThat> </testcase>
Goto-Actions benötigen ein Sprungziel in derselben Datei:
<testcase name="hasGotoAction_ToNamedDestination"> <assertThat testDocument="actions/bookmarksWithPdfOutline.pdf"> <hasLocalGotoAction> <toDestination name="destination2.1" /> </hasLocalGotoAction> </assertThat> </testcase>
Der Test ist erfolgreich, wenn das aktuelle Test-PDF das erwartete Sprungziel „destination2.1“ enthält.
GotoRemote-Actions benötigen ein Sprungziel in einer anderen Datei:
<testcase name="hasGotoRemoteActionTo_NamedDestination"> <assertThat testDocument="actions/gotoRemotePageAction.pdf"> <hasRemoteGotoActionTo file="destination.pdf" destination="destination-3" /> </assertThat> </testcase> <testcase name="hasGotoRemoteAction_ToPage"> <assertThat testDocument="actions/gotoRemotePageAction.pdf"> <hasRemoteGotoActionTo file="destination.pdf" page="4" /> </assertThat> </testcase>
Es wird lediglich überprüft, ob das Test-PDF-Dokument eine Aktion mit diesem Sprungziel besitzt. Es wird nicht geprüft, ob die Zieldatei bzw. das Sprungziel in der Zieldatei existiert.
Open-Actions werden beim Laden eines PDF-Dokumentes ausgeführt. Häufig sind es JavaScript- oder Goto-Actions.
<testcase name="hasOpenAction_MultipleInvocation"> <assertThat testDocument="actions/documentOpenAction_Print.pdf"> <hasOpenAction> <matchingRegex>(?ms).*print(.*)</matchingRegex> <matchingComplete>this.print(true);</matchingComplete> </hasOpenAction> </assertThat> </testcase>
Innerhalb des Tags <hasOpenAction />
können
Texte mit den üblichen Textvergleichs-Tags verglichen werden.
Darüber hinaus gibt es für Open-Actions noch das Tag
<withDestinationTo />
, mit dem das Ziel
der Open-Action geprüft wird:
<testcase name="hasOpenAction_GotoPage2"> <assertThat testDocument="actions/documentOpenAction_Goto.pdf"> <hasOpenAction> <withDestinationTo page="2" /> </hasOpenAction> </assertThat> </testcase>
Print-Actions sind JavaScript-Actions, die direkt vor oder direkt
nach dem Drucken ausgeführt werden. Sie sind innerhalb von PDF mit den
Events WILL_PRINT
oder DID_PRINT
verbunden.
<testcase name="hasPrintAction_WillPrint"> <assertThat testDocument="actions/documentPrintActions.pdf"> <hasPrintAction> <matchingComplete>app.alert('A sample for a WILL_PRINT-action');</matchingComplete> </hasPrintAction> </assertThat> </testcase>
Analog zu JavaScript-Actions wird der erwartete Inhalt der Print-Action nach einer Normalisierung der Whitespaces mit der erwarteten Zeichenkette verglichen.
ResetForm-Actions sind parameterlos, ein Textvergleich findet nicht statt. Es wird nur geprüft, ob eine solche Action existiert:
<testcase name="hasResetFormAction"> <assertThat testDocument="acrofields/javaScriptForFields.pdf"> <hasResetFormAction /> </assertThat> </testcase>
Save-Actions sind JavaScript-Actions, die direkt vor oder direkt
nach dem Speichern ausgeführt werden. Sie sind mit den
PDF-Events WILL_SAVE
bzw. DID_SAVE
verknüpft.
<testcase name="hasSaveAction_MultipleInvocation"> <assertThat testDocument="actions/documentSaveActions.pdf"> <hasSaveAction> <matchingComplete>app.alert('A sample for a DID_SAVE-action');</matchingComplete> </hasSaveAction> </assertThat> </testcase>
Auch hier wird der Vergleich erst nach einer Normalisierung der Whitespaces durchgeführt. Alle üblichen Tags zum Vergleichen von Texten stehen zur Verfügung.
SubmitForm-Actions benötigen ein Ziel, an das das Formular geschickt werden soll:
<testcase name="hasSubmitFormAction_ToUri"> <assertThat testDocument="acrofields/javaScriptForFields.pdf"> <hasSubmitFormAction> <withDestination toURI="http://www.geek-tutorials.com/java/itext/submit.php" /> </hasSubmitFormAction> </assertThat> </testcase>
PDFUnit prüft nicht die Existenz des Ziels, sondern nur die Gleichheit des Sprungziels mit der erwarteten Zeichenkette.
URI-Actions benötigen eine Ziel-URI:
<testcase name="hasURIAction"> <assertThat testDocument="actions/noBookmarks-manyActions.pdf"> <hasURIAction> <matchingComplete>http://www.imdb.com/</matchingComplete> </hasURIAction> </assertThat> </testcase>
Es findet kein Zugriff auf das Web statt, somit überprüft PDFUnit auch nicht, ob die URI existiert. Es wird nur geprüft, ob das Test-PDF eine URI-Aktion mit diesem Namen enthält.
Das folgende Beispiel zeigt mehrere Testfunktionen, angewendet auf ein Test-PDF, das die verschiedenen Aktionen auch enthält:
<testcase name="hasAnyAction_DifferentKindOfActions"> <assertThat testDocument="actions/chainedActions.pdf"> <hasAnyAction> <matchingComplete>app.alert('Demo: the first action of five.');</matchingComplete> <matchingComplete>http://www.google.de</matchingComplete> <matchingComplete>c:/windows/notepad.exe</matchingComplete> <matchingComplete>this.print(true);</matchingComplete> </hasAnyAction> </assertThat> </testcase>
Bei Textvergleichen kann die Behandlung der Whitespaces durch den Test gesteuert werden. Im folgenden Beispiel werden Zeilenumbrüche und Leerzeilen ignoriert:
<testcase name="hasCloseAction_Containing_ContentFromReader"> <assertThat testDocument="actions/documentCloseAction.pdf"> <hasCloseAction> <containing filename="actions/documentCloseAction.js" whitespaces="IGNORE" /> </hasCloseAction> </assertThat> </testcase>
Das Kapitel 13.4: „Behandlung von Whitespaces“ geht ausführlich auf den flexiblen Umgang mit Whitespaces ein.