Google App Engine für Java unterstützt JPA und JDO. Beide Implementierungen sind nicht 100% vollständig. Nicht unterstütze JPA-Features sind in der Google Dokumentation genannt (ganz unten). Zusätzlich sollte jeder Google-Cloud-Aspirant wissen:

  1. Primärschlüssel von Entitäten in AppEngine müssen den Typ com.google.appengine.api.datastore.Key haben. Dies ist nicht konform zur JPA-Spezifikation.
  2. Nach Aufruf von EntityManager.persist ist die Id des persistierten Entity-Objekts nicht gesetzt. Dies ist ebenso nicht JPA-Spec-konform.

Ein Aufruf an EntityManager.refresh hilft, damit die Id gesetzt ist und z.B. an den Client zurückgeliefert werden kann, wie folgendes Beispiel zeigt:

QuestEntity questEntity = new QuestEntity();
questEntity.setMessage(quest.message);
entityManager.persist(questEntity);
entityManager.refresh(questEntity); // to retrieve the id
assert questEntity.getId() != null;
quest.id = KeyFactory.keyToString(questEntity.getId());

Mit einer JPA-Implementierung wie Hibernate oder EclipseLink wäre questEntity.id bereits nach Aufruf von “persist()” gesetzt. Vermutlich hat dies etwas mit dem Fakt zu tun, dass App Engine mit der verteilten Objektdatenbank Big Table arbeitet und nicht mit einer relationalen Datenbank.

Testmodule

Maven-Module verwalten Produktiv- und Testcode in getrennten Verzeichnissen (src/main und test). Das Packaging (z.B. als JAR) exkludiert Testklassen und -ressourcen. Damit andere Module Test-Hilfsklassen, Mock-Implementierungen, etc. verwenden können, müssen diese in src/main liegen. Elemente von src/test bleiben für andere Module verborgen. Um Testklassen wiederzuverwenden, lagert man in diese in ein separates Testmodul aus. Das sieht z.B. wie folgt aus:

image

Der Testcode und die Hilfsklassen für “framework” sind nach “framework-test” ausgelagert worden. Die Test-Hilfsklassen operieren auf “framework”-Klassen. Die Testklassen benötigen die Testhilfsklassen. Würden die Tests innerhalb von “framework” liegen gebe es einen Modulzyklus. Dies ist weder sinnvoll noch von Maven erlaubt.

image 

Wer seine Testabdeckung automatisiert ermittelt bekommt mit dieser Modulaufteilung Probleme. Per Default ermitteln Cobertura, Clover, Emma die Unit-Test-Abdeckung nur pro Modul. Obwohl umfangreiche Tests für den Code in “framework” existieren, liefert eine Messung 0% Abdeckung.

Lösung mit JaCoCo

JaCoCo wird von den Emma-Machern entwickelt und arbeitet über Byte-Code-Instrumentation zur Laufzeit mittels eines JVM-Agents.

Mittels JaCoCo-Agent und dem JaCoCo-Sonar-Plugin lässt sich das Testabdeckungsproblem lösen und sogar die Abdeckung für Integrationstests messen. Das im Folgenden beschriebene Setup umfasst Jenkins / Hudson sowie Sonar Konfiguration und Anpassungen an Euren Maven POMs.

1. JaCoCo bereitstellen

Das “jacoco-agent.jar” wird auf dem System, welches die Tests ausführt (z.B. Euer CI Server), benötigt.

  1. ZIP herunterladen (wget …) z.B. nach /var/lib/jacoco. Achte darauf, dass Deine Sonar-Installation zum JaCoCo-Agent kompatibel ist oder entschließe Dich, Deine Sonar-Installation upzugraden.
  2. unzip
2. POM anpassen

Das Surefire Plugin muss den JaCoCo-Agent einbinden, wenn es die Tests ausführt. Der Agent muss Coverage-Informationen für alle Eure Module in dieselbe Datei schreiben.

   1: <profiles>

   2:   <profile>

   3:     <id>ci</id>

   4:       <build>

   5:         <pluginManagement>

   6:           <plugins>

   7:             <plugin>

   8:               <artifactId>maven-surefire-plugin</artifactId>

   9:                 <configuration>

  10:                   <argLine>-javaagent:${jacoco.agent.path}=destfile=${jacoco.unit.path}</argLine>

  11:                 </configuration>

  12:               </plugin>

  13:             </plugins>

  14:           </pluginManagement>

  15:       </build>

  16:   </profile>

  17: </profiles>

