Heutzutage müssen wir als Entwickler nicht nur qualitativ hochwertigen Programmcode entwickeln, sondern auch für eine ausreichende Testabdeckung sorgen. Hierzu gibt es eine Vielzahl von Möglichkeiten dies zu tun, die alle Ihre Vor- aber leider auch Nachteile haben. Dieser Artikel beschreibt einen neuen Ansatz, der es uns möglich macht, zum einen schnell erfolgreich zu testen und zum anderen alle Stärken des Oracle Application Express (APEX) zu instrumentalisieren. 

Status Quo

Viele Entwickler beantworten die Frage nach der aktuell eingesetzten Teststrategie mit einem Satz in etwa wie: „Der Fachbereich bekommt den Zugriff und gibt uns dann ein ‚okay‘ oder ‚nicht okay‘.“. Im Gegensatz zu dem reinen Abnahmetest durch den Fachbereich gibt es in hochkritischen Anwendungen umfangreiche Test-Suites, die in mühevoller Arbeit entwickelt wurden. Häufig wird dabei auf existierende Frameworks zurückgegriffen, die mit Ihrer eigenen Sprache und natürlich auch Vor- und Nachteilen daherkommen. Häufig hören wir an dieser Stelle Produktnamen wie Selenium, Ranorex, Cucumber oder Katalon. Unbestritten kann man sagen, dass es sich bei den genannten Produkten um sehr mächtige Werkzeuge handelt, denen es aber genau an einer Sache mangelt: der direkte Bezug zum Oracle Application Express.

Die Metadaten anhand derer eine APEX-Anwendung aufgebaut wird, befinden sich, sauber strukturiert, in der Datenbank und stellen uns eigentlich eine super Basis für unsere Testkriterien dar. Dazu aber später mehr.

Was ist die Grundlage für erfolgreiche Tests?

In einem Lastenheft oder einer Story im Backlog eines Scrum-Projekts steht im Regelfall immer eine Textpassage, die beschreibt, welcher Sachverhalt erfüllt werden muss, damit eine Funktion oder ein Feature als erfolgreich entwickelt gilt. Dazu gehört unter anderem auch eine Beschreibung dessen, was auf der Oberfläche der Anwendung zu sehen bzw. nicht zu sehen ist. Ebenso wird beschrieben, was passieren soll, wenn eine Aktion vorgenommen wurde.

Dies stellt dann die Grundlage für die Entwicklung eines Features, aber auch für die Entwicklung von Testszenarien dar. Der Entwickler plant, setzt das angeforderte um und führt zunächst einen „Entwickler-Smoke-Test“ durch, der im groben die Funktionalität des Entwickelten überprüft. Kurz gesagt: Das Artefakt wird entsprechend der Interpretation der Story vom Entwickler getestet und dann für weitere Tests freigegeben. Das was nun noch fehlt ist genau das, was ein „Projekt“ zu einem „erfolgreichen Projekt“ machen kann. Automatisierte Tests, die im besten Fall regelmäßig ausgeführt werden und somit die Integrität und die Fehlerfreiheit einer Anwendung dauerhaft gewährleisten kann.

Die beliebtesten Frameworks, mit denen heutzutage getestet wird

In den meisten Fällen setzen Unternehmen auf ein Framework, das bereits seit langer Zeit existiert – wie zum Beispiel Selenium. Selenium und Selenium Grid bieten ein weites Spektrum an unterstützten Browsern an, die vorher aufgenommene Tests ausführen können. Hierbei wird üblicherweise ein Browser-Plugin installiert, das Schritt für Schritt aufnimmt, welche Aktivitäten im Browser durchgeführt werden. Dies dient im Anschluss als Drehbuch für Tests, die dann automatisch ausgeführt werden.

In vielen Fällen steht auch kein Browser-Plugin zur Verfügung, da das Testframework dies nicht unterstützt. In diesen Fällen werden manuell Tests programmiert, die mehr als nur zeitaufwändig sind. Wer in seinem Projekt für eine lückenlose Testabdeckung sorgen will, muss einen Teil der Entwicklungszeit aufschlagen, um Testfälle sorgfältig aus zu programmieren.

