Mittwoch, 22. Dezember 2010

Java Generics - wie (typ)sicher kann ich mir sein

Ich habe heute mit einem Kollegen vor einem etwas kniffligen Problem gesessen. Beim Versuch in einer JSF-Seite auf ein Property eines Entities zuzugreifen bekamen wir einen Fehler, da dort anstelle des erwarteten Entities ein BigDecimal-Wert ankam. Es war klar, dass es sich dabei um einen dummen Fehler in unserem Code handeln musste, die Frage war nur, wo der zu suchen war. Auf den ersten und auch auf den zweiten Blick sah alles OK aus. Das Entity kam aus einer java.util.List die über Java Generics typsicher definiert war. Erst nachdem wir an einer passenden Stelle einen Breakpoint eingefügt haben, kamen wir so langsam dahinter was da schief lief.

Um die Sache einfacher erklären zu können hier ein stark vereinfachtes Beispiel:


import java.util.List;

public class MyClass {
  DataProvider dataProvider;

  public MyClass() {
    dataProvider = new DataProviderImpl();
  }

  @SuppressWarnings( "unchecked" )
  public void loadData() {
    List<MyEntity> data = (List<MyEntity>) dataProvider.getData();
    // Do something with the data...
  }
}


Sieht auf den ersten Blick alles korrekt aus, oder? Das haben wir auch gedacht, aber irgendwo musste ja der Fehler stecken.


Quiz-Frage: Welchen Typ haben die Elemente in der List<MyEntity> data?


Man sollte annehmen, dass darin nur Objekte vom Typ MyEntity enthalten sein können, oder? Stimmt aber nicht! In unserem Fall waren darin zur Laufzeit BigDecimal-Werte, weil in unserem DataProvider die Methode fehlerhaft programmiert war. Die eigentliche Fehlerursache war letztendlich ziemlich unspektakulär (wir hatten vergessen bei einer Hibernate SQL-Query den entsprechenden Entity-Typ anzugeben). Viel spannender war die Frage warum wir keine ClassCastException bekommen hatten, die uns wahrscheinlich ziemlich schnell auf die Ursache aufmerksam gemacht hätte.


Um das zu verstehen muss man wissen, dass Java Generics lediglich vom Java Compiler - also zur Kompilierzeit - interpretiert werden. Zur Kompilierzeit kann der Compiler nicht wissen, ob die getData() Methode zur Laufzeit eine Liste mit MyEntity- oder anderen Objekten zurückgibt, da die Methoden-Signatur nur eine einfache List (ohne Generics-Definition) als Rückgabe-Typ definiert. Das einzige was der Compiler tun kann ist uns zu warnen, dass er eine typunsichere Zuweisung gefunden hat. Das hat er in unserem Beispiel sogar getan, aber wir haben mit der @SuppressWarnings Annotation explizit gesagt "Kein Problem, wir wissen was wir tun" (eine fatale Selbstüberschätzung).

Beim Kompilieren - nachdem mögliche Typ-Fehler und/oder -Warnungen ausgewertet wurden - werden die Generics-Angaben aus dem Quell-Code entfernt. Das was nach dem Kompilieren - im Java Byte-Code - von der Zuweisung bestehen bleibt ist das Folgende:

List data = (List) dataProvider.getData();

Also eine Cast-Anweisung die zur Laufzeit keine Auswirkung mehr hat. Daher wird hier auch keine ClassCastException ausgelöst, egal welchen Typ von Elementen die getData() Methode zurückgibt.

Um den gewünschten Cast-Effekt zu erhalten müssen die einzelnen Elemente der Liste gecastet werden.

List data = new ArrayList();
for (Object o : dataProvider.getData()) {
  data.add((MyEntity) o);
}


Dann kann auch die @SuppressWarnings Annotation entfernt werden, und zur Laufzeit fliegt die erwartete ClassCastException, sobald dort was anderes ankommt als erwartet.