3. Jenkins / Hudson Job konfigurieren

Jenkins muss die Variablen “jacoco.agent.path” und “jacoco.unit.path”. Dazu bei “Goals und Optionen” in der Job-Konfiguration z.Bsp. folgendes angeben:

clean install -P ci -Djacoco.agent.path="/var/lib/jacoco/lib/jacocoagent.jar" -Djacoco.unit.path="/tmp/mapgame-jacoco-unit"

Gib bei der Sonarkonfiguration des CI-Jobs die Option “-Dsonar.jacoco.itReportPath=/tmp/mapgame-jacoco-it“ an. Das JaCoCo-Sonar-Plugin erkennt diese System-Property und findet dadurch die Report-Datei.

image

4. Sonar JaCoCo Plugin installieren

Installiere im Sonar Update Center das Plugin “JaCoCo”. Konfiguriere unter Sonar – Settings – Core – Code Coverage Plugin den Wert “jacoco”. Hast Du alles korrekt gemacht, so wirst Du auf Deinem Sonar-Dashboard nach Lauf des CI-Jobs im Jenkins / Hudson den korrekten Wert für die Unit-Testabdeckung sehen.

image image

 

Trennung Unit- und Integration-Tests

JaCoCo kann natürlich auf die Integration-Test-Abdeckung messen. Dies solltest Du getrennt von der Unit-Test-Messung halten. Mit Unit-Tests kann nach TDD entwickelt werden, mittels Integration-Tests nicht wirklich, welche Change & Test-Phase zu lang dauert und Unit-Tests eine viel lokalere Betrachtung erlauben. Dadurch ist Bugfixing bei Testfehlschlägen effizienter.

Für Integrationstests sollte das “maven-failsafe-plugin” verwendet werden, da es Aufräumarbeiten nach Integrationstests (Server stoppen, etc.) besser beherrscht (post-integration-test Phase wird bei Test-Fehlschlägen aufgerufen. Dies ist bei Surefire nicht der Fall.).

1. POM erweitern

Die Code-Abdeckung in den Integration-Tests selbst soll gemessen werden:

<plugin>  <artifactId>maven-failsafe-plugin</artifactId>  <configuration>    <argLine>-javaagent:${jacoco.agent.path}=destfile=${jacoco.it.path}</argLine>  </configuration></plugin>

Natürlich musst Du den JaCoCo-Agent auch in den getesteten Systemen einbinden. Bei mir ist dies die lokale Google App Engine.

<profiles>  <profile>    <id>ci</id>    <build>      <pluginManagement>        <plugins>          <plugin>            <groupId>net.kindleit</groupId>            <artifactId>maven-gae-plugin</artifactId>            <executions>              <execution>                <id>start-app-engine</id>                <phase>pre-integration-test</phase>                <goals>                  <goal>start</goal>                </goals>                <configuration>                  <javaAgent>${jacoco.agent.path}=destfile=${jacoco.it.path}</javaAgent>                </configuration>              </execution>            </executions>          </plugin>        </plugins>      </pluginManagement>    </build>  </profile></profiles>

2. Jenkins / Hudson CI Job erweitern
  • Zusätzlicher Parameter bei “Goals und Optionen”: -Djacoco.it.path="/tmp/mapgame-jacoco-it"
  • Zusätzlicher Parameter bei Sonar MAVEN_OPTS: -Dsonar.jacoco.itReportPath=/tmp/mapgame-jacoco-it
3. Sonar Dashboard erweitern
  1. Im Sonar Dashboard “configure widgets” klicken
  2. “IT coverage widget” hinzufügen

 

Fazit

JaCoCo funktioniert!

Hohe Code-Abdeckung durch gut strukturierte Unit- und Integrationstests, die jeweils nur eine “Sache” testen, sind die halbe Miete für gute Softwarequalität!

Hier kurz für Euch und mich mein Vorgehen zur Performance Analyse von Android Apps. Es geht darum relevante Stellen im Code zu finden, deren Optimierung sich lohnt. Zwei Wege: Analyse über (1) Memory Allocations und (2) Ausführungszeit der Methoden .. alles basierend auf Eclipse.