Neben diesem sehr bekannten und häufig genutzten Framework haben in den vergangenen Jahren immer wieder neue Frameworks das Licht der Welt erblickt, die alle ihre Daseinsberechtigung haben, jedoch alle wiederum nur begrenzt sinnvoll für den Einsatz mit APEX sind.

Was fehlt uns und was brauchen wir?

Die genannten Frameworks können alle genutzt werden, um Web- und Mobile-Anwendungen zu testen. Sie haben jedoch auch alle ihre Eigenheiten. Dazu gehört zum Beispiel die Syntax, die verwendet wird, um sequenzielle Abläufe zu programmieren. Im Regelfall muss man für jedes dieser Frameworks einen Aufwand betreiben, um es auf APEX zuzuschneiden. Häufig muss man dabei auch auf Funktionalitäten verzichten, die sich im Kontext von APEX nicht abbilden lassen. Dazu kommt, dass der Autor der Tests einen Sachverhalt inklusive aller daran beteiligten Komponenten niederschreibt und somit ein relativ starres Objekt ablegt. Um genau dieses „Starre“ aus unseren Tests herauszubekommen und vor Allem von einem immensen Zeitersparnis zu profitieren, sollten wir uns eine der Stärken von APEX zunutze machen: Die Metadaten!

Alle Informationen, die eine APEX-Anwendung ausmachen, befinden sich in der Datenbank und somit können wir beispielsweise einfach ermitteln, welche Komponenten sich auf einer APEX-Seite befinden und diese für unsere Tests verwenden. Mit Hilfe von mehr oder weniger einfachen Select-Statements, können wir alle nötigen Meta-Informationen ermitteln und für eine deklarative Erstellung von Testfällen zur Verfügung stellen.

Das Metadaten-Modell von APEX ist umfangreich und gut strukturiert, sodass wir stets auf den „Ist-Zustand“ einer Anwendung schauen.

Automatisiertes Ausführen von automatisiert erstellten Tests

So exotisch wie der Titel dieses Absatzes war auch die Idee, die hinter einem Tool steckt, das in diesem Artikel vorgestellt werden soll. Wir machen uns die Nachteile der bekannten Test-Frameworks und die Vorteile von APEX zu Nutze und entwickeln etwas Großes! Mit einer Handvoll neuer Technologien und vielen bekannten Faktoren vereinfachen wir die Schritte, die uns sonst viel Zeit kosten. Nach ersten Gehversuchen mit „Puppeteer“, dem Framework für automatisierte Tests im Google Chrome Browser und diversen Tests mit CodeceptJS, haben wir etwas Neues geschaffen. Mit Hilfe von Node.js und dem Templating Framework „Handlebars“ kann dynamisch ein Test-Code für User Acceptance Tests erzeugt werden, der wiederum in vielen Umgebungen unter verschiedenen Voraussetzungen ausgeführt werden kann. Um es dann noch abzurunden, kann mittels Docker für die angemessene Unterbringung der Testumgebung gesorgt werden.

Das Tool

Abbildung 1: Die Übersicht

Den Einstieg in die Anwendung, die uns das einfache Erstellen von Tests ermöglicht, erhalten wir über die Übersichtsseite, die den Aufbau von den so genannten Test-Worksheets verdeutlicht. Mit Hilfe eines Worksheets werden mehrere Test-Cases gruppiert und als eine Art Arbeitsmappe für eine zu testende Anwendung gesehen werden. Innerhalb eines Test-Cases werden wiederum Test-Schritte (Steps) angeordnet. Dies wird gleich näher erläutert, wenn wir einen Blick unter die Haube werfen.

Der Test-Designer

Abbildung 2: Der Test-Designer

Mit dem Test-Designer sind wir in der Lage alle Komponenten einer zu testenden APEX-Seite auf einen Blick zu sehen und für eine Anwendung auszuwählen. Hierzu wird, wie in der Abbildung zu sehen ist, ausgewählt, welche Komponenten relevant für diesen Test sind. In der ersten Spalte dieses Grids wird hierzu „Test Relevant“ auf „Yes“ gesetzt. Hinzukommen werden zum einen weitere Meta-Daten für die jeweilige Komponenten und zum anderen wird die Möglichkeit gegeben, Eingabefelder mit entsprechenden Daten zu versehen. So wählen wir in dem oben gezeigten Beispiel ein Textfeld aus, das den technischen Namen „P45_PROJ_NAME“ besitzt und geben in das Feld „Item-Value“ einen Text-String ein, der innerhalb dieses Tests verwendet werden soll. In der weiteren Entwicklung ist vorgesehen, dass sich diese Felder auch mehrfach belegen lassen, sodass sich mehrfache Tests pro Komponente einfach erstellen lassen.

