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.

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!

May 192011
 

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.