Memory Allocation

  1. App in Eclipse mittels Android Development Tools (ADT) starten.
  2. App in den Zustand versetzen ab der die Untersuchung relevant ist (bei mir: in Spielewelt einloggen)
  3. In Eclipse-Perspektive DDMS wechseln.
  4. Den Prozess im Emulator / Device auswählen, der Eurer App entspricht.
  5. Auf den Reiter “Allocation Tracking” wechseln
  6. Button “Start Tracking” klicken
    image
  7. Abwarten bis aus Eurer Sicht genug Daten gesammelt sind (bei mir ein paar Animation-Frames)
  8. “Get Allocations” Button klicken um die gesammelten Informationen anzuzeigen
  9. Interessant für Optimierungen sind für mich häufig auftretende Allokation an denselben Code-Stellen. Dazu die Tabelle nach “Allocated In” sortieren. Für jede Allokation kann im unteren Teil der View der Stack-Trace betrachtet werden um die verantwortliche Stelle im eigenen Code zu identifizieren.
    image

Anhand dieser Analysen habe ich Stellen identifiziert in den z.B. unnötig Collection-Objekte erzeugt wurden. Diese habe ich durch Caching der verwendeten Collection in Instanzvariablen des längerlebigen Objekts optimiert (Vermeidung von Allokationen).

Ausführungszeiten von Methoden

Analog zu den Speicherallokationen können die Ausführungszeiten – genauer der Anteil der Ausführungszeit einzelner Methoden an der Gesamt-Ausführungszeit der App – analysiert werden. Effizienter Code für Android Apps wie Spiele ist zum Einen essentiell für eine akzeptable Frame-Rate und zum Anderen für die Optimierung des Batterieverbrauchs. Wer schon mal Angry Birds gespielt und danach Kopfschüttelnd zum Ladekabel gegriffen hat, kann das sicher nachvollziehen.

Die Infos zu Ausführungszeiten schreibt Emulator / Device auf die SD-Card. Daher muss das “AndroidManifest.xml” die entsprechende Permission anfordern:

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

Um die Analyse durchzuführen zunächst die Schritte 1 – 4 von oben wie bei Memory Allocations ausführen, dann

5. “Start Method Profiling” Icon-Button klicken
image
6. Warten bis genug Daten gesammelt sind (ein paar Frames in meinem Spiel)
7. “Stop Method Profiling” Icon-Button klicken
8. Trace-File vom Emulator / Device herunterladen
image

9. Trace-File mittels Trace-View-Tool des Android SDK (liegt bei mir unter “D:\java\android-sdk-windows\tools\traceview.bat”) öffnen. Optimalerwise verknüpft Ihr die Endung “.trace” mit dem Trace-View-Tool in Windows. Dann sieht das Ergebnis in Trace-View wie folgt aus:
image

10. Die Prozent-Angaben der Spalte “Incl %” waren für mich relevant. In welchen, möglichst tief in der Aufrufhierarchie liegenden Methoden, wird sehr viel Zeit verbraucht? In einem ersten Schritt habe ich TreeMaps durch eine eigene Implementierung ersetzt, die für meine Zwecke effizienter ist. Ohne diese Analyse mit Trace-View fällt die Identifikation der optimierungsrelevanten Stellen schwer.

Fazit

Optimiert Eure Apps nicht ins Blaue hinein. Aufwand und Nutzen müssen in gesundem Verhältnis stehen. Mit den oben beschrieben Ansätzen lassen sich schnell Stellen für Optimierungspotential finden. Mindestens genauso wichtig ist die Erfolgsprüfung Eurer Optimierungsmaßnahmen und die Nachhaltung über automatisierte Tests.

Gerade konfiguriere ich die Ausführung des Android Emulator mit Jenkins Continuous Integration. Mir hat gerade sehr geholfen, eine Fehlermeldung des Plugin über grepcode.com zu finden. Diese Site erlaubt die konfortable Navigation im Quellcode vieler Open-Source-Projekte. Sehr hilfreich.

In Android teilt sich eine App in Activities auf. In der Android API fehlt die Möglichkeit eine App komplett zu beenden. Meine Lösung verwendet startActivityForResultonActivityResultsetResult und finish.

In meiner Tower Defense App durchläuft der Benutzer drei Activities: WelcomeActivity, WorldSelectActivity und WorldActivity = Spielen. Die Klassenhierarchie wie folgt:

Die im folgenden beschriebenen Methoden sind Bestandteil des Layer-Supertypes AbstractActivity. Der Nutzer drückt “quit” im Menu der WorldActivity, dann soll die aktuelle und alle vorher aufgerufenen Activities der App beendet werden.

  public boolean onOptionsItemSelected(final MenuItem _item) {
    switch (_item.getItemId()) {
      case R.id.menuEntryQuit:
        quitApp();
        return true;
      default:
    }
 
    return super.onOptionsItemSelected(_item);
  }

