Blog

Middleware-Integrationstests mit JUnit, Maven und VMware, Teil 2 (von 3)

Vincent Partington

Aktualisiert Oktober 23, 2025
9 Minuten

Letzte Woche habe ich über den Ansatz geschrieben, den wir bei Xebia verwenden, um die Integrationen mit den verschiedenen Middleware-Produkten zu testen, die unser Java EE-Bereitstellungsautomatisierungsprodukt Deployit unterstützt.

Der Blog endete mit dem Versprechen, dass ich erörtern würde, wie man testet, ob eine Anwendung die Konfigurationen, die unsere Middleware-Integrationen (auch bekannt als Steps) erstellen, auch wirklich verwenden kann. Doch bevor wir uns damit befassen, lassen Sie uns zunächst die Frage beantworten, warum wir das brauchen. Wenn der Code eine Datenquelle ohne den Anwendungsserver konfigurieren kann, muss es doch für eine Anwendung in Ordnung sein, sie zu verwenden, oder? Nun, nicht immer. Während WebSphere und WebLogic einige Funktionen enthalten, um die Verbindung zur Datenbank zu testen und damit zu überprüfen, ob die Datenquelle korrekt konfiguriert wurde, steht diese Funktion für andere Konfigurationen wie JMS-Einstellungen nicht zur Verfügung. Und JBoss hat überhaupt keine solche Funktionalität. Die Frage ist also: Wie können wir nachweisen, dass eine Anwendung mit den durch unsere Schritte erstellten Konfigurationen auch wirklich funktionieren kann?

Geben Sie die große alte Testanwendung ein

Als erstes haben wir versucht, eine Testanwendung zu schreiben, die die gesamte Funktionalität eines Java EE-Anwendungsservers nutzt. So etwas wie die Java EE Pet Store-Anwendung. Wir starteten also Eclipse WTP und schrieben eine Reihe von Servlets/JSPs/Spring-Controllern, die Dinge wie die Abfrage einer Tabelle, das Einstellen einer Nachricht in eine Warteschlange und deren Abruf usw. erledigen. Wir packten diese Anwendung in eine EAR und stellten sie auf unserem Zielanwendungsserver bereit und prüften, ob sie korrekt funktioniert.

Wir könnten dies zu einem Teil unserer kontinuierlichen Integration machen, indem wir einen JUnit-Test schreiben, der diese Anwendung auf jedem der Anwendungsserver bereitstellt und dann die Anwendung durch Anfordern verschiedener URLs testet. Bei dieser Lösung gibt es jedoch einige Probleme:

  1. Jedes Mal, wenn Sie die Testanwendung ändern, müssen Sie sie neu erstellen und die daraus resultierende EAR-Datei in das Verzeichnis der Testressourcen(src/test/resources/...) für das Middleware-Integrationsprojekt legen.
  2. Für alle drei unterstützten Anwendungsserver gibt es nur eine Testanwendung: IBM WebSphere Application Server, Oracle WebLogic Server und JBoss Application Server. Aber die Tests, die Sie für jeden Server durchführen können, sind leicht unterschiedlich. WebSphere bietet beispielsweise Warteschlangen für einen integrierten Service Integration Bus, für WebSphere MQ und für normale JMS-Anbieter. Man könnte zwar drei verschiedene Testanwendungen erstellen, aber das macht die Sache nur noch komplizierter.

Das Grundproblem ist, dass diese Testanwendung und der andere Teil des Tests (der Teil, der in Teil 1 dieser Serie beschrieben wurde) an verschiedenen Orten gespeichert sind, wie hier. Würden die Dinge nicht viel besser funktionieren, wenn diese Teile des Codes zusammen wären?

Wäre es nicht schön, wenn wir diese Testanwendungen im Handumdrehen erstellen könnten? Mit dem kleinen Stückchen Testcode, das wir auf dem Anwendungsserver in seinem Java EE-Kontext ausführen möchten? Ja? Nun, ich hatte gehofft, Sie würden mir zustimmen, denn das ist genau das, was die OnTheFly-Bibliothek, die wir geschrieben haben, tut. ;-)

JAR-Dateien im laufenden Betrieb erstellen

Als Erstes müssen wir sicherstellen, dass wir eine JAR-Datei im Handumdrehen erstellen können. Wenn wir das geschafft haben, können wir WAR-Dateien und EAR-Dateien erstellen. Dies ist der Code für unsere Klasse JarOnTheFly:

