Abschlussarbeiten

Selenium Server zum Testen von SWT-Anwendungen

In dieser Arbeit geht es darum, einen Selenium Server zu entwickeln, mit dem es möglich ist, SWT-Anwendungen anzusteuern.

Selenium ist ein Test-Framework, das vor allem zum Testen von Web-Applikationen Verwendung findet. Zu diesem Zweck steht der sogenannte Selenium Server zur Verfügung, welcher über eine festgelegte Schnittstelle angesprochen werden kann und dann die Kommunikation mit dem Browser übernimmt. Für diesen Server stehen Clients in verschiedensten Programmiersprachen zur Verfügung. So gibt es mit der Hackage Bibliothek webdriver zum Beispiel auch eine Client-Implementierung in Haskell. Auf diese Weise können System Tests für Web-Anwendungen in Haskell formuliert und mit Hilfe des Servers mit verschiedenen Browsern ausgeführt werden.

Seit kurzem ist es auch möglich mobile Applikationen mit Hilfe von Selenium zu testen. Zu diesem Zweck stehen mehrere Server-Implementierungen (z.B. [appium]:(http://appium.io) und [Selendroid]:(http://selendroid.io)) zur Verfügung. Ein solcher Server implementiert für Clients die gleiche Schnittstelle wie der Selenium Server führt die entsprechenden Anweisungen aber mit dem iOS Simulator bzw. dem Android Emulator aus.

Inhalt dieser Abschlussarbeit ist die prototypische Entwicklung eines Selenium Servers zur Steuerung von SWT-Anwendungen, der es ermöglicht, mit dem GUI-Framework SWT entwickelte Desktop-Anwendungen zu testen. Zur Implementierung des Servers sollte die Programmiersprache Scala % oder die Programmiersprache Haskell genutzt werden. Zum Testen des Servers kann der Client frei gewählt werden, er sollte aber auf jeden Fall auch mit dem Haskell-Client getestet werden.

Der erste Schritt dieser Abschlussarbeit besteht in der Evaluation von Bibliotheken zum Ansteuern von SWT-Applikationen. Hier kommt vermutlich vor allem die Bibliothek SWTBot in Frage. Der zweite Schritt besteht dann in der Einarbeitung in die JSON Schnittstelle des Selenium Servers.

Generierung von mehrsprachigen Benutzerhandbüchern mit Hilfe von Selenium

In dieser Arbeit soll in der Programmiersprache Haskell eine Bibliothek zur Generierung von mehrsprachigen Benutzerhandbüchern für Web-Anwendungen entwickelt werden.

Benutzerhandbücher für Web-Anwendungen zu pflegen ist eine aufwändige Aufgabe, insbesondere wenn die Benutzerhandbücher in verschiedenen Sprachen zur Verfügung stehen müssen und die Web-Anwendung lokalisiert ist. Wenn die Web-Anwendung zusätzlich noch eine Kunden-spezifische Anpassung des Erscheinungsbildes erlaubt, kann die Pflege der Handbücher manuell nicht mehr bewälltigt werden.

In dieser Arbeit soll das Selenium Framework, das zum Testen von Web-Anwendungen zum Einsatz kommt, genutzt werden, um automatisiert Benutzerhandbücher zu generieren.

In einem ersten Schritt muss zu diesem Zweck eine domänenspezifische Sprache zur Spezifikation eines Benutzerhandbuchs entwickelt werden. Die Sprache muss berücksichtigen, dass die Text-Inhalte eines Handbuchs in mehreren Sprachen zur Verfügung stehen. Die domänenspezifische Sprache soll genutzt werden, um innerhalb einer Web-Anwendung Abläufe zu spezifizieren, welche dann mit Hilfe des Selenium Frameworks ausgeführt werden. Dabei sollen Bildschirmfotos von der Applikation erzeugt werden. Außerdem sollte es möglich sein, bestimmte Teile eines Bildschirmfotos hervorzuheben bzw. auszuschneiden. Das Ergebnis eines Programmlaufs sollte ein PDF-Dokument sein, das den spezifizierten Ablauf illustriert und mit einem Text erklärt.

RxSWT

In dieser Arbeit soll die UI-Bibliothek SWT mit dem Signal-Framework RxScala verbunden werden, um reaktive UIs in Scala zu unterstützen.

Mit dem in Java entwickelten Standard Widget Toolkit existiert ein ausgereiftes Open-Source-Projekt für native GUIs in Java. Dies lann zwar direkt in Scala-Programmen verwendet werden, nutzt aber die Eigenschaften von Scala nicht. Ein Ziel der Arbeit ist es, Abstraktionen für SWT zu definieren, die das Erstellen von UIs in Scala vereinfachen.

Für das Event Handling soll dabei das Observer Pattern durch Signale ersetzt werden. Reaktive UI-Programmierung mit Signalen ist derzeit im Trend, sowohl auf mobilen Endgeräten als auch auf Webseiten. Listener werden nicht an Steuerelementen registriert, sondern Steuerelemente stellen Signale bereit, deren Werte in neue Signale transformiert, mit den Werten anderer Signale kombiniert oder von anderen Komponenten weiterverarbeitet werden.

Zwei Textfelder für Vor- und Nachname, deren Inhalt zu einem Label für den Namen kombiniert werden sollen, lassen sich prägnant implementieren:

newLabel(parent, text = "First name:")
val firstNameField = newText(parent)

newLabel(parent, text = "Last name:")
val lastNameField  = newText(parent)

newLabel(parent, text = "Full name:")
val fullNameLabel  = newLabel(parent)

val fullNameSignal = 
  for ((fn, ln) <- firstNameField.text.combineLatest(lastNameField.text)
    yield s"$fn $ln"

fullNameLabel.text(fullNameSignal)

Die Textfelder stellen jeweils Signale text: Observable[String] bereit, die über den Kombinator combineLatest und der Kontaktenation zu einem neuen Observable[String] kombiniert werden. Der Inhalt des Ergebnis-Labels wird an dieses Signal gebunden.

Dasselbe Programm mit SWT/Java hat diverse Nachteile.

Label label1 = new Label(parent, SWT.LEFT);
label1.setText("First name:");
final Text firstNameField = new Text(parent, SWT.BORDER);

Label label2 = new Label(parent, SWT.LEFT);
label2.setText("Last name:");
final Text lastNameField = new Text(parent, SWT.BORDER);

Label label3 = new Label(parent, SWT.LEFT);
label3.setText("Full name:");
final Label fullNameLabel = new Label(parent, SWT.BORDER);

ModifyListener sharedListener = new ModifyListener {
  public void modifyText(e: ModifyEvent) {
    fullNameLabel.setText(firstNameField.getText() + " " + lastNameField.getText());
  }
}

firstNameField.addModifyListener(sharedListener);
lastNameField.addModifyListener(sharedListener);

Der Listener muss bei beiden Textfeldern registriert werden. Der Inhalt des Labels für das Ergebnis wird imperativ gesetzt. Der Inhalt könnte auch durch andere Programmkomponenten gesetzt werden, z.B. durch einen anderen Listener. Wie sich der Inhalt des Ergebnis-Labels zusammensetzt ist nicht unmittelbar erkennbar, insbesondere wenn das Programm erweitert wird. Die Umsetzung der hier noch überschaubaren State Machine wird dem Programmierer überlassen. Das Programm wird damit fehleranfälliger und schlechter wartbar. Die Komplexität steigt mit der Anzahl der involvierten Listener. Während für eine Drag&Drop-Komponenten mit dem Observer Pattern drei Listener notwendig sind und die State Machine selbst implementiert werden muss, kann in der reaktiven UI-Programmierung einfach ein prägnantes Signal definiert werden:

for (startPoint <- control.mouseDown;
     endPoint <- control.mouseMove.takeUntil(control.mouseUp))
 yield (startPoint, endPoint)

Die entwickelte API sollte in kleinen Programmen getestet werden, die typische Problemstellungen der UI-Programmierung abbilden. Anhand der Programme sollen die Vorteile der reaktiven UI-Programmierung gegenüber dem Observer Pattern dargestellt werden.

Typsichere SQL-Anfragen in Scala

In dieser Arbeit sollen SQL-Queries typsicher in Scala eingebettet werden.

In der Master Thesis von Christoph Wulf wurde ein Ansatz vorgestellt, SQL-Abfragen über XML-Präprozessoranweisungen in Scala einzubetten und durch den Scala-Compiler typen zu lassen. Der Ansatz von 2012 hat diverse Nachteile: Es ist ein Compiler-Plug-in notwendig. Das Datenbankschema muss als Argument dem Compiler übergeben werden. Es gibt keine Abbildung von Relationen auf Scala-Objekte. Die eingebetteten Anfragen werden nur transformiert, d.h. das Compiler-Plug-in prüft nur auf Syntaxfehler in Anfragen. Die Typprüfung der Anfragen erfolgt durch den Scala Compiler, was zu sehr obskuren Fehlermeldungen führt. Darüber hinaus können sehr einfach Constraints im Datenmodell nicht im inferierten Typ eines Statements berücksichtigt werden.

Seit Version 2.10 unterstützt Scala sowohl String Interpolation als auch Makros. Beide Konzepte können kombiniert werden, um die Einbettung von SQL-Anfragen zu optimieren. Bessere Typprüfung und -inferenz sowie bessere Nutzung des Frameworks können realisiert werden.

Die Einbettung über String Interpolation fügt sich viel besser in Scala-Programme ein:

sql"SELECT id FROM User" // : BagStatement[Long]

Über die String Interpolation lassen sich direkt Scala-Ausdrücke in Abfragen einbetten, deren Typen bei der Typprüfung der Anfrage durch das Makro schon fest stehlen. Die Ergebnisse der Ausdrücke lassen sich an ein PreparedStatement übergeben, um Anfragen zu beschleunigen und SQL Injection zu vermeiden.

sql"SELECT id FROM User WHERE id > ${name + 1}"
// => Error: Can't compare BIGINT (mapped to Long) with VARCHAR (mapped to String)

Allgemein verhindert die String Interpolation ungewollte SQL-Injection, da Anfragen nicht über String-Konkatenation erstellt werden können. Eine Notation, um dynamische Teile einer Anfrage zu generieren, ist dennoch denkbar.

Die Abfragen müssen nicht gegen ein globales Schema validiert werden. Es ist denkbar, die Relationen der Datenbank auf case classes abzubilden. Das erlaubt eine Projektion von *, die Objekte der Relationsklasse liefert. Andere Projektionen können weiterhin auf Tupel oder einzelne Werte abgebildet werden.

case class User(id: Long, emailAddress: String, firstName: String, lastName: String)

sql"SELECT * FROM User" // : BagStatement[User]
sql"SELECT emailAddress, CONCAT(firstName, ' ', lastName) FROM User"
// : BagStatement[(String, String)]

Die case classes können mit weiteren Informationen über das Datenbankschema angereichert werden, um die Typen der Anfragen zu optimieren. So kann geprüft werden, ob eindeutige Schlüssel gebunden sind oder eine 1:n-Beziehung selektiert wird.

case class User(
  @primaryKey userId: Long,
  @uniqueKey emailAddress: String,
  firstName: String,
  lastName: String)

case class Activity(
  @foreignKey(User) userId: Long,
  time: Date,
  timeZone: TimeZone,
  description: String)

sql"SELECT * FROM User WHERE emailAddress = 'cwulf@clinical-registry.com'"
// : Statement[Option[User]]

sql"SELECT * FROM User LEFT NATURAL JOIN Role ORDER BY emailAddress, time"
// : BagStatement[(User, List[Activity])]

Bei der Implementierung kann die Syntaxanalyse aus der Master Thesis von Christoph Wulf übernommen werden. Beim Entwurf der API sollten auch Unterschiede zwischen Anbietern von RDBMS berücksichtigt und eine Erweiterbarkeit ermöglicht werden. Zum Beispiel ist es denkbar neben einem Interpolator sql für den SQL-Standard auch anbieterspezifische Interpolatoren wie psql, tsql, mysql anzubieten und weitere zu erlauben.