Da erfahrene Entwickler stets dazu raten für jede Komponente eine so genannte „Static ID“ zu vergeben, kann die Engine, die unsere Tests generiert, diese verwenden, um eine Komponente eindeutig zu identifizieren. Aber keine Angst – sollten Sie diese „Static ID“ nicht auf jeder Komponente hinterlegt haben, so wird im Test-Designer, die jeweilige ID angezeigt, die APEX selbst vergeben hat. Das kann eine kryptische Zeichenfolge sein, kann aber auch ein logisch vorbelegter Name sein, wie es beispielsweise bei Textfeldern und einigen anderen Komponenten in APEX der Fall ist. Aber Vorsicht: Automatisch erzeugte IDs für Elemente können sich je nachdem, wie Sie die Anwendung exportieren bzw. importieren, ändern!

Ist ein Test-Case im Test-Designer erstellt und gespeichert worden, so wird anhand dieser Eingaben und ausgewählten Komponenten dafür gesorgt, dass ein automatisiertes Erstellen der Tests möglich wird. Hierzu benötigen wir das so genannte Manifest.

Das Manifest

Eine wichtige Rolle für die Definition von Test-Skripten, ist ein Manifest, das so abstrakt ist, dass es alle Informationen für die auszuführenden Tests beinhaltet, jedoch so generisch ist, dass es automatisiert weiterverarbeitet werden kann. Zu den enthaltenden Informationen gehören auch, die im letzten Absatz genannten APEX-Items. Ein Manifest kann folgendermaßen aussehen:

{
    "base_url": "http://apex.kai-donato.de/ords/f?p=",
    "test_project_name": "SampleApplication123",
    "login": {
        "needed": 1,
        "login_pageid": 101,
        "usr_fn": "scott",
        "pwd_fn": "secret"
    },
    "app_id": 103,
    "steps": [{
        "onpage": 1,
        "isModalPage": 0,
        "type": "fillForm",
        "items": ["P1_SEARCH"],
        "item_sub_type": ["NATIVE_TEXT_FIELD"],
        "values": ["Bug"]
    }]
}

Listing 1: Das Manifest

In diesem Manifest sehen wir zum einen Bestandteile, die immer vorhanden sind, wie zum Beispiel die URL bei der ein Test starten soll oder auch Informationen darüber, wie und mit welchen Daten sich in der Anwendung angemeldet werden soll. Darüber hinaus befindet sich aber auch der Punkt „steps“ auf der obersten Ebene. Innerhalb dieses Attributs „steps“ befindet sich eine Sammlung aus generischen Objekten, die dann die einzelnen Testschritte beschreiben. So können wir beispielsweise den Typ eines Testschritts dafür nutzen, um festzustellen, welche Interaktion im Laufe des Tests erfolgen soll. Dies ist notwendig für die Übersetzung in die einheitliche Testsprache, die wir im folgenden Absatz über CodeceptJS kennenlernen werden. Ebenfalls innerhalb eines Testschritts befindet sich, sofern nötig, eine Auflistung von beteiligten Elementen. Bei dem oben gezeigten Manifest ist beispielsweise das Befüllen eines Eingabefeldes gefordert und demnach ein Element (P1_SEARCH) und der einzugebende Text-String (Bug) hinterlegt. Zusammen mit dem Typ des Testschritts (fillForm) ist nun alles bekannt, um einen entsprechenden Testschritt zu erzeugen. Und das übrigens nicht nur einmalig, sondern beliebig häufig, wenn sich etwas an den Test-Templates ändert. Nehmen wir also an, dass ein Test auf Grund von Änderungen in einer neuen Version von APEX nicht mehr zuverlässig funktioniert, so muss lediglich das Test-Template so modifiziert werden, dass es eine Komponente gemäß der neuen Funktionsweise testen kann. Anschließend werden die entsprechenden Tests anhand der existierenden Manifest-Dateien erneut generiert und können dann, wie gewohnt, ausgeführt werden.

