Dec 092014
 

Beim Coden ohne Internetzugriff in der Münchner U-Bahn sind Offlinedokus recht hilfreich für mich. Ob jedoch ein derart hoher Aufwand gerechtfertigt ist um sich eine Offlinedoku fürs Ionic Framework zu erzeugen bleibt jedem selbst überlassen:

  1. Ruby 1.x mit Ruby-Gems installieren von http://rubyinstaller.org/downloads/ nach Verzeichnis ruby (o.ä.) (Achtung: 2.x funktioniert nicht mit Jekyll wg. fehlendem Support für Ruby 2.x in Yaml siehe http://stackoverflow.com/questions/16498287/jekyll-liquid-exception-cannot-load-such-file-yajl-2-0-yajl
  2. PATH-Umgebungsvariable um ruby/bin erweitern
  3. Zertifikatsproblem beheben (evtl. Windowsspezifisch, siehe https://gist.github.com/fnichol/867550)
    a) cacert.pem von vertrauenswürdiger Quelle herunterladen (von z.B. http://curl.haxx.se/ca/cacert.pem) und speichern nach ruby/cacert.pen
    b) in Datei ruby/bin/gem.bat die Zeile “set SSL_CERT_FILE=c:\dev\ruby\cacert.pem” einfügen
  4. Ruby Dev-Kit passend zur Ruby-Version installieren
    a) Dowload von http://rubyinstaller.org/downloads/
    b) extract nach ruby-devkit
    c) Ausführen von ruby dk.rb init
    d) Ausführen von ruby dk.rb install
  5. Jekyll WebServer installieren durch Ausführen von gem install jekyll
  6. Verzeichnis ionic-site anlegen
  7. Ionic-Site clonen durch Ausführen von git clone https://github.com/driftyco/ionic-site ionic-site
  8. Jekyll WebServer starten in Verzeichnis ionic-site durch Ausführen von jekyll serve -w
  9. Doku lesen unter http://127.0.0.1:4000/docs/

+1 für ein downloadbares Archiv der Doku auf ionicframework.com.

Dec 072014
 

Heute Morgen habe ich meine Checklist-App im Google Play Store veröffentlicht: eine Alternative zu dem Berg an Checklist-Apps.

android-presentation

Kostenlos, einfach, schlank und werbefrei. Meine App kann mehrere Checklisten parallel verwalten, Mehrfachbearbeiten und Undo/Redo.  Schauen wir mal, wie’s für angenommen wird. Ist ja nicht leicht bei den Gazillionen an Apps für  Checklisten. Ich werd’s nutzen – das reicht mir schon Smiley

Ich wollte kurz teilen, welches Technologiesetup ich verwende:

  • Ionic Framework mit Apache Cordova
  • UnderscoreJS
  • Cordova Keyboard Plugin um gezielt die Keyboard-Tastatur zu öffnen / schließen
  • Gulp für Build mit minifyCss, minifyHtml, ngmin, stripDebug, uglify, useref
  • Jasmine für UnitTests
  • Protractor für GUI-Tests

Die Bedienbarkeit / Geschwindigkeit der Checklist-App mit Cordova ist passabel. Die Entwicklung mit dem Stack macht einfach Spaß.

Jul 132014
 

Deine Foto-Dateien sind Dir heilig? Du willst diesen Erinnerungen bewahren, gleichzeitig online darauf zugreifen ohne dass die ganze Welt Deine Urlaubsbilder kennt. Vielleicht ist dann meine Backup-Strategie etwas für Dich:

Dafür brauchst Du:

Continue reading »

Jul 122014
 

Der Quellcode des Angular-Moduls ngMock hat mir gerade verraten, wie man optimal einen zu testenden Service / Controller injected. Mit diesem Vorgehen ist der Service / Controller in beforeEach, afterEach und jedem Test (it-Block) einer Suite (describe-Block) als Closure-Variable verfügbar:

describe("Checklists", function () {

var service;

function doSomething() {
// ...
service.doAnything();
// ...
}

beforeEach(function() {
module("checklists");
inject(function(Checklists) {
service = Checklists;
doSomething();
});
});

it("can do", function () {
// ...
service.doAnything();
// ...
});
});

Die Registrierung des zu testenden Angular-Moduls muss innerhalb eines Testfalls mit “module(“<name>”)” passieren, da die Liste der Module in ngMock für jeden Testfall zurückgesetzt wird. Mittels “inject” innerhalb eines laufenden Testfalls erfolgt der Aufruf der übergebenen Funktion und übergeben der zu injiziierenden Argumente. Ein “beforeEach(inject(function(Checklist) { … }));” ist nicht möglich.

May 142014
 

Du willst eine Web-App auf verschiedenen WebBrowsern testen? Leicht: Einfach installieren. Auf verschiedenen Versionen desselben Browsers? Schon schwieriger.