import java.io.*;
import java.util.*;
import org.apache.commons.io.IOUtils;
import org.springframework.core.io.Resource;
public class JarOnTheFly {
  private Map files = new HashMap();
  public void addFile(String filename, Ressource resource) {
  files.put(Dateiname, Ressource);
  }
  public void write(File jarFile) throws IOException {
  FileOutputStream jarFileOut = new FileOutputStream(jarFile);
  versuchen {
  JarOutputStream jarOut = new JarOutputStream(jarFileOut);
  versuchen {
  for (Map.Entry eachFile : files.entrySet()) {
  String filename = eachFile.getKey();
  Ressource resource = eachFile.getValue();
  jarOut.putNextEntry(new JarEntry(filename));
  InputStream resourceIn = resource.getInputStream();
  versuchen {
  IOUtils.copy(resourceIn, jarOut);
  } finally {
  IOUtils.closeQuietly(resourceIn);
  }
  jarOut.closeEntry();
  }
  } finally {
  IOUtils.closeQuietly(jarOut);
  }
  } finally {
  IOUtils.closeQuietly(jarFileOut);
  }
  }
  protected File writeToTemporaryFile(String prefix, String suffix) throws IOException {
  File tempJarFile = File.createTempFile(prefix, suffix);
  write(tempJarFile);
  return tempJarFile;
  }
}

Sie können eine Instanz dieser Klasse erstellen, ihr mit der Methode addFile Dateien hinzufügen und sie dann in eine bestimmte Datei oder in eine temporäre Datei schreiben. Da Sie jede Klasse übergeben können, die die Spring Ressource implementiert, können Sie jede Art von Inhalt in die JAR-Datei aufnehmen: normale Dateien, Dateien aus dem Klassenpfad, speicherinterne Byte-Arrays und sogar Eingabeströme.

WAR-Dateien im laufenden Betrieb erstellen

Die Klasse WarOnTheFly baut auf der Klasse JarOnTheFly auf und fügt eine Reihe von WAR-spezifischen Funktionen hinzu. Zunächst einmal kann eine minimale WEB-INF/web.xml erstellt werden. Wenn Sie die Methode writeToTemporaryFile aufrufen, wird dies automatisch durchgeführt und die Erweiterung wird automatisch auf .war gesetzt.

Zweitens und vor allem können Sie jede Klasse in Ihrem aktuellen Klassenpfad als Servlet in die WAR-Datei einfügen. Der einfache Name der Klasse wird in der WEB-INF/web.xml als Name des Servlets hinzugefügt.

Es ist zwar auch möglich, Test-JSPs zu dieser WAR-Datei hinzuzufügen, aber die Möglichkeit, Ihren Testcode als Servlet zu schreiben, macht diesen Ansatz so interessant. Der Test-Servlet-Code kann direkt neben dem restlichen Testcode platziert werden, so dass Sie ihn immer im Blick haben, während Sie an den Tests arbeiten. Eine Einschränkung der aktuellen Implementierung besteht darin, dass Ihr Test-Servlet in genau einer Testklasse enthalten sein sollte. Alle Hilfsklassen oder inneren Klassen werden nicht in die WAR-Datei kopiert.

import java.io.*;
import java.util.*;
import org.apache.velocity.VelocityContext;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.ClassPathResource;
public class WarOnTheFly extends JarOnTheFly {
  private String name;
  private Map servlets;
  public WarOnTheFly(String name) {
  this.name = name;
  this.servlets = new HashMap();
  }
  public void addServlet(Klasse servletClass) {
  String servletName = servletClass.getSimpleName();
  String servletClassFilename = servletClass.getName().replace('.', '/') + ".class";
  addFile("WEB-INF/classes/" + servletClassFilename,
  new ClassPathResource(servletClassFilename));
  servlets.put(servletName, servletClass.getName());
  }
  public void addWebXml() {
  VelocityContext context = new VelocityContext();
  context.put("name", name);
  context.put("servlets", servlets);
  String webxml = evaluateTemplate(context,
  "com/xebialabs/deployit/test/support/onthefly/web.xml.vm");
  addFile("WEB-INF/web.xml", new ByteArrayResource(webxml.getBytes()));
  }
  public File writeToTemporaryFile() throws IOException {
  addWebXml();
  return writeToTemporaryFile(name, ".war");
  }
  public String getName() {
  Name zurückgeben;
  }
}