CodeceptJS

Mit einem Framework wie „CodeceptJS“ lassen sich die zu Beginn des Artikels genannten Nachteile der Testframeworks aus der Welt schaffen und mit einer einheitlichen Syntax viele Test-Engines für die Ausführung unserer Skripte nutzen. Das folgende einfache Test-Skript verdeutlicht, wie einfach der CodeceptJS-Syntax selbst zu lesen ist und nach welchem Schema er aufgebaut ist:

Feature('Sample Application Login and Search');
 Scenario('Main-Szenario', (I) => {
    I.amOnPage('http://apex.kai-donato.de/ords/f?p=103');
    I.waitForElement('#P101_USERNAME',5);
    I.waitForElement('#P101_PASSWORD',5);
    I.appendField('#P101_USERNAME','scott');
    I.appendField('#P101_PASSWORD','secret');
    I.click('Sign In');
    I.wait(10);
    I.dontSeeElement('.t-Alert-wrap');
    I.saveScreenshot('Auth26-05-2019.png');
    I.waitForElement('#P1_SEARCH', 5);
    I.appendField('#P1_SEARCH','Bug');
    I.saveScreenshot('fillFormstep-026-05-2019.png');
});

Listing 2: CodeceptJS-Code

Dieses Test-Skript ist nicht nur leicht zu lesen, es ist auch im Aufbau so konzipiert worden, dass es sich unter anderem auch mit den gängigen Treibern ausführen lässt. Hierzu gehört zum Beispiel:

  • WebDriver
  • Puppeteer
  • Protractor
  • Appium

Diese so genannten Treiber oder auch Engines genannt stellen die Basis für das Testen im Google Chrome, Mozilla Firefox, Apple Safari, Microsoft Internet Explorer, Microsoft Edge oder sogar mobilen Geräten dar.

Wer sich das Testskript etwas genauer ansieht, kann dort einiges bereits Bekanntes erkennen. Unter anderem sind dort die Namen von APEX-Items, sowie APEX-spezifische CSS-Klassen zu sehen. Diese kommen aus den Metadaten unserer APEX-Anwendung, doch wo werden diese definiert?

Ausführung der Tests

Die Ausführung der generierten Tests ist zum Zeitpunkt dieses Artikels auf drei Arten möglich. Zum einen können die Tests in einem Docker-Container ausgeführt werden, der sich innerhalb der eigenen Infrastruktur befindet. Hierbei wird ein vorbereitetes Docker-Image von CodeceptJS verwendet, das bereits alle notwendigen Treiber und Softwarepakete beinhaltet. Hierbei wird beispielsweise ein „Headless Chrome“ gestartet, welches nicht mehr und nicht weniger als ein vollfunktionsfähiger Google Chrome Browser ist, mit der Besonderheit, dass es keine grafische Oberfläche gibt, die der Benutzer betrachten kann. Was jedoch möglich ist, ist die Erstellung von Screenshots, die uns hinterher Aufschluss darüber geben, was in dem Browser bei dem jeweiligen Testschritt angezeigt wurde. Das hat zur Folge, dass der Docker-Container ohne die nötigen Ressourcen eines Arbeitsplatz-Computers auskommt und dazu auch noch vollkommen unabhängig des Host-Systems läuft. Somit kann für jeden Test ein neuer „frischer“ Container gestartet werden, der sich immer in derselben Ausgangssituation befindet. Dieser Docker-Container kann aber auch dafür genutzt werden, die Tests weiter zu orchestrieren. Nehmen wir an, wir besitzen ein Selenium Grid, welches bereits eigenständig aufgesetzt wurde und eine Vielzahl an Browsern bereitstellt. In der Definition unserer Tests können wir darauf verweisen, dass der jeweilige Test nicht im Container selbst ausgeführt werden soll, sondern alles Nötige an das Selenium Grid weitergibt und eine Ausführung von dort an im Grid stattfinden soll. Alle relevanten Testergebnisse und auch alle Screenshots landen nach der Ausführung des Tests wieder im Docker-Container und werden vor dem Beenden des Prozesses wie gewohnt an APEX weitergeleitet und auf dem Dateisystem abgelegt.