Eine Lösung dafür bietet browserling.com: Einfach URL angeben, Browser und Version auswählen und direkt im eigenen HTML5-fähigem WebBrowser testen. Der Test-Browser wird auf einer Serverfarm ausgeführt und im eigenen Browser innerhalb eines Canvas angezeigt. Interaktion ist möglich.

Kostenlos: Drei Browser-Starts, aber nur eine IE-Version.

Potential: Zahlst Du mind. 20 $/Monat sind viele Browserversionen – sogar IE6, 7 und 8 – unbegrenzt oft aufrufbar.

Einschränkungen: Alle Browser laufen auf Windows Desktop, keine mobilen Browser, kein Mac. Interaktionen mit der WebSite im Test-Browser sind sehr verzögert.

Warteschlange: Ab und an kommt es vor, dass man nach Auswahl eines Browsers etwas warten muss. Bislang waren diese bei mir nur kurz (<30 Sekunden).

Welche andere Varianten zu Cross-WebBrowser-Tests kennst Du?

Nov 302013
 

“So einfach wie möglich. Aber nicht einfacher.” meint Albert Einstein. Ich bin überzeugt: Redundanzfreiheit ist eine Programmiertugend, die einfache, verständliche und vor allem änderbare Software begünstigt. Bekannt ist diese Tugend auch als DRY – Don’t repeat yourself. Das gilt natürlich auch für Softwaretests. Ich liebe das mächtige, einfache Jasmine-Test-Framework für JavaScript.

Jasmine strebt danach, verhaltensgetriebene Softwareentwicklung (BDD) zu unterstützen und liegt damit im Trend der testgetriebenen Softwareentwicklung (TDD). Wenn-Dann-Testfälle definieren das Softwareverhalten vor dessen Implementierung. Jasmine möchte insbesondere für lesbare Testfälle sorgen.

Meine Idee: Die absolute Reduktion eines Jasmine-Testfalls auf dessen Namen. Dies ist besonders nützlich für Testfälle die sich in den verwendeten Daten unterschieden – also mehrere Testfalldatensätze verwenden.  Die von jedem Test verwendete Testfunktion extrahiert sich Eingabedaten und erwartetes Ergebnis aus dem Testfallnamen.

Sieht wie folgt bei mir aus:

describe("taskEditCtrl", function() {

    function testDueInputDisplayByTestDesc() {

        var desc = jasmine.getEnv().currentSpec.description;

        var dueInput = desc.replace(/.+with '([.\w\s\d]+)'.*/, '$1');

        var expectedDueText = desc.replace(/.+to display '([.\w\s\d]+)'.*/, '$1');

        editDueAndSave(dueInput);

        expect(task.getDueText()).toBe(expectedDueText);

    }

 

    it("can edit and save due with '1 day' to get '2013-6-13'", testDueInputByTestDesc);

    it("can edit and save due with '1day' to get '2013-6-13'", testDueInputByTestDesc);

    it("can edit and save due with '1 days' to get '2013-6-13'", testDueInputByTestDesc);

    it("can edit and save due with '2 days' to get '2013-6-14'", testDueInputByTestDesc);

    it("can edit and save due with '2days' to get '2013-6-14'", testDueInputByTestDesc);

    it("can edit and save due with '32 days' to get '2013-7-14'", testDueInputByTestDesc);

    it("can edit and save due with '1 week' to get '2013-6-19'", testDueInputByTestDesc);

    it("can edit and save due with '1 weeks' to get '2013-6-19'", testDueInputByTestDesc);

    it("can edit and save due with '2 weeks' to get '2013-6-26'", testDueInputByTestDesc);

    it("can edit and save due with '5 weeks' to get '2013-7-17'", testDueInputByTestDesc);

});

Diese Testsuite für das Modul “taskEditCtrl” prüft die Änderung des Due-Datums mit verschiedenen Eingabewerten und erwarteten Ergebnissen. Als Due-Datum kann ein Nutzer z.B. “2 days” für das Datum zwei Tage von heute in der Zukunft abgeben. Die Testfunktion “testDueInputByTestDesc” wird einfach als Referenz in jedem Testfall angegeben. Der Erste Testfall entällt als Eingabe “1 day” und als erwarteter Ausgabewert “2013-6-13”.

Meine Jasmine-Testergebnisse zeigen anhand des Testfallnamens was der Test leistet – ohne dabei das Wissen über den Testfall redundant im Testnamen und im Testcode redundant zu halten. Bedeutet für mich: “So einfach wie möglich. Aber nicht einfacher.”

Eine Alternative zu meinem Vorgehen gibt’s von JP aus Frisco: http://blog.jphpsf.com/2012/08/30/drying-up-your-javascript-jasmine-tests. Liest sich wie “@Parameters” in JUnit und gefällt mir auch sehr gut.

Jan 072013
 