Die evaluateTemplate-Methode, die von der addWebXml-Methode aufgerufen wird, ist eine statische Utility-Methode, die eine Velocity-Vorlage aus dem angegebenen Klassenpfad liest und sie unter Verwendung des angegebenen Kontexts auswertet. Die web.xml.vm sieht wie folgt aus:


  ${name}
#foreach( $key in $servlets.keySet() )

  $Schlüssel
  $!servlets.get($key)

  $Schlüssel
 
/$key

#end

EAR-Dateien im laufenden Betrieb erstellen

Um nicht in die Bredouille zu kommen, dass jeder Anwendungsserver von Ihnen verlangt, das Kontext-Root für eine WAR-Datei auf eine andere Weise anzugeben, müssen wir die WAR-Datei in eine EAR-Datei verpacken. Genau das tut die Klasse EarOnTheFly. Die Implementierung sollte keine große Überraschung sein, nachdem Sie die Klassen JarOnTheFly und WarOnTheFly kennengelernt haben:

import java.io.*;
import java.util.*;
import org.apache.velocity.VelocityContext;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
public class EarOnTheFly extends JarOnTheFly {
  private String name;
  private Kartenkriege;
  public EarOnTheFly(String name) {
  this.name = name;
  this.wars = new HashMap();
  }
  public void addWarOnTheFly(WarOnTheFly wotf) throws IOException {
  String warFilename = wotf.getName() + ".war";
  Resource warFile = new FileSystemResource(wotf.writeToTemporaryFile());
  addFile(warFilename, warFile);
  wars.put(wotf.getName(), warFilename);
  }
  private void addApplicationXml() {
  VelocityContext context = new VelocityContext();
  context.put("name", name);
  context.put("wars", wars);
  String webxml = evaluateTemplate(context,
  "com/xebialabs/deployit/test/support/onthefly/application.xml.vm");
  addFile("META-INF/application.xml", new ByteArrayResource(webxml.getBytes()));
  }
  public File writeToTemporaryFile() throws IOException {
  addApplicationXml();
  return writeToTemporaryFile("itest", ".ear");
  }
  public String getName() {
  Name zurückgeben;
  }
}

Der Vollständigkeit halber finden Sie hier den Inhalt der Velocity-Vorlage application.xml.vm:


  ${name}

#foreach( $key in $wars.keySet() )

  $!wars.get($key)
  $Schlüssel

#end

Das Test-Servlet

Wie verwenden wir also diese Klassen? Wir beginnen mit dem Schreiben unseres Test-Servlets. Wenn es aufgerufen wird, sollte das Test-Servlet die zu testende Java EE-Konfiguration ausführen und schließlich eine bestimmte Meldung ausgeben, wenn der Test erfolgreich war. Wenn etwas schief geht, kann eine Fehlermeldung ausgegeben oder eine Ausnahme ausgelöst werden. Der folgende Code ist das Servlet, das wir zum Testen unserer Datenquellenschritte verwenden:

