Wiki > Grails-Kochbuch > Übergabe von Daten an WebTest

Problem

Es sollen Systemtests mit WebTest erstellt werden, wobei die zu testenden Datenkonstellationen nicht (effizient) über die Anwendung ermittelt werden können (z.B. mittels einer Suche).
Als erschwerende Hürde ist keine Testdatenbank verfügbar/zu nutzen, sondern es steht ausschließlich eine Echtdatenbank bereit.

Wichtiger Hinweis:
Wenn möglich sollte immer eine Möglichkeit gesucht werden, mit statischen/generierten Testdaten zu arbeiten. Dies sorgt für wesentlich stabilere Tests -- und es ist leichter, testgetrieben zu entwickeln (engl.: test driven development, TDD).

Beispielszenario

Als Beispiel soll hier eine Personendatenbank dienen, bei der neben den reinen Personendaten auch die Beziehungen zu anderen Personen notiert sein sollen.

Der Anwendungsfall enthält den folgenden Passus:

Die Anzeige einer Person (A) soll -- neben den reinen Personendaten -- noch folgende Informationen auflisten:

1) Steht die Person zu keiner anderen in Beziehung, so erscheint ein entsprechender textueller Hinweis.
2) Steht die Person zu genau einer anderen Person (B) in Beziehung, so sind die Personendaten von B rechts neben denen von A anzuzeigen.
3) Steht die Person mit mehreren Personen C1...Cn in Beziehung, so ist eine Liste der Namen C rechts von den Personendaten von A anzuzeigen.

Die zu erstellenden Tests für 1, 2 und 3 sind somit direkt davon abhängig, entsprechende Datenkonstellationen in der Datenbank zu kennen.

Kann der Test gegen eine Testdatenbank erfolgen, deren Daten z.B. in der Klasse BootStrap aufgebaut werden, so ist es trivial möglich, entsprechende IDs zu vergeben und den WebTest dementsprechend aufzubauen.

Leider gibt es jedoch gelegentlich den Fall, daß der Test zwingend gegen die Echtdatenbank ausgeführt werden muss und diese viele (mehrere Mio.) Datensätze enthalten kann. Damit entfällt die Möglichkeit, alle Datensätze iterativ abzuarbeiten.

Lösungen

Bitte beachten:
Beide Varianten senken die Wartbarkeit und führen möglicherweise zu instabilen Tests.

Variante A

Ein Ansatz ist der, einen Controller zu schreiben, der die entsprechenden Daten liefert. Die URL darf dann nicht öffentlich genutzt und muss zusätzlich noch mit Berechtigungen versehen werden.

Dies birgt jedoch grundsätzlich die Gefahr für Sicherheitslöcher.

Variante B

Ein anderer Ansatz ist, die benötigten Daten zu einem vorherigen Zeitpunkt -- im Rahmen der Integrationstest -- zu ermitteln und geeignet an den Systemtest zu übergeben. Dieser Ansatz wird im Folgenden beschrieben.

Zunächst wird ein Integrationstest benötigt, der die Testfälle aufsammelt und die IDs der Personentitäten in eine Property-Datei schreibt.

class EntityFinderTests
extends GroovyTestCase {

  void testFindeEntitaeten() {
    File file = new File("entity.properties")
    if (file.exists()) {
      file.delete()
    }
    file << "# automatisch generierte Datei\n\n"
    // Person-Id -- ohne Beziehung
    Person person = findePersonOhneBeziehung()
    file << "person.ohne=${person?person.id:-1}\n"
    ... u.s.w. ...
  }

}

Die Programmlogik der Klasse EntityFinderTests findet sich auch in Lösungsvariante A wieder, ist dort aber als "Fachlogik" getarnt, die sie nicht ist.
Werden z.B. die Tests genutzt um eine neue -- aber äquivalente -- Anwendung zu erstellen, wird der Entwickler gezwungen auch eben diese "Fachlogik" nachzubauen.

Die Funktionsweise der Methode findePersonOhneBeziehung() wird hier nicht näher erläutert. Der Name sollte jedoch hinreichend genau wiedergeben, daß mit einer geeigneten Datenbankanfrage eine Person ohne Beziehung ermittelt wird.
Für die anderen Fälle (exakt eine Beziehung und mehr als eine Beziehung) werden analoge Aufrufe benötigt.

Damit existiert nun im Hauptverzeichnis des Grails-Projektes die Datei entity.properties:

# automatisch generierte Datei

person.ohne=42
person.eine=4711
person.mehrere=-1

Jetzt wird eine WebTest-Klasse erzeugt, die über eine kleine Hilfsmethode getId(propname) verfügt:

class PersonTest 
extends grails.util.WebTest {
  private Properties defaultProps = null
  private final static String MUTEX = "PersonTestMutex"


/** Liefert die ID aus dem Property-Namen */
protected long getId(String propname) {
    synchronized(MUTEX) {
      if (defaultProps == null) {
        try {
          Properties props = new Properties()
          defaultProps.load(new FileInputStream("entity.properties"))
          defaultProps = props // <-- Wichtig, damit wir auf Mehrkernprozessoren keine Probleme im Multi-Threading bekommen. ;)
          println "entity.properties wurde erfolgreich gelesen"
        }
        catch (IOException ausnahme) {
          ausnahme.printStackTrace()
        }
      }
    }
    return Long.parseLong(defaultProps.getProperty(propname));
  }

  def testPersonOhneBeziehung() {
    if (getId('person.ohne')==-1) {
      throw new IllegalStateException("Es liegt keine Person ohne Beziehung im Datenbestand vor! Daher wird der Testfall übersprungen.")
    }
    webtest('Person ohne Beziehung'){
      invoke(url: "person/view/${getId('person.ohne')}")
      ... eigentlicher Test ...
    }
  }

}

Damit kann der Test auf den zuvor dynamisch ermittelten Datensätzen aufbauen. Im "regulären" Projektalltag sollten nun die Integrationstests vor den Systemtests ausgeführt werden, um sicherzustellen, daß die IDs weiterhin korrekt sind.

Diskussion

Dafür werden in Variante B die Tests voneinander abhängig, was TDD und Wartbarkeit erschwert. -- Stefan Roock

In beiden Varianten sind die Tests von den Methoden zum Auffinden benötigter Entitäten abhängig. In Variante A ist die Klasse EntityFinderTests lediglich als "Fachlogik getarnt" (als Controller). Ergo sind beide Varianten dbzgl. gleich gut oder gleich schlecht.
Lediglich in punkto Sicherheit hat Variante B einen kleinen Vorteil.
Zusätzlich finde ich, daß in die Anwendung nichts reingehört, was ausschließlich zum Testen genutzt wird. Was wiederum ein sauberes Design bedeutet. ;-)
Die Wartbarkeit leidet in beiden Fällen gleich stark. Aber dies liegt an der Problemstellung, daß eben keine statischen Testdaten genutzt werden können.
Es gibt eben nicht immer den Idealfall, daß sich alles testgetrieben entwickeln lässt. ;-)
Ich habe den Artikel bzgl. unserer hier aufgeführten Gedanken ergänzt. -- Marc Pompl

Add new attachment

Only authorized users are allowed to upload new attachments.
« This page (revision-1) was last changed on 05-Jun-2008 00:25 by unknown [RSS]