Beim Beenden der Activity muss ein entsprechender Result-Code gesetzt werden, …

  protected void quitApp() {
    setResult(RESULT_QUIT_APP);
    finish();
  }

…damit die aufrufende Activity das Beenden der App erkennt und sich ebenfalls beendet.

  protected void onActivityResult(final int _requestCode,
      final int _resultCode, final Intent _data) {
    if (_resultCode == RESULT_QUIT_APP) {
      quitApp();
    } else {
      super.onActivityResult(_requestCode, _resultCode, _data);
    }
  }

Obwohl die aufrufende Activity kein echtes Ergebnis der Folge-Activity erwartet, muss es die folgende Actvity in Erwartung eines Ergebnisses starten um das Result-Ergebnis RESULT_QUIT_APP über onActivityResult zu erhalten:

startActivityForResult(intent, REQUEST_WORLD);

Drückt also der Nutzer “quit” so beenden sich alle Activities im Stack der App. Dieser Blog-Post zerstört sich im Übrigen auch in 10 Sekunden selbst ;-)

Mar 042011

Gerade habe ich eines meiner Subversion Repositories auf einen neuen Server umgezogen. Das war recht einfach und funktioniert wie folgt…

Continue reading »

Es soll IT-Architekten geben, die in der Kernarbeitszeit tatsächlich länger am Stück arbeiten.  Kommt bei mir auch vor. Großprojekte mit vielen Kollegen sind sehr anregend für den eigenen Wissenshorizont, jedoch nur bedingt gut für die Konzentration.
Ich empfehle daher zur Begünstigung unterbrechungsfreien Arbeitens den 24h-Drum’n'Bass Radio-Stream Bassdrive.  Bei durchschnittlichen 175 bpm arbeite ich einfach schneller und konzentrierter. Bei mir läuft gerade Bassdrive auf Media-Monkey.
Und ja, in der restlichen Zeit gebrauchen IT-Architekten Ihr loses Mundwerk und zeichnen Pfeile, Kreise und Rechtecke auf Flipcharts in großen Meetingräumen.

Um die Java-Klassen für WebService-Clients zu generieren empfiehlt sich das JAX-WS-Plugin für Maven. Dieses klinkt sich bei Verwendung von “wsimport” in die Build-Phase “generate-sources” ein.

Für mich ist es sinnvoll, die WSDL-Dateien des anzusprechenden Services direkt in “src/wsdl” meines Projekts abzulegen statt diese über eine URL einzubinden. Dies hat den Vorteil, dass ich Änderungen an der Schnittstelle des Service in der Subversion-Historie nachvollziehen kann. Muss mein Projekt mehrere Service ansprechen, so lege ich die WSDL- plus die Schema-Datei (XSD) jedes Service im genannten Verzeichnis ab.
Continue reading »

Zuletzt habe ich über das Beschleunigen von lokalen JEE-Deployments geschrieben. Für das Beschleunigen von JEE-Deployments ist meiner Einschätzung nach zwingend das exploded Deployment notwendig. Dabei wird ein EAR oder WAR nicht als Archiv-Datei sondern als entpackte Verzeichnisstruktur deployt. Leider ist das Deployment von exploded EARs und WARs noch nicht durch die JEE-Spezifikation abgedeckt. Daher hat jeder JEE Container dabei so seine Eigenheiten. Die folgenden Zeilen liefern ein paar wichtige Informationen zum lokalen Deployment von explodierten EARs unter Glassfish V2.
Continue reading »

Schneller, höher, weiter! Das Entwickeln von JEE-Applikationen ist bestimmt durch Round-Trips aus Code-Änderung, Kompilierung, Re-Deployen und Testen. Da sich diese Round-Trips in kurzen Abständen (Minutenbereich) wiederholen, sollte das Kompilieren und Re-Deployen so wenig Zeit möglich in Anspruch nehmen. Zum einen lässt das schlicht mehr Zeit zum Programmieren und Testen. Zum anderen macht das Arbeiten den Programmierern so einfach mehr Spaß! (… und die Zufriedenheit des Programmierers ist direkt proportional zur Lebensdauer seiner Tastatur).

Continue reading »

© 2011 Sophisticated IT Suffusion theme by Sayontan Sinha