public class DataSourceStepsItestServlet extends HttpServlet {
  protected void doGet(HttpServletRequest req, HttpServletResponse resp)
  throws ServletException, IOException
  {
  versuchen {
  InitialContext ic = new InitialContext();
  DataSource ds = (DataSource) ic.lookup("jdbc/itest");
  Verbindung conn = ds.getConnection();
  versuchen {
  Anweisung stmt = conn.createStatement();
  versuchen {
  stmt.executeUpdate("CREATE TABLE TEST_TABLE " +
  ("THEVALUE VARCHAR(16) )");
  } catch (SQLException ignore) {
  }
  String expectedValue = Integer.toString(new Random().nextInt());
  stmt.executeUpdate("INSERT INTO TEST_TABLE (THEVALUE) " +
  "VALUES ( '" + expectedValue + "' ) ");
  String sql = "SELECT * FROM TEST_TABLE";
  boolean found = false;
  ResultSet rs = stmt.executeQuery(sql);
  versuchen {
  while (rs.next()) {
  String returnValue = rs.getString(1);
  if (!found && returnValue.equals(expectedValue)) {
  gefunden = true;
  }
  }
  } finally {
  rs.close();
  }
  if (!gefunden) {
  throw new RuntimeException("INSERTED "" + expectedValue +
  "in die Tabelle TEST_TABLE, konnte sie aber nicht zurückholen");
  }
  resp.setContentType("text/plain");
  PrintWriter out = resp.getWriter();
  out.println("Der Middleware-Integrationstest hat bestanden!");
  } finally {
  conn.close();
  }
  } catch (NamingException exc) {
  throw new ServletException(exc);
  } catch (SQLException exc) {
  throw new ServletException(exc);
  }
  }
}

Zuerst wird eine Testtabelle erstellt, dann wird eine zufällige Zeichenkette darin gespeichert und schließlich wird der Inhalt der Tabelle gelesen, um zu sehen, ob die Tabelle diese zufällige Zeichenkette enthält. Wenn das funktioniert, können wir ziemlich sicher sein, dass die JDBC-Einrichtung korrekt ist!

Einsetzen des Test-Servlets

Der nächste Schritt ist die Bereitstellung des Testservlets auf dem Anwendungsserver. Wir verpacken es also in eine WAR-Datei, die wiederum in eine EAR-Datei verpackt wird (siehe den Code unten). Anschließend verwenden wir einen anwendungsserverspezifischen Code, um diese EAR-Datei zu verteilen (nicht gezeigt).

public static EarOnTheFly createItestEarOnTheFly(Class itestServletClass)
  throws IOException
{
  WarOnTheFly wotf = new WarOnTheFly("itest");
  wotf.addServlet(itestServletClass);
  EarOnTheFly eotf = new EarOnTheFly("itest");
  eotf.addWarOnTheFly(wotf);
}

Ausführen des Test-Servlets

Nun, da unser Testservlet auf einem Anwendungsserver bereitgestellt ist, müssen wir es aufrufen und prüfen, ob es erfolgreich läuft. Zu diesem Zweck verwenden wir das HttpUnit-Framework, da es eine praktische Möglichkeit bietet, URLs aufzurufen und die zurückkommende Antwort zu überprüfen.

public static void assertItestServlet(Host serverHost, int port,
  Class itestServletClass) throws IOException, SAXException
{
  String url = "https://" + serverHost.getAddress() + ":" + port + "/" +
  ITEST_WAR_NAME + "/" + itestServletClass.getSimpleName();
  WebConversation wc = new WebConversation();
  WebRequest req = new GetMethodWebRequest(url);
  WebResponse resp = wc.getResponse(req);
  String responseText = resp.getText();
  assertTrue(responseText.contains("Der Middleware-Integrationstest hat bestanden!"));
}

Alles zusammenfügen

Schließlich müssen wir diese Anwendungsserver-seitigen Tests zu den Middleware-Integrationstests hinzufügen, wie ich sie im vorherigen Blog beschrieben habe. Die genauen Details hängen von dem zu testenden Anwendungsserver ab, aber eine solche Testmethode sollte in etwa so aussehen wie diese aus dem JBoss-Plugin von Deployit:

@Test
public void testDeploymentOfOneDataSourceToExistingServer() throws IOException, SAXException {
  dataSource = new JbossasDataSource();
  dataSource.setJndiName("jdbc/itest");
  dataSource.setDriverClass("org.hsqldb.JdbcDriver");
  dataSource.setConnectionUrl("jdbc:hsqldb:file:/tmp/itest.hdb");
  dataSource.setUsername("sa");
  dataSource.setPassword("");
  mbeanName = "jboss.jca:name=" + dataSource.getJndiName() + ",service=ManagedConnectionPool";
  assertMBeanDoesNotExist();
  createDataSource();
  deployItestEar(existingJBossServer, createItestEarOnTheFly(DataSourceStepsItestServlet.class));
  restartServer();
  assertMBeanExists();
  assertItestServlet(existingJBossHost, 8080, DataSourceStepsItestServlet.class);
  destroyDataSource();
  undeployItestEar();
  restartServer();
  assertMBeanDoesNotExist();
}

Fortsetzung folgt...

Wir verwenden diesen Ansatz nun schon seit einiger Zeit, um unsere Middleware-Konfiguration zu testen, und er hat uns viel Vertrauen in unseren Middleware-Integrationscode gegeben. Es gibt nur einen Teil, von dem ich Ihnen noch nichts erzählt habe: wie Sie diesen Ansatz mit Ihrer kontinuierlichen Integration unter Verwendung von Maven und VMware integrieren können. Mehr dazu nächste Woche...

Verfasst von

Vincent Partington

Contact

Let’s discuss how we can support your journey.