Ich hoffe Folgendes hilft dem ein oder anderen beim Schnelleinstieg in die Erstellung eines Contract First RESTful Service unter folgenden Kriterien:

  • JAX-RS (im Speziellen Jersey)
  • Maven-basiert

Contract First bedeutet hier, dass die Maven-Phase “generate-sources” mittels JAXB-Plugin aus XML Schema (XSD) die notwendigen Java-Klassen generiert, welche als Datentypen in einem Service verwendet werden. Daher wähle ich den Contract First Ansatz, um ein Schema zur Schnittstellenabstimmung mit meinen Schnittstellenpartner zu verwenden.

Alle Code-Auszüge findet Ihr in Gänze in meinem Beispielprojekt.

Für die Code-Generierung folge ich Empfehlungen zur Nutzung des jaxb2-maven-plugin in meiner pom.xml:

   1: <build>

   2:   <plugins>

   3:     <plugin>

   4:       <groupId>org.codehaus.mojo</groupId>

   5:       <artifactId>jaxb2-maven-plugin</artifactId>

image

XSD und Binding-Dateien liegen in den separaten Verzeichnissen src/main/xsd und xjb (Default von jaxb2-maven-plugin).

Die XSD enthält die Datentyp-Definitionen (complexType) aus denen das jaxb2-maven-plugin die Datentypklassen generiert. Für die Verwendung als eigenständige Payload für Request bzw. Response eines Service muss die generierte Datentypklasse die JAXB-Annotation @XmlRootElement erhalten. Hierzu muss die XSD zusätzlich zum “xs:complexType” ein “xs:element” unter Verwendung des Typs definieren und…

   1: <xs:element name="businessPartner" type="BusinessPartner"/>

   2: <xs:complexType name="BusinessPartner">

   3:   ...

   4: </xs:complexType>

… das JAXB-Plugin muss eine Binding-Datei (.xjb) vorfinden, welche den Simple-Mode mittels “xjc:simple” aktivitert. Das Binding referenziert in schemaLocation relativ die betreffende XSD.

   1: <?xml version="1.0"?>

   2: <jxb:bindings version="1.0" xmlns:jxb="http://java.sun.com/xml/ns/jaxb" xmlns:xjc="http://java.sun.com/xml/ns/jaxb/xjc" jxb:extensionBindingPrefixes="xjc" xmlns:xs="http://www.w3.org/2001/XMLSchema">

   3:   <jxb:bindings node="/xs:schema" schemaLocation="../xsd/businessPartner.xsd">

   4:     <jxb:globalBindings>

   5:       <xjc:simple />

   6:     </jxb:globalBindings>

   7:   </jxb:bindings>

   8: </jxb:bindings>

Für das JAXB-Plugin ist zu definieren, dass Vendor-spezifische JAXB-Bindings (hier xjc:simple) erlaubt sind:

   1: <plugin>

   2:   ...

   3:   <artifactId>jaxb2-maven-plugin</artifactId>

   4:   <configuration>

   5:      <extension>true</extension>

Da jaxb2-maven-plugin vom Maven-Eclipse-Plugin m2e im Maven-Live-Cycle unterstützt wird, wird unmittelbar Java-Quellcode für die Datentypen generiert, z.B.

   1: ...

   2: @XmlAccessorType(XmlAccessType.FIELD)

   3: @XmlType(name = "BusinessPartner", propOrder = {

   4:     "addresses"

   5: })

   6: @XmlRootElement(name = "businessPartner")

   7: public class BusinessPartner {

   8:

   9:     @XmlElement(name = "address")

  10:     protected List<Address> addresses;

  11:     ...

Der RESTful Service ist für JAX-RS händisch implementiert und referenziert die generierten Datentypen:

   1: @Path("/businessPartner")

   2: @Consumes(MediaType.APPLICATION_JSON)

   3: @Produces(MediaType.APPLICATION_JSON)

   4: public class BusinessPartnerResource {

   5:

   6:     ...

   7:

   8:     @POST

   9:     public void create(BusinessPartner bupa) {

  10:         ...

  11:     }

  12:

  13:     ...

  14:

  15: }

Damit Eclipse WTP den Maven-default der web-Dateistruktur  verwendet, muss in .settings/org.eclipse.wst.common.component das Verzeichnis “src/main/webapp”  statt “WebContent” angegeben werden:

   1: <?xml version="1.0" encoding="UTF-8"?><project-modules id="moduleCoreId" project-version="1.5.0">

   2:     <wb-module deploy-name="testservices">

   3:         <wb-resource deploy-path="/" source-path="/src/main/webapp" tag="defaultRootSource"/>

   4:         ...

   5:     </wb-module>

   6: </project-modules>

Damit steht dem Deployment über die Eclipse-Server-View und anschließendem Test nichts mehr im Weg.

image

Feedback ist willkommen!

Jul 152011
 

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.

Jun 232011
 

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!