Abbildung 3: Blick auf das Dateisystem nach der Ausführung eines Tests

Da es sich in erster Linie um User Acceptance Tests handelt, kann abgesehen von den Tests innerhalb der eigenen Infrastruktur, von jedem Ort getestet werden, von dem die zu testende Anwendnung erreichbar ist. Hierzu können wir mit Hilfe des Anbieters „Browserstack“ testen. Hierbei wird anstelle des im Container befindlichen Browsers oder des Selenium Grids, ein externer Dienst aufgerufen, der die Tests dann in unterschiedlichen Browsern auf diversen Plattformen ausführt. Hierbei verlassen dann unsere Test-Skripte inklusive möglicher Login-Informationen die eigene Infrastruktur.

Abbildung 4: Auszug aus den verfügbaren Browsern auf Browserstack.com

Ich denke zu all diesen Möglichkeiten bieten sich Anwendungsfälle. Sicherlich ist das Testen innerhalb der eigenen Infrastruktur häufiger von Vorteil und mit Sicherheit auch häufiger nötig. Es gibt jedoch auch die Fälle, in denen man davon profitieren kann, dass Tests von außerhalb ausgeführt werden. Eine Vielzahl an Betriebssystemen und Browsern stehen zur Verfügung, um eine möglichst hohe Testabdeckung zu ermöglichen. Die Server befinden sich allerdings, wie bereits gesagt, nicht innerhalb der eigenen Infrastruktur und sollten gerade im Zusammenhang mit sensiblen Daten mit Vorsicht genutzt werden.

Die Queue

Egal welche der oben genannten Ausführungsoptionen man verwendet, landen alle Testergebnisse wieder in unserer Anwendung und können dort ausgewertet werden.

Abbildung 5: Ausgeführte und laufende Tests werden übersichtlich in der Queue dargestellt.

Nachdem ein Test für die Ausführung bestimmt oder automatisch gestartet wurde, taucht ein neuer Eintrag in der Test-Queue auf und zeigt den Status dessen in Echtzeit an. Nach Abschluss des Tests kann eingesehen werden, welche Dateien erzeugt wurden und sowohl die Log-Ausgaben als auch erstellte Screenshots angezeigt werden.

Module

Der deklarative Ansatz dieser Lösung steht klar im Vordergrund. Der Endbenutzer soll, wie er es in APEX gewohnt ist, auf deklarative Art und Weise Tests erstellen und sich wenig wie möglich mit den technischen Einzelheiten, die im Hintergrund passieren, auseinandersetzen. Durch den modularen Aufbau der Engine im Hintergrund kann in Form von Templates dafür gesorgt werden, dass spezielle, im Projekt angeforderte, Testroutinen einmalig ausprogrammiert werden und anschließend deklarativ in der Oberfläche des Tools verwendet werden. Dies ist zum einen Aufwand, der projektspezifisch geleistet werden muss, jedoch auch die Flexibilität, die man von einem Tool erwarten sollte. So ist es auch möglich, die im Framework enthaltenen Oberflächentests um Integrations- und Modultests zu erweitern.

Fazit

Aus einer Idee und vielen Experimenten konnte eine Lösung geschaffen werden, die uns in Zukunft viel Zeit sparen kann. Als APEX-Entwickler haben wir die deklarative Arbeit schätzen und lieben gelernt und sind es gewohnt mit wenigen Klicks ein tolles Ergebnis zu schaffen. Das vorgestellte Tool soll eine weitere Lücke schließen und den deklarativen Ansatz auch für das Thema Testing ermöglichen. Es gibt viele Features, die sich in der Pipeline befinden und so wird beispielsweise auch das visuelle Testen in Form von Screenshot-Vergleichen Einzug in diese Lösung finden. Zu guter Letzt bin ich gespannt darauf, was Sie zu diesem Thema zu sagen haben und wie diese Lösung bei Ihnen eingesetzt werden kann/könnte!

Abbildung 6: Schematische Darstellung des Test-Prozesses

Dieser Artikel wurde in der Ausgabe 4/2019 im DOAG RedStack Magazin veröffentlicht.

Jetzt teilen auf:

Jetzt kommentieren