Fazit: Die @SuppressWarnings Annotation ist schnell eingefügt (in Eclipse gibt's dafür sogar extra einen Quick Fix). Trotzdem macht es manchmal Sinn den oben beschriebenen "Umweg" in Kauf zu nehmen um wirklich sicher zu sein, dass unerwartete Elemente nicht einfach weiterverarbeitet werden.

In diesem Sinne, frohe Weihnachten...

Freitag, 8. Oktober 2010

Einrichten einer Android Entwicklungsumgebung und Aufsetzen eines Projektes

In diesem und nachfolgenden Beiträgen beschreibe ich die schrittweise Entwicklung einer Android Applikation. Jeder Beitrag wird sich mit einem Aspekt der Android Programmierung beschäftigen. In diesem ersten Beitrag beschreibe ich die Einrichtung der Entwicklungsumgebung sowie das Aufsetzen eines neuen Android Projektes in Eclipse.

Die im Laufe dieses Projektes entwickelte Applikation wird eine Minigolf Ball-Datenbank, so dass ich die Einarbeitung in die Android-Entwicklung mit meinem zweiten großen Hobby, dem professionellen Minigolf-Sport kombinieren kann. 

Um mir erreichbare Ziel zu stecken, werde ich die Entwicklung in kleinschrittige Iterationen unterteilen. Dabei werde ich mir für jede Iteration jeweils ein bewusst kleines Feature auswählen und die entsprechende Umsetzung an dieser Stelle beschreiben. 

Mein Ziel für die erste Iteration:

Einrichtung der Entwicklungsumgebung und Anlage eines Android Projektes

Die Einrichtung der Entwicklungsumgebung, die Installation des Android SDK Starter Packages und des ADT Eclipse-Plugins sowie die Installation der eigentlichen Android Plattformen, ist auf der Android Developer Seite im SDK Installation Guide recht gut beschrieben.

Voraussetzung für die Installation des SDK ist ein Java JDK in der Version 5 oder 6. Weiterhin wird in der Android Anleitung eine bereits vorhandene Eclipse Entwicklungsumgebung in der Version 3.4 oder 3.5 vorausgesetzt. Ich verwende die SpringSource Tool Suite in der Version 2.3.2, die auf Eclipse 3.5 basiert, zusammen mit einem Java 6 JDK.

Die Installation des SDK Starter Packages beschränkt sich auf Download und Entpacken eines ZIP-Archivs. Der Pfad des entpackten SDK Starter Packages wird zur PATH-Umgebungsvariablen hinzugefügt. Das Starter Package enthält lediglich die SDK Tools. Die eigentlichen Android Plattformen, in verschiedenen Versionen, werden im übernächsten Abschnitt installiert.

Nach der Installation des Starter Packages wird zunächst das Android Developer Tools (ADT) Plugin für Eclipse installiert. Im SDK Installation Guide ist eine Eclipse Update Site angegeben, über die das Plugin ohne Probleme installiert werden kann. Nach dem obligatorischen Eclipse-Neustart muss dann in den Preferences im Bereich Android lediglich noch das zuvor entpackten Android SDK Verzeichnis konfiguriert werden.

Schließlich müssen die eigentlichen Android Plattformen - die verschiedenen Android Versionen, die unterstützt werden sollen - heruntergeladen und installiert werden. Die Installation kann direkt aus Eclipse über den Android SDK und AVD Manager (Menü Window) erfolgen. Zur Entwicklung von Android Applikationen muss mindestens eine Android Plattform installiert werden. Im Bereich "Available Packages" des Managers können die gewünschten Komponenten ausgewählt und installiert werden. Ich lade i.d.R. alle verfügbaren Komponenten herunter. Für das hier beschriebene Projekt sollte jedoch die Android Plattform 2.1 Update 1 ausreichen.

Anlage eines neuen Eclipse Projektes

Nachdem Eclipse gestartet, und ggf. ein neuer Workspace angelegt wurde, muss zuerst ein neues Android Projekt angelegt werden. Dazu stellt das ADT Plugin einen Android Project Wizard zur Verfügung der über den üblichen Weg "File -> New -> Project... und dann Android -> Android Project" gestartet wird. Der Wizard fragt die relevanten Parameter des Projektes ab.

Der Projekt Name ist der Name des Eclipse Projektes, der z.B. in der Navigator View angezeigt wird.

Wie bei allen Eclipse Projekten kann das Projektverzeichnis entweder innerhalb des Workspaces (default location) oder an anderer Stelle erzeugt werden. Ich halte meine Sourcen und meinen Workspace lieber in getrennten Verzeichnissen.

Das Build Target definiert die Android Plattform Version für welche die Applikation kompiliert wird.

Der Application Name ist der Name der Applikation, wie er später auf einem Android Gerät im Menü oder im Fenster-Titel angezeigt wird.

Der Package Name bestimmt das Basis Java-Package der Applikation.

Die Minimum SDK Version bestimmt die kleinste Android Plattform Version, auf der die Applikation laufen soll. Die Version wird in Form einer API Level Kennzahl angegeben, die z.B. aus der Liste der Build Targets abgelesen werden kann.

Mit der Option Create Activity kann mit dem Projekt gleichzeitig eine Activity für die Applikation angelegt werden. Was eine Activity genau ist, werde ich im nächsten Beitrag beschreiben. Für den Moment genügt es, wenn wir die Activity als die eigentliche Anwendung verstehen, die beim Aufruf der Applikation gestartet wird.

Für mein Android Projekt wähle ich die folgenden Einstellungen:

Projekt Name: AndroidBallApp
Build Target: Android Plattform 2.1 Update 1
Application Name: BallApp
Package Name: de.mandry.android.ballapp
Create Activity: BallAppActivity
Min SDK Version: 7

Im nächster Schritt fragt der Android Project Wizard, ob auch ein Android Test Projekt angelegt werden soll. In der Android Welt ist der Test zu einer Applikation ebenfalls eine Applikation, die in einem eigenen Projekt, einem Test Projekt, abgelegt wird. Ein Test-Projekt bezieht sich immer auf genau ein Applikations-Projekt. Wird die "Create a Test Project" Checkbox aktiviert, werden die Properties für das Test Projekt automatisch von denen des eigentlichen Anwendungsprojektes abgeleitet und ein leeres Test-Projekt wird erstellt.

Nach dem Klick auf "Finish" werden die beiden Eclipse-Projekte angelegt. Durch die Option "Create Activity" enthält das Projekt "AndroidBallApp" bereits eine minimale aber lauffähige Android Anwendung, die aus Eclipse heraus direkt gestartet werden kann (über das Kontext-Menü des Eclipse Projektes "Run as --> Android Application".

Beim ersten Ausführen einer Android Applikation meldet Eclipse, dass noch kein Target Device vorhanden ist, auf dem die Anwendung ausgeführt werden kann. Es wird angeboten nun ein Android Virtual Device (AVD) zu erstellen. Dazu wird wieder der bereits bekannte SDK und AVD Manager aufgerufen. Der New-Button im Bereich "Virtual Devices" öffnen einen Dialog in dem ein Name sowie eine Reihe von Einstellungen für das virtuelle Gerät definiert werden können. Für den Anfang genügt es neben einem beliebigen Namen die gewünschte Android Plattform (Target) auszuwählen und das virtual Device ansonsten mit den Default Einstellungen anzulegen.

Nach dem Schließen des SDK und AVD Managers muss im Android Device Chooser die Liste der Virtual Devices aktualisiert werden. Danach kann das neu angelegte virtuelle Gerät ausgewählt und die Anwendung darauf deployed und gestartet werden.

Eclipse startet das virtuelle Android Gerät in einem separaten Fenster. Das starten des Devices dauert immer einige Zeit, daher ist es empfehlenswert, das einmal gestartete Device Fenster offen zu lassen. Beim nächsten Ausführen der Applikation aus Eclipse heraus wird dieses dann weiter verwendet und die Anwendung wird lediglich neu deployed und gestartet. Das dauert dann nur wenige Sekunden.



Die gestartete Anwendung ist erwartungsgemäß nicht besonders spannend. Neben dem Anwendungsnamen BallApp, den ich bei Anlage des Android Projektes festgelegt habe, wird lediglich ein Label mit dem Text "Hello World, BallAppActivity!" angezeigt. Das werden wir in einer der nächsten Iterationen ändern.

Donnerstag, 22. Juli 2010

Unit-Test von E-Mail Funktionalität mit Mock JavaMail

Automatisiertes Testen von Java-Code ist meist nicht so einfach. Selbst wenn die erste Hürde genommen ist und man weiß was man testen will, bleibt man oft recht schnell an der Frage hängen, wie das zu tun ist. Richtig schwierig wird es, wenn die zu testende Funktionalität von externen Diensten abhängt. Der Versand von E-Mails fällt in diese Kategorie.

Vor kurzem stand ich im Rahmen eines Projektes vor dem Problem den Versand von E-Mails zu testen. Nach kurzer Recherche bin ich auf das Mock JavaMail Projekt (https://mock-javamail.dev.java.net/) gestoßen.

Die Verwendung von Mock JavaMail ist so einfach wie genial. Es muss lediglich das JAR-Archiv in den Classpath des JUnit-Tests aufgenommen werden und schon ersetzt die Mock-Implementierung den echten Mail-Server. Eine Anpassung der JavaMail-Konfiguration ist nicht notwendig. Im einfachsten Fall kann der fest verdrahtete Mail-Server in der Java-Klasse stehen.

Auf die gesendete E-Mail zugreifen kann man über eine Mailbox-Klasse, über welche zu einer E-Mail-Adresse eine Liste der empfangenen Nachrichten abgerufen werden kann.



  @Test
  public void testSendMail() {
    String subject = "Test E-Mail";
    String message = "Dies ist eine Test E-Mail";
    String recipientEmail = "test@mail.de";
    String senderEmail = "noreply@mail.de";

    mailSender.sendMail(subject, message, recipientEmail, senderEmail);

    try {
      List inbox = Mailbox.get(recipientEmail);

      assertEquals("Mailbox enthält falsche Anzahl E-Mails", 1, inbox.size());

      Message mail = inbox.get(0);

      assertEquals("Empfangene E-Mail hat falschen Betreff", subject,
          mail.getSubject());
      assertTrue("Empfangene E-Mail hat falschen Absender", mail.getFrom()[0]
          .toString().contains(senderEmail));

    } catch (AddressException exc) {
      fail(exc.toString());
    } catch (MessagingException exc) {
      fail(exc.toString());
    }
  }


Neben dem Abfangen und Testen von gesendeten E-Mails kann mit Mock JavaMail auch eine Mailbox simuliert (gemockt) werden, um das Abrufen von E-Mails (z.B. über POP3) zu testen.

Schließlich kann das Framework dazu verwendet werden Fehler beim Senden von E-Mails zu provozieren um so das Fehler-Handling der eigenen Applikation zu testen.


  @Test
  public void testSendMailError() {
    String subject = "Test E-Mail";
    String message = "Dies ist eine Test E-Mail";
    String recipientEmail = "test@mail.de";
    String senderEmail = "noreply@mail.de";

    try {
      Mailbox.get(recipientEmail).setError(true);

      try {
        mailSender.sendMail(subject, message, recipientEmail, senderEmail);
        fail("Fehler beim E-Mail-Versand löst keine RuntimeException aus");
      } catch (RuntimeException exc) {
        // Success
      }

    } catch (AddressException exc) {
      fail(exc.toString());
    }
  }


Ein sehr hilfreiches Test-Framework das man kennen sollte.