Groups | Search | Server Info | Keyboard shortcuts | Login | Register [http] [https] [nntp] [nntps]


Groups > de.comp.lang.java > #12972 > unrolled thread

Unit-Tests von Einheiten ohne öffentliche Leseschnittstelle

Started by"Christian H. Kuhn" <qno-news@qno.de>
First post2016-07-07 21:19 +0200
Last post2016-07-11 01:13 +0200
Articles 20 on this page of 21 — 5 participants

Back to article view | Back to de.comp.lang.java


Contents

  Unit-Tests von Einheiten ohne öffentliche Leseschnittstelle "Christian H. Kuhn" <qno-news@qno.de> - 2016-07-07 21:19 +0200
    Re: Unit-Tests von Einheiten ohne öffentliche Leseschnittstelle Peter <peter@localhost.com> - 2016-07-07 22:37 +0200
      Re: Unit-Tests von Einheiten ohne öffentliche Leseschnittstelle Michael Paap <feunews@mpaap.de> - 2016-07-07 23:21 +0200
        Re: Unit-Tests von Einheiten ohne öffentliche Leseschnittstelle "Christian H. Kuhn" <qno-news@qno.de> - 2016-07-08 14:09 +0200
      Re: Unit-Tests von Einheiten ohne öffentliche Leseschnittstelle Wanja Gayk <brixomatic@yahoo.com> - 2016-07-08 22:10 +0200
        Re: Unit-Tests von Einheiten ohne öffentliche Leseschnittstelle Patrick Roemer <sangamon@netcologne.de> - 2016-07-09 00:17 +0200
          Re: Unit-Tests von Einheiten ohne öffentliche Leseschnittstelle Wanja Gayk <brixomatic@yahoo.com> - 2016-07-18 00:33 +0200
            Re: Unit-Tests von Einheiten ohne öffentliche Leseschnittstelle "Christian H. Kuhn" <qno-news@qno.de> - 2016-07-18 01:01 +0200
            Re: Unit-Tests von Einheiten ohne öffentliche Leseschnittstelle Patrick Roemer <sangamon@netcologne.de> - 2016-07-19 10:58 +0200
              Re: Unit-Tests von Einheiten ohne öffentliche Leseschnittstelle Wanja Gayk <brixomatic@yahoo.com> - 2016-07-21 00:07 +0200
            Re: Unit-Tests von Einheiten ohne öffentliche Leseschnittstelle "Christian H. Kuhn" <qno-news@qno.de> - 2016-07-19 14:09 +0200
    Re: Unit-Tests von Einheiten ohne öffentliche Leseschnittstelle Patrick Roemer <sangamon@netcologne.de> - 2016-07-08 00:28 +0200
      Re: Unit-Tests von Einheiten ohne öffentliche Leseschnittstelle "Christian H. Kuhn" <qno-news@qno.de> - 2016-07-08 14:13 +0200
    Re: Unit-Tests von Einheiten ohne öffentliche Leseschnittstelle "Christian H. Kuhn" <qno-news@qno.de> - 2016-07-08 16:05 +0200
      Re: Unit-Tests von Einheiten ohne öffentliche Leseschnittstelle Wanja Gayk <brixomatic@yahoo.com> - 2016-07-08 22:35 +0200
      Re: Unit-Tests von Einheiten ohne öffentliche Leseschnittstelle Patrick Roemer <sangamon@netcologne.de> - 2016-07-09 00:01 +0200
        Re: Unit-Tests von Einheiten ohne öffentliche Leseschnittstelle "Christian H. Kuhn" <qno-news@qno.de> - 2016-07-11 00:40 +0200
          Re: Unit-Tests von Einheiten ohne öffentliche Leseschnittstelle "Christian H. Kuhn" <qno-news@qno.de> - 2016-07-15 23:25 +0200
      Re: Unit-Tests von Einheiten ohne öffentliche Leseschnittstelle "Christian H. Kuhn" <qno-news@qno.de> - 2016-07-15 00:12 +0200
    Re: Unit-Tests von Einheiten ohne öffentliche Leseschnittstelle Wanja Gayk <brixomatic@yahoo.com> - 2016-07-08 21:57 +0200
    Re: Unit-Tests von Einheiten ohne öffentliche Leseschnittstelle "Christian H. Kuhn" <qno-news@qno.de> - 2016-07-11 01:13 +0200

Page 1 of 2  [1] 2  Next page →


#12972 — Unit-Tests von Einheiten ohne öffentliche Leseschnittstelle

From"Christian H. Kuhn" <qno-news@qno.de>
Date2016-07-07 21:19 +0200
SubjectUnit-Tests von Einheiten ohne öffentliche Leseschnittstelle
Message-ID<du7ob7Fecu7U1@mid.individual.net>
Hallo Gemeinde,

Bevor hier der Löschantrag wegen Inaktivität kommt, mach ich doch lieber
nochmal Traffic :-)

Auch mein aktuelles Projekt hat mal wieder Lerneffekte, wo ich sie nicht
erwartet habe. Ziel war, Gradle kennenzulernen und in Jenkins zu
integrieren. Dazu habe ich mir ein gemischtes Java-/Android-Thema
herausgesucht, da kann ich dann auch Android Studio kennenlernen. Mit
Android 2.3.3 hatte ich im Studium mal zu tun, mal schauen, was sich
geändert hat.

Ich schreibe eine Schachuhr. Die, die es gibt, sind mehrheitlich von
geringer Kenntnis des internationalen Turnierschachs und der FIDE-Regeln
geprägt, es gibt also sogar mögliche Anwender. Grobstruktur: drei
Pakete. Ein Paket common mit der Funktionalität, ein Paket java mit der
Java-Swing-GUI, ein Paket android mit der Android-GUI. Letzteres kommt
später und soll erstmal nicht interessieren.

Die Anwendung soll eine elektronische Schachuhr darstellen. Eine
Schachuhr hat zwei Uhren, die von einer vorgegebenen Zeit abwärts laufen
und bei Erreichen der Null ein optisches Zeichen („Fallblättchen“)
geben; zwei Knöpfe, die die Uhr auf der Seite anhalten, auf der der
Knopf gedrückt wird, und die Uhr auf der anderen Seite in Gang setzen,
so dass immer nur eine Uhr läuft; einen Knopf zum Stoppen; einen Knopf
zum Zurücksetzen. Verschiedene Funktionen zum Einstellen der Uhr sind im
Menü enthalten. Zur Zeit ist nur eine Bedenkzeitperiode möglich. Erlaubt
sind klassische (Zeit pro Partie) und Fischer-Bedenkzeiten (Zeit pro
Partie, kumulativer Zeitzuschlag pro Zug). Künftige Versionen sollen
auch mehrere Bedenkzeitperioden (mit und ohne Zugzähler) und
Bronstein-Bedenkzeiten (nicht-kumulativer Zeitzuschlag pro Zug)
enthalten. Eine Light-Version erlaubt nur voreingestellte Bedenkzeiten,
eine Pro-Version erlaubt freie Wahl der Bedenkzeit und ermöglicht
Sonderfunktionen für den Schiedsrichter.

Das Paket common enthält zwei Klassen, zwei Interfaces und einen
Aufzählungstyp. QChessClock ist die Klasse für die Gesamtfunktion. Sie
implementiert das Interface QChessClockObservable (QChessClockObserver
wird von den GUI implementiert). Der Aufzählungstyp QChessClockState
wird in einer privaten Variablen benutzt, um den Zustand (läuft,
angehalten, nicht gestartet) festzuhalten. Zwei weitere private
Variablen sind vom Typ QChessTimer. Die öffentliche Schnittstelle
besteht aus Methoden, die das Drücken der Knöpfe und die Menübefehle
abbilden, und den Methoden zum Registrieren und Benachrichtigen der
Observer. Außer notifyObservers() gibt es keine öffentliche Funktion zum
Abfragen des Zustands von privaten Variablen.

QChessTimer stellt die einzelne Uhr dar. Die „öffentliche“ Schnittstelle
ist nur paketsichtbar, da ein direkter Zugriff auf die einzelnen Uhren
nur über die Hauptklasse QChessClock erfolgen soll. Verschiedene
Konstruktoren erlauben das initiale Setzen der Bedenkzeit. start() und
stop() dienen zum Starten und Anhalten der Uhr. isRunning() gibt den
Status (läuft, angehalten) an, isFlagFallen() zeigt ein gefallenes
Blättchen an. unsetFlagFallen() setzt das Fallblättchen zurück. Und
schließlich gibt getRemainingSeconds() die verbleibende Zeit in Sekunden an.

Wer den Quellcode sehen will: https://www.qno.de/gitweb/

Das Coden war jetzt tatsächlich das kleinere Problem, und Gradle ist
zwar anders als Maven, aber gut bedienbar. Probleme tauchen beim Testen
auf. Ich will ja TDD anwenden. Also erst die Tests und dann den Code
schreiben. Hat mit JUnit4 bei QChessTimer auch ganz gut funktioniert.

Bei QChessClock habe ich nicht den Schimmer einer Vorstellung, wie ich
die Klasse testen soll. Es gibt keine Getter-Funktionen, mit denen sich
was überprüfen ließe. Ich kann also testen, dass das Testobjekt nach
Konstruktor ungleich null ist, sonst nichts. Eine Änderung der
Schnittstelle nur zu Testzwecken kommt selbstverständlich nicht in
Frage. Bleibt eigentlich nur, dass die Testklasse QChessClockObserver
implementiert und auf die Benachrichtigungen des Observable wartet. Der
Weg ist gangbar, ich habe ihn aber noch in keinem Buch gefunden. Daher
vermute ich, dass es da eleganteres gibt?

In noch extremerem Umfang gilt das für die Java-GUI. Die GUI erzeugt ein
QChessClock-Objekt und registriert sich dort als Observer. Sie stellt
die erwähnten Knöpfe und Menüs bereit, die auf die öffentlichen Methoden
der Schachuhr zugreifen. Öffentliche Methoden sind außer dem Konstruktor
und actionPerformed() noch die verschiedenen update()-Funktionen, über
die das Observable seine Zustandsänderungen mitteilt. Auch hier habe ich
praktisch nichts, was ich mit JUnit testen könnte, und andere Tests der
Klasse kenne ich nicht. Auch für den Integrationstest des gesamten
Systems scheint JUnit das falsche Mittel zu sein.

In der Folge ist entsprechend die Testabdeckung, die von JaCoCo
ermittelt wird, entsprechend niedrig. Wenn andere Tests eingesetzt
werden, werden die von JaCoCo erkannt? Oder muss man Klassen dann aus
der Testabdeckungsanalyse exkludieren?

Und schließlich die alles entscheidende Frage: Kann mir das jemand hier
beantworten? Oedr denke ich zu theoretisch?

TIA
QNo

[toc] | [next] | [standalone]


#12973

FromPeter <peter@localhost.com>
Date2016-07-07 22:37 +0200
Message-ID<1467923825.579226@alpaka.in-berlin.de>
In reply to#12972
Am 07.07.2016 um 21:19 schrieb Christian H. Kuhn:
> Hallo Gemeinde,
[viel Text]

Dein Vorgehen ist falsch.
Du mußt ein Projekt so aufsetzen, daß es von Anfang an testbar ist.
Die ganz hart gesottenen schreiben erst die Testklassen, und danach die
Implementierung.
Nicht ganz so radikal aber wirksam:
Die Implementierung und die Testklassen gemeinsam entwerfen.
Der Sinn dieses Vorgehens besteht genau darin, daß das Projekt von
Anfang an testbar ist, und nicht im nachhinein festgestellt wird, daß
man das Design nicht vernünftig testen kann.

Package-private Schnittstellen kann man testen, indem du das
Test-Package mit der Implementierung identisch benennst;
Du hast also im Projekt zwei Ordner mit Sourcen:
src/myPack/MyClass
und
test/myPack/TestMyClass

Und besorg dir ein Werkzeug, welches die Testabdeckung mißt; so kannst
du leicht feststellen, wo noch Tests fehlen.

[toc] | [prev] | [next] | [standalone]


#12974

FromMichael Paap <feunews@mpaap.de>
Date2016-07-07 23:21 +0200
Message-ID<nlmh53$2pk$1@news-ailanthus.fernuni-hagen.de>
In reply to#12973
Am 07.07.2016 um 22:37 schrieb Peter:

> Dein Vorgehen ist falsch.
> Du mußt ein Projekt so aufsetzen, daß es von Anfang an testbar ist.
> Die ganz hart gesottenen schreiben erst die Testklassen, und danach die
> Implementierung.

Ich lese: "Probleme tauchen beim Testen auf. Ich will ja TDD anwenden.
Also erst die Tests und dann den Code schreiben."

> Und besorg dir ein Werkzeug, welches die Testabdeckung mißt; so kannst
> du leicht feststellen, wo noch Tests fehlen.

Ich lese: "In der Folge ist entsprechend die Testabdeckung, die von
JaCoCo ermittelt wird, entsprechend niedrig."

Auch wenn's schwerfällt: Es ist durchaus sinnvoll, zu lesen, worauf man
antwortet.

Gruß,
Michael

[toc] | [prev] | [next] | [standalone]


#12976

From"Christian H. Kuhn" <qno-news@qno.de>
Date2016-07-08 14:09 +0200
Message-ID<du9jfvFrg2pU1@mid.individual.net>
In reply to#12974
Am 07.07.2016 um 23:21 schrieb Michael Paap:
> Auch wenn's schwerfällt: Es ist durchaus sinnvoll, zu lesen, worauf man
> antwortet.

Danke.

mfg
QNo

[toc] | [prev] | [next] | [standalone]


#12980

FromWanja Gayk <brixomatic@yahoo.com>
Date2016-07-08 22:10 +0200
Message-ID<MPG.31ea26fc280e77349896bc@news.aioe.org>
In reply to#12973
In article <1467923825.579226@alpaka.in-berlin.de>, Peter 
(peter@localhost.com) says...

> [viel Text]
> 
> Dein Vorgehen ist falsch.

Diese Aussage ist in ihrer Pauschalität falsch.

> Du mußt ein Projekt so aufsetzen, daß es von Anfang an testbar ist.
> Die ganz hart gesottenen schreiben erst die Testklassen, und danach die
> Implementierung.
> Nicht ganz so radikal aber wirksam:
> Die Implementierung und die Testklassen gemeinsam entwerfen.

Geht nicht immer, will man nicht immer, ist oft auch zu aufwändig. 
Manchmal programmiert man ohne klares Ziel, weil das noch nicht in Sicht 
ist und man sich langsam voran tastet, bzw. baut man wärend der 
Implementation sehr oft noch sehr viel um, refactored eine Menge und man 
will dafür nicht immer die Tests komplett umbauen, weil sich die API 
ändert (weil man auf halbem Wege merkt, dass sie doch nicht so elegant 
war, wie man dachte und einem was besseres einfällt). 

Es ist in so einem Fall pragmatischer und schneller sich erstmal ins 
Getümmel zu werfen und erst dann mit den Tests zu beginnen, wenn sich 
die Lage beruhigt. Bis dahin sollte man nach meiner Erfahrung einige 
Prinzipien beachten, wie z.B. Singletons und Monostates zu vermeiden, 
Dependencies zu injizieren (z.B. im Kontruktor), bzw. direkt ein DI-
Framework zu nutzen (CDI, Guice, Dagger2, you name it), um sich den Weg 
in die Testbarkeit nicht zu verbauen.

Gruß,
-Wanja-


-- 
..Alesi's problem was that the back of the car was jumping up and down 
dangerously - and I can assure you from having been teammate to 
Jean Alesi and knowing what kind of cars that he can pull up with, 
when Jean Alesi says that a car is dangerous - it is. [Jonathan Palmer]

[toc] | [prev] | [next] | [standalone]


#12983

FromPatrick Roemer <sangamon@netcologne.de>
Date2016-07-09 00:17 +0200
Message-ID<nlp8pd$js5$1@newsreader4.netcologne.de>
In reply to#12980
Responding to Wanja Gayk:
> Manchmal programmiert man ohne klares Ziel, weil das noch nicht in Sicht 
> ist und man sich langsam voran tastet, bzw. baut man wärend der 
> Implementation sehr oft noch sehr viel um, refactored eine Menge und man 
> will dafür nicht immer die Tests komplett umbauen, weil sich die API 
> ändert (weil man auf halbem Wege merkt, dass sie doch nicht so elegant 
> war, wie man dachte und einem was besseres einfällt). 

Aber wie merkt man das denn ohne Tests (oder sonstigen Code der mit dem
API arbeitet)? :)

Natürlich lege ich auch erst mal explorativ los, und schreibe ganz
bestimmt nicht erst mal für jede Zeile "Produktivcode" einen Test, der
diese erzwingt. Aber ich will relativ bald ein Gefühl dafür bekommen,
wie sich so ein API anfühlt - und den explorativen Code dafür schreibe
ich doch lieber gleich als Testcases. Wenn das API sich zu stark ändert,
schmeisse ich die entsprechenden Tests lieber weg anstatt zu
refaktorieren und schreibe neue. Und wenn es sich stabilisiert, habe ich
schon eine Basis, aus der man eine "ordentliche" Testsuite wachsen
lassen kann.

Viele Grüße,
Patrick

[toc] | [prev] | [next] | [standalone]


#13013

FromWanja Gayk <brixomatic@yahoo.com>
Date2016-07-18 00:33 +0200
Message-ID<MPG.31f6261eb408512e9896be@news.aioe.org>
In reply to#12983
In article <nlp8pd$js5$1@newsreader4.netcologne.de>, Patrick Roemer 
(sangamon@netcologne.de) says...
> 
> Responding to Wanja Gayk:

> > Manchmal programmiert man ohne klares Ziel, weil das noch nicht in Sicht 
> > ist und man sich langsam voran tastet, bzw. baut man wärend der 
> > Implementation sehr oft noch sehr viel um, refactored eine Menge und man 
> > will dafür nicht immer die Tests komplett umbauen, weil sich die API 
> > ändert (weil man auf halbem Wege merkt, dass sie doch nicht so elegant 
> > war, wie man dachte und einem was besseres einfällt). 
> 
> Aber wie merkt man das denn ohne Tests (oder sonstigen Code der mit dem
> API arbeitet)? :)

Nicht jede Methode ist Teil einer öffentlichen API, sondern das meiste 
Zeug ist private (bzw. package private) oder Teil einer groben Idee.

Beispiel: Ich will ne Liste von Objekten zusammen stellen, die sich aus 
irgendeinem Zustand ergibt (Zeilen aus einer tabelle, Artikel, bestimmte 
Objekte aus einer größeren Masse von Objekten, egal). 
Du machst also ne einfache Methode, die sieht so aus:

List<Something> getSomethings(){...}

Schön, sieht erstmal richtig aus, sieht auch nicht so schwer aus, tut 
wahrscheinlich was es soll, man freut sich. Werde da bei Gelegenheit 
einen Test für schreiben, um sicher zu gehen, aber jetzt geht's erstmal 
weiter mit dem Code, der das dann benutzt,vielleicht fällt dann ja was 
auf.

Dann merke ich: Nah, also eine Liste zu liefern war irgendwie blöd, weil 
ein Set da klarer wäre, obwohl: wenn ich die Duplikate sehen will, wäre 
es sinnvoll eine Liste einem Set gegenüber zu stellen.. will ich 
duplikate loggen oder nicht? Will ich vielleicht ne LinkedList haben, 
und billig vorne was einfügen können? Weiß ich noch nicht so genau, aber 
es erscheint mir ne gute Idee zu sein, wenn ich erstmal eine Collection 
im Parameter mitgebe, dann kann ich mir später als Aufrufer aussuchen, 
was für eine Collection ich befüllen will.
Also wird daraus:

<T extends Collection<? super Something>> T getSomethings(T target){...}

Schön, sieht richtig aus, mache ich dann einen Test für, wenn es mir 
passt, erstmal weiter gehen..

Dann merke ich: Okay, hier benutze ich das Stück nochmal, aber das hier:

Set<Something> someThings = getSomethings(new HashSet<Something>());
Set<OtherThings> otherThings = new HashSet<>();
for(Something s : someThings){
 otherThings.add(toOtherThing(s));
}

...ist irgendwie blöd jetzt, ich packe alles in meine Collection und 
muss dann nochmal drüber iterieren, um es auf was anderes zu mappen, 
besser ich gebe der Methode nicht nur Zielcollection mit, sondern 
zusätzlich eine Mapping-Funkction dann muss ich nur einmal drüber 
iterieren und der Aufrufer, der das schon benutzt, der übergibt einfach 
ne Identity-Funktion und ist fein raus, oder ich mache ihm eine neue 
Methode mit der alten Signatur, die einfach weiter delegiert und eine 
Identity-Funktion als Parameter übergibt, damit wird der Aufrufer 
lesbarer:
Also wird daraus:

<T extends Collection<? super Something>> T getSomethings(T target){
 return getSomethings(target, Function.identity());
}
<U, T extends Collection<? super U>> T getSomethingsFunction<? super 
Something, U> mapper, T target){...}

Und der Code oben wird zu:
Set<OtherThings> otherThings = getSomethings(myClass::toOtherThing, new 
HashSet<OtherThings>());

Hätte ich diese Entwicklung "test first" geschrieben, also mit allen 
Corner cases, hätte ich jeden der Tests für diese Methode (und das kann 
ein ganzer Batzen sein) zweimal umschreiben müssen. So sehe bin ich bei 
etwas angekommen, wo ich denke: okay, das sollte reichen.
Entweder schreibe ich jetzt einen Test, oder wenn mich noch das 
"höhere" Ziel quält, wegen dem ich diese Methode überhaupt geschrieben 
habe, kann es sein, dass ich das erst zuende machen muss, um den Faden 
nicht zu verlieren.
Also mache ich mit meiner Idee erstmal weiter, bis ich zu dem Punkt 
komme, wo ich denke dass es jetzt "soweit funktionieren sollte, dass ich 
es testen kann", weil ich keine größeren Änderungen mehr erwarte und 
dann schreibe ich Tests. Zunächst für das grobe Zeug, dann für das 
feinere Zeug. Ich präzisiere dann meine Erwartungen immer genauer und 
die Tests sagen mir, ob ich richtig liege, oder einen Hund drin habe. 

Meine Entwicklung sieht in der Regel so aus:
1.Hack
2.Refine
do{
 3.Write Test
 4.Fix Bugs
}while (bugs detected || tests incomplete)

Üblicherweise beschränkt auf ein System von 1 bis 4 Klassen, bzw. auf 
ein bis drei Milestones auf meiner Todo-Liste.

Ein Test zurrt mir die API einer Methode fest, macht Änderungen teurer, 
schränkt mich ein. Derart Entscheidungen verschiebe ich gerne auf 
später.
Ein Reihe Tests, die ich nach einem halben Tag wieder löschen kann, weil 
ich merke, dass ich auf dem Weg eine bessere Lösung für ein Problem 
gefunden habe ("oh der Workaround ist eigentlich ziemlich cool, da 
brauche ich diese 5 Methoden eigentlich gar nicht mehr.."), ist 
verschwendete Zeit, die verbringe ich lieber am Problem oder in der 
Teeküche, um den Kopf wieder frei zu kriegen.

> Natürlich lege ich auch erst mal explorativ los, und schreibe ganz
> bestimmt nicht erst mal für jede Zeile "Produktivcode" einen Test, der
> diese erzwingt. Aber ich will relativ bald ein Gefühl dafür bekommen,
> wie sich so ein API anfühlt - und den explorativen Code dafür schreibe
> ich doch lieber gleich als Testcases. 

Letzteres ist bei mir high level. Beispiel: Ich brauche einen REST Web 
Service, der mir irgendwelche Kunden zurück liefert. Der Test ist 
einfach: HTTP connection aufmachen, Kundennummer rein geben, kommt der 
Kunde zurück? Stimmen die Fehlermeldeungen? Das will ich ja nicht jedes 
mal immer neu ausprobieren und mit dem Browser oder CURL rumlutschen, da 
soll einmal Knöpfchen drücken genügen. Also Test schreiben, dann 
implementieren. Da weiß ich, was ich will, im voraus.

Und dann hast du Aufgaben, wie: Ich suche alle Files in einem Directory, 
extrahiere daraus irgendwelche Daten, cache die, ggf. muss ich per 
Timestamps prüen, ob sich was in einem File geändert hat, damit ich den 
neu einlese, etc. Da ergeben sich Fragen, wie: Wie halte ich die Daten, 
wie die Relation der Daten zu den Files und den Timestamps, bzw. brauche 
ich read-write-locks, was passiert, wenn ein File gelöscht wird, oder 
whatever.. und da weiß man ggf. noch nicht ganz genau, was man will und 
was das Sinnvollste ist. Mit anderen Worten wird erstmal explorativ 
drauflos gehackt und geschaut, wie sich das verhält und ob man das so 
nehmen kann, oder ob was anderes besser wäre, etc. und der einzige 
"Test" bis dahin hat die Qualität einer hingerotzen Main-Methode, nur um 
das Zeug auf dem höchsten Level auszulösen. Keine Corner cases, etc.
Und wenn ich dann ein Gefühl dafür habe, wohin die Reise geht, was ich 
will und dass die Idee, die ich da ausformuliert habe gut ist, sodass 
jetzt der Feinschliff kommen kann, dann fange ich an top down die Tests 
zu schreiben, um a) die API zu fixieren, b) vergessene corner cases zu 
entdecken und c) sicher zu gehen, dass ein späterer Bugfix kein anderes, 
erwartetes Verhalten bricht.

> Wenn das API sich zu stark ändert,
> schmeisse ich die entsprechenden Tests lieber weg anstatt zu
> refaktorieren und schreibe neue.

Ich fand heute diesen Talk hier relativ interessant (okay, ich bin 
schuldig mir sowas auf nem Sonntag anzuschauen, weil es interessanter 
ist das das TV-Programm). Zufällig drüber gestolpert. Ich war 
überrascht, dass der Abschnitt von Minute 15 bis Minute 28 "Spike and 
Stabilize" meiner Art Software zu entwickeln sehr nahe kommt:
https://www.youtube.com/watch?v=USc-yLHXNUg

Gruß,
-Wanja-


-- 
..Alesi's problem was that the back of the car was jumping up and down 
dangerously - and I can assure you from having been teammate to 
Jean Alesi and knowing what kind of cars that he can pull up with, 
when Jean Alesi says that a car is dangerous - it is. [Jonathan Palmer]

[toc] | [prev] | [next] | [standalone]


#13014

From"Christian H. Kuhn" <qno-news@qno.de>
Date2016-07-18 01:01 +0200
Message-ID<dv2h45Fr01rU1@mid.individual.net>
In reply to#13013
Am 18.07.2016 um 00:33 schrieb Wanja Gayk:

> "Spike and Stabilize"

Kann man machen. Hat man jahrelang gemacht. Wenn man alleine arbeitet
oder in Teams mit sehr klar verteilten Aufgaben.

Ich würde nie behaupten, dass TDD irgendeiner anderen Methode überlegen
ist. Seit ich es in meinen Projekten mehr oder (meistens) weniger
konsequent anwende, hat sich die Qualität meines Codes erhoht, aber da
war von Anfang an viel Luft nach oben. Wer mehr Praxis hat, hat auch
weniger Optimierungspotential (allerdings nie keines), dafür mehr
eingefahrene Gewohnheiten, wird also für die Umstellung auf TDD mehr
Energie für weniger Ertrag aufwenden müssen.

Zwei Gründe für TDD konnte ich inzwischen bestätigen: Wenn ich erst die
Tests schreibe, mache ich mir vorher programmnähere und weniger
abstrakte Gedanken. Oft sind dann die später auftretenden Änderungen
soviel kleiner, dass trotz Änderung der Tests insgesamt weniger Arbeit
aufzuwenden war. Gefühlt, nicht gemessen. Und zweitens: In agilen
Methoden, wo im Prinzip jeder an allem arbeiten kann, egal ob pair
programming, Scrum oder sonstwas, führt TDD dazu, dass die Arbeit an
einer Stelle problemloser von jemand anderem übernommen werden kann.
Wenn der Test vor dem Code entsteht, übernimmt man immer grün, also
lauffähig, und muss nicht erst die Fehler zusammensuchen.

lg
QNo

[toc] | [prev] | [next] | [standalone]


#13017

FromPatrick Roemer <sangamon@netcologne.de>
Date2016-07-19 10:58 +0200
Message-ID<nmkq35$bd1$1@newsreader4.netcologne.de>
In reply to#13013
Responding to Wanja Gayk:
> In article <nlp8pd$js5$1@newsreader4.netcologne.de>, Patrick Roemer 
> (sangamon@netcologne.de) says...
>> 
>> Responding to Wanja Gayk:
> 
>> > Manchmal programmiert man ohne klares Ziel, weil das noch nicht in Sicht 
>> > ist und man sich langsam voran tastet, bzw. baut man wärend der 
>> > Implementation sehr oft noch sehr viel um, refactored eine Menge und man 
>> > will dafür nicht immer die Tests komplett umbauen, weil sich die API 
>> > ändert (weil man auf halbem Wege merkt, dass sie doch nicht so elegant 
>> > war, wie man dachte und einem was besseres einfällt). 
>> 
>> Aber wie merkt man das denn ohne Tests (oder sonstigen Code der mit dem
>> API arbeitet)? :)
> 
> Nicht jede Methode ist Teil einer öffentlichen API, sondern das meiste 
> Zeug ist private (bzw. package private) oder Teil einer groben Idee.

Package private ist ja schon wieder API für andere Klassen im selben
Package.

> List<Something> getSomethings(){...}
[...]
> <T extends Collection<? super Something>> T getSomethings(T target){...}

List<Something> getSomethings() {
  return getSomethings(new ArrayList<>());
}

Inlinen, und alle bestehenden Aufrufer sind zufrieden.

> <T extends Collection<? super Something>> T getSomethings(T target){
>  return getSomethings(target, Function.identity());
> }
> <U, T extends Collection<? super U>> T getSomethingsFunction<? super 
> Something, U> mapper, T target){...}

Und hier ändert sich dank des Overloads für bestehende Aufrufer eh nichts.

> Und der Code oben wird zu:
> Set<OtherThings> otherThings = getSomethings(myClass::toOtherThing, new 
> HashSet<OtherThings>());
> 
> Hätte ich diese Entwicklung "test first" geschrieben, also mit allen 
> Corner cases, hätte ich jeden der Tests für diese Methode (und das kann 
> ein ganzer Batzen sein) zweimal umschreiben müssen.

Von test-first mit allen corner cases war ja eh nicht die Rede. Aber
wenn ich irgendwann Testcases hierfür geschrieben habe, dann, weil ich
mir aus irgendeinem Grund speziell über diese Methode Gedanken gemacht
habe. Ganz so trivial wird die dann nicht sein; dann schadet es nichts,
wenn es weiterhin Aufrufer gibt, die die neue Version durchexerzieren,
wenn auch nicht in allen möglichen Variationen. Tests dafür kann ich
hinzufügen, wenn und wann ich es für nötig halte, oder eben nicht. Das
Abschreckungspotential des "Umschreibens" halte ich in diesem konkreten
Beispiel jedenfalls für überschaubar.

> Meine Entwicklung sieht in der Regel so aus:
> 1.Hack
> 2.Refine
> do{
>  3.Write Test
>  4.Fix Bugs
> }while (bugs detected || tests incomplete)

Ich finde, dass das Schreiben von Tests beim "Refine" ungemein hilfreich
sein kann.

> Mit anderen Worten wird erstmal explorativ 
> drauflos gehackt und geschaut, wie sich das verhält und ob man das so 
> nehmen kann, oder ob was anderes besser wäre, etc. und der einzige 
> "Test" bis dahin hat die Qualität einer hingerotzen Main-Methode, nur um 
> das Zeug auf dem höchsten Level auszulösen. Keine Corner cases, etc.
> Und wenn ich dann ein Gefühl dafür habe, wohin die Reise geht, was ich 
> will und dass die Idee, die ich da ausformuliert habe gut ist, sodass 
> jetzt der Feinschliff kommen kann, dann fange ich an top down die Tests 
> zu schreiben, um a) die API zu fixieren, b) vergessene corner cases zu 
> entdecken und c) sicher zu gehen, dass ein späterer Bugfix kein anderes, 
> erwartetes Verhalten bricht.

Das impliziert irgendwie, dass Corner Cases keinen Einfluss auf das
Design haben. Und wenn ich Code dafür schreibe, will ich den auch
irgendwie triggern.

Wenn ich mit einer main anfange, sammelt sich da peu a peu aller
möglicher auskommentierter und toter Code von Variationen dieses
High-Level-Szenarios an. Da spendiere ich lieber noch ein paar
Assertions und packe die Varianten in Testcases.

> Ich war 
> überrascht, dass der Abschnitt von Minute 15 bis Minute 28 "Spike and 
> Stabilize" meiner Art Software zu entwickeln sehr nahe kommt:
> https://www.youtube.com/watch?v=USc-yLHXNUg

Have you written lots and lots of TDD code? ;)

Was er im Prinzip sagt, ist doch: Wenn (und nur wenn!) man hinreichend
viel konsequent mit agilen Prozessen gearbeitet hat, weiss man ja,
worauf es ankommt. Dann kann man die Regeln ruhig Regeln sein lassen
(das wichtige davon hat man ja eh internalisiert), den Code hinballern
und hinterher aufräumen. Das mag für ihn und für Dich funktionieren,
aber das als Entwicklungsprozess zu verticken, finde ich schon mutig.
Und mir fehlt da eben der Aspekt des Test-Driven *Design*.

Viele Grüße,
Patrick

[toc] | [prev] | [next] | [standalone]


#13030

FromWanja Gayk <brixomatic@yahoo.com>
Date2016-07-21 00:07 +0200
Message-ID<MPG.31fa1430587fc56f9896c0@news.aioe.org>
In reply to#13017
In article <nmkq35$bd1$1@newsreader4.netcologne.de>, Patrick Roemer 
(sangamon@netcologne.de) says...

> > Nicht jede Methode ist Teil einer öffentlichen API, sondern das meiste 
> > Zeug ist private (bzw. package private) oder Teil einer groben Idee.
> 
> Package private ist ja schon wieder API für andere Klassen im selben
> Package.

Prinzipiell schon, aber in der Regel hat man den Source gut genug unter 
Kontrolle, um hier aufzupassen (ich schreib mir auch übölicherweise 
einen Kommentar dran, der sagt: Packaga private nur für unit tests. 
nicht benutzen!).
Wer es dennoch tut ist des Todes (bzw. selbst schuld). :-)

> > Meine Entwicklung sieht in der Regel so aus:
> > 1.Hack
> > 2.Refine
> > do{
> >  3.Write Test
> >  4.Fix Bugs
> > }while (bugs detected || tests incomplete)
> 
> Ich finde, dass das Schreiben von Tests beim "Refine" ungemein hilfreich
> sein kann.

Kommt auf die Sitation an. Bei trivialen Methoden spare ich es mir. Sind 
die Methoden zu kompliziert, gehören sie meist eh aufgespalten.

> > sodass 
> > jetzt der Feinschliff kommen kann, dann fange ich an top down die Tests 
> > zu schreiben, um a) die API zu fixieren, b) vergessene corner cases zu 
> > entdecken und c) sicher zu gehen, dass ein späterer Bugfix kein anderes, 
> > erwartetes Verhalten bricht.
> 
> Das impliziert irgendwie, dass Corner Cases keinen Einfluss auf das
> Design haben.

Was den üblichsten Corner case angeht: Wie haben uns in meiner Gruppe 
geeinigt, dass Methoden in der Regel davon ausgehen, dass Parameter 
nicht null sind, falls sie null sein dürfen, muss es dokumentiert sein. 
Bei vielen Methoden ist diese Eigenschaft aber eher "egal", weil es 
keinen Sinn ergibt sie so zu benutzen und man sie auch nirgendwo 
benutzt, sprich: ich kümmere mich manchmal zunächst nicht drum, sondern 
erst, wenn ich den Test. Ich enscheide dann, ob ich Toleranz will, oder 
einen strikten check (den ich dann einbaue) und das wird dann durch den 
Test fixiert. Ich habe lediglich die Entscheidung über das Verhalten in 
dem Corner Case verzögert, weil der Gedanke in der "Hack/Refine"-Phase 
noch nicht wichtig war. Das gehört dann mehr zur Stabilisierung. 
Manchmal lasse ich mir eine Möglichkeit länger offen, das hat auch 
Vorteile. Ist natürlich Fallabhängig. 

> Und wenn ich Code dafür schreibe, will ich den auch
> irgendwie triggern.

Oft genug schreibe ich gar keinen Code für Corner Cases. Das Verhalten 
einer Methode für irgendein internes Zeug mit einem oder zwei Aufrufern 
ergibt sich, bzgl. Corner Cases, eher automatisch.
Eine Methode, beispielsweise, die prüfen soll, ob zwei Maps bzgl. ihrer 
Keys identisch sind, um den Unterschied zu loggen, wird bei mir nahezu 
automatisch ne NPE werfen, wenn eine der Maps null ist. Da muss man 
nicht vorher nen Test zu schreiben. Vor allem nicht, wenn der einzige 
Aufrufer garantiert zwei Maps liefern muss, weil er sonst selbst nicht 
funktionieren würde.
So einen Test kann man nachliefern, ohne sich ins Bein zu schießen, das 
muss man nicht vorher tun.

> > Ich war 
> > überrascht, dass der Abschnitt von Minute 15 bis Minute 28 "Spike and 
> > Stabilize" meiner Art Software zu entwickeln sehr nahe kommt:
> > https://www.youtube.com/watch?v=USc-yLHXNUg
> 
> Have you written lots and lots of TDD code? ;)

Jap, genug jedenfalls, um mittlerweile bei Tests nicht mehr ganz so 
streng zu sein. Ich denke mein Code sieht dennoch halbwegs passabel aus. 
Das liegt aber schon an der Abneigung gegen lange Methoden.

> Was er im Prinzip sagt, ist doch: Wenn (und nur wenn!) man hinreichend
> viel konsequent mit agilen Prozessen gearbeitet hat, weiss man ja,
> worauf es ankommt.

Da bin ich mit etwa 15 Jahren Java-Erfahrung, glaube ich, mittlerweile 
relativ firm.

Ich freue mich allerdings nach den Projekten, in denen ich schon war, 
mittlerweile mehr darüber, wenn es überhaupt Tests gibt - und erst 
recht, wenn die automatisch laufen und der CI-Server böse Mails 
verschickt. Ich habe da Kollegen gehabt, das geht auf keine Kuhhaut 
mehr, die handelten so nach der Devise: Test gebrochen - Test gelöscht.

> Dann kann man die Regeln ruhig Regeln sein lassen
> (das wichtige davon hat man ja eh internalisiert), den Code hinballern
> und hinterher aufräumen. Das mag für ihn und für Dich funktionieren,
> aber das als Entwicklungsprozess zu verticken, finde ich schon mutig.

Finde ich nicht. Ich halte es für einen pragmatischen Ansatz. Die 
Disziplin, sich nicht um die Tests zu drücken ist das einzig schwere 
dabei. Und schlecht zu testender Code wird einem spätestens dann so 
unangenehm auffallen, dass man sich beim nächsten Mal etwas mehr 
anstrengt. :-)

> Und mir fehlt da eben der Aspekt des Test-Driven *Design*.

Big Design upfront war noch nie mein Ideal. Klar braucht man ein Ziel, 
ein grobes Konzept (kommt auch auf die Domäne an, bei einer UI will ich 
gerne vorab ein möglichst präzises Design) und etwas Vorausschau, damit 
man sich nicht auf dem Weg einmauert, das meiste, in der Regel internes 
Zeug, ist Freistil. Und da halte ich es eher mit kleinen Iterationen. 

Der Unterschied ist wohl: Test Driven Design setzt darauf, dass man sich 
vorher mehr Gedanken macht, Spike and Stabilize darauf, dass man sich 
seine Optionen länger offen hält. Beides kann durchaus erfolgreich sein.
Ich lebe mit letzterem einfach besser. Meinem Spaß finde ich vor allem 
darin etwas zum Funktionieren zu bringen. Erst die Kür, dann die 
Pflicht: Erst bring ich es zum laufen, dann mache ich es schön. Manche 
brauchen es andersrum. :-)

Gruß,
-Wanja-

-- 
..Alesi's problem was that the back of the car was jumping up and down 
dangerously - and I can assure you from having been teammate to 
Jean Alesi and knowing what kind of cars that he can pull up with, 
when Jean Alesi says that a car is dangerous - it is. [Jonathan Palmer]

[toc] | [prev] | [next] | [standalone]


#13018

From"Christian H. Kuhn" <qno-news@qno.de>
Date2016-07-19 14:09 +0200
Message-ID<dv6jlkFq1trU1@mid.individual.net>
In reply to#13013
Am 18.07.2016 um 00:33 schrieb Wanja Gayk:

> Nicht jede Methode ist Teil einer öffentlichen API, sondern das meiste 
> Zeug ist private (bzw. package private) oder Teil einer groben Idee.

Ach ja, noch einen zu 100% code coverage: Wenn privater Code bei den
Testdurchläufen nicht getestet wird, sollte man sich dringend anschauen,
ob der überhaupt je benutzt wird. Es gibt Fälle, in denen die Antwort
„ja“ lautet (bestimmte Exceptions, synchronized), weil die Bedingungen
einfach nicht gescheit von außen herzustellen sind. Die meisten Fälle
sind die anderen :-)

lg
QNo

[toc] | [prev] | [next] | [standalone]


#12975

FromPatrick Roemer <sangamon@netcologne.de>
Date2016-07-08 00:28 +0200
Message-ID<nlml2i$s2p$1@newsreader4.netcologne.de>
In reply to#12972
Responding to Christian H. Kuhn:
> Bei QChessClock habe ich nicht den Schimmer einer Vorstellung, wie ich
> die Klasse testen soll. Es gibt keine Getter-Funktionen, mit denen sich
> was überprüfen ließe. Ich kann also testen, dass das Testobjekt nach
> Konstruktor ungleich null ist, sonst nichts. Eine Änderung der
> Schnittstelle nur zu Testzwecken kommt selbstverständlich nicht in
> Frage. Bleibt eigentlich nur, dass die Testklasse QChessClockObserver
> implementiert und auf die Benachrichtigungen des Observable wartet.

Variante: Der Test *erzeugt* ein Exemplar des Observer-Interface, das
entweder Assertions beinhaltet, oder eingehende Events speichert und
Methoden bietet, über die der Test diese später abfragen kann, um
Assertions darauf loszulassen.

> Der
> Weg ist gangbar, ich habe ihn aber noch in keinem Buch gefunden.

Genau dieser Ansatz sollte eigentlich in jedem TDD-Buch abgehandelt
werden. Die Nomenklatur ist allerdings herzlich inkonsistent: Das läuft
unter "Mock", "Shunt", "Stub", "Fake",... Bei Beck[1] ist das etwa ein
"(Self) Shunt" (und verwendet genau Dein Beispiel: Ein Test, der selber
ein Listener-Interface implementiert), während ein "Mock" eher ein nicht
verifizierender Drop-In für eine komplexe Ressource ist. Bei Meszaros[2]
heisst es hingegen "Test Spy" bzw. eben "Mock" (und Becks "Mock" wäre
ein "Fake Object").

> In noch extremerem Umfang gilt das für die Java-GUI. Die GUI erzeugt ein
> QChessClock-Objekt und registriert sich dort als Observer. Sie stellt
> die erwähnten Knöpfe und Menüs bereit, die auf die öffentlichen Methoden
> der Schachuhr zugreifen. Öffentliche Methoden sind außer dem Konstruktor
> und actionPerformed() noch die verschiedenen update()-Funktionen, über
> die das Observable seine Zustandsänderungen mitteilt. Auch hier habe ich
> praktisch nichts, was ich mit JUnit testen könnte, und andere Tests der
> Klasse kenne ich nicht.

Ich kann kaum glauben, dass eine Websuche mit "swing junit" nichts
zutage fördert...

GUI-Testing ist mühsam und IMHO recht spaßfrei. Es empfiehlt sich
deshalb (und nicht nur deshalb), möglichst viel Präsentationslogik in
eigenständig testbaren Klassen zu halten, die nicht nur innerhalb der
GUI-Eventloop lauffähig sind. Ein solcher Ansatz wäre z.B. Presentation
Model[3].

Um die korrekte Verkabelung des Modells mit den Widgets zu testen, muss
man entsprechende APIs des GUI-Frameworks verwenden, mit denen man
Widgets auffinden, ihre Zustände abfragen und Events auslösen kann.
Swing bietet sowas, und darauf basierend gibt es diverse auf JUnit
aufsetzende GUI-Test-Frameworks.

Bis zu einem gewissen Grad kann man sicher auch mit diesen Mitteln noch
testen, ob die GUI halbwegs so aussieht, wie erwartet (Positionierung,
Pixelfarben,...). So extrem bin ich aber selber noch nie geworden. :)

Viele Grüße,
Patrick

[1] Kent Beck, "Test-Driven Development by Example"
[2] Gerard Meszaros, "xUnit Test Patterns"
[3] http://www.jgoodies.com/download/presentations/patterns-and-binding.pdf

[toc] | [prev] | [next] | [standalone]


#12977

From"Christian H. Kuhn" <qno-news@qno.de>
Date2016-07-08 14:13 +0200
Message-ID<du9jnkFri9qU1@mid.individual.net>
In reply to#12975
Am 08.07.2016 um 00:28 schrieb Patrick Roemer:
> Genau dieser Ansatz sollte eigentlich in jedem TDD-Buch abgehandelt
> werden. Die Nomenklatur ist allerdings herzlich inkonsistent: Das läuft
> unter "Mock", "Shunt", "Stub", "Fake",... 

Ah. Bei mir ist Mocking in der anderen Richtung angekommen: Wenn ich in
meinem Beispiel die Schachuhr vor den Einzeluhren entwickelt hätte,
hätte ich für die Uhren Mocking-Objekte benutzt, die auf definierte
Aktionen definierte Ergebnisse liefern. Solange, bis ich dann eine
getestete Klasse für die echte Uhr habe. Die Transferleistung, dass das
Mocking-Objekt auch auf höherer Stufe in der Klassenhierarchie als die
zu testende Klasse ansiedeln kann, war dann zu schwer für mich.

> Ich kann kaum glauben, dass eine Websuche mit "swing junit" nichts
> zutage fördert...
> 
> [1] Kent Beck, "Test-Driven Development by Example"
> [2] Gerard Meszaros, "xUnit Test Patterns"
> [3] http://www.jgoodies.com/download/presentations/patterns-and-binding.pdf

Ich melde mich wieder, wenn ich fertig mit Lesen bin :-)

Vielen Dank!

mfg
QNo

[toc] | [prev] | [next] | [standalone]


#12978

From"Christian H. Kuhn" <qno-news@qno.de>
Date2016-07-08 16:05 +0200
Message-ID<du9qajFt4ctU1@mid.individual.net>
In reply to#12972
Zwischenspiel.

Am 07.07.2016 um 21:19 schrieb Christian „Ingrid“ Kuhn:
> In der Folge ist entsprechend die Testabdeckung, die von JaCoCo
> ermittelt wird, entsprechend niedrig. 

JaCoCo war das Mittel der Wahl, weil es von EclEmma benutzt wird und
somit in Eclipse zur Verfügung steht. Es scheint nicht das beste Mittel
zu sein:

public class Rumspiel {

    private enum Possibilities {
        A, B;
    }

    private Possibilities poss;

    public Rumspiel() {
        poss = Possibilities.A;
    }

    public void setPoss(final boolean _poss) {
        if (_poss) {
            poss = Possibilities.A;
        } else {
            poss = Possibilities.B;
        }
    }

    public void aMethod() {

        switch (poss) {
        case A:
            System.out.println("Fall A");
            break;
        case B:
            System.out.println("Fall B");
            break;
        }
    }
}

Ein default in der Switch-Anweisung wäre unerreichbarer Code; andere
Möglichkeiten als A und B können nicht vorkommen. Insbesondere kann poss
nicht null sein, das wird durch Konstruktor und setPoss verhindert. Ohne
Default behauptet JaCoCo aber, dass nur 2 von 3 Branches abgedeckt sind.

Lösbar ist das Problem, dass einer der cases zum default deklariert
wird. Das vermindert aber zumindest die Lesbarkeit des Codes; und ich
behaupte einfach mal, dass auch die Wartbarkeit leidet, weil ich bei
einer möglichen späteren Erweiterung des enum, das vielleicht inzwischen
in ein anderes Package ausgelagert wurde, nicht mehr sofort erkennen
kann, dass default eben nicht default, sondern ein ganz konkreter case ist.

Google kennt das Problem, aber nicht die Lösung. Sourcen ändern, um bei
gleicher Funktionalität Tests zu erfüllen, ist wohl kein guter Gedanke.
Den Branch-Schwellenwert, der für einen grünen Build erreicht werden
muss, abzusenken kann auch keine Lösung sein, denn der erträgliche Wert
hängt zu sehr von der Anzahl der switch- zu sonstigen Verzweigungen ab.

Bleibt ein anderes Tool. Eines, das mit Maven, Gradle und Jenkins
zusammenarbeitet. Ich bin auf Serenity, Cobertura und das proprietäre
Clover gestoßen, JCov scheint sich mit CI-Tools nicht zu vertragen. Was
benutzt ihr? Und mit welchen Erfahrungen?

TIA
QNo

[toc] | [prev] | [next] | [standalone]


#12981

FromWanja Gayk <brixomatic@yahoo.com>
Date2016-07-08 22:35 +0200
Message-ID<MPG.31ea2cd269a06f2d9896bd@news.aioe.org>
In reply to#12978
In article <du9qajFt4ctU1@mid.individual.net>, Christian H. Kuhn (qno-
news@qno.de) says...

> Bleibt ein anderes Tool. Eines, das mit Maven, Gradle und Jenkins
> zusammenarbeitet. Ich bin auf Serenity, Cobertura und das proprietäre
> Clover gestoßen, JCov scheint sich mit CI-Tools nicht zu vertragen. Was
> benutzt ihr? Und mit welchen Erfahrungen?

EclEmma, bzw. JaCoCo für den Jenkins. Nicht perfekt, aber gut genug, 
ergänzt durch Kommentare, oder asserts als "zur Laufzeit testbare 
Kommentare" an der einen oder anderen Stelle, die manchen Fehlgriff des 
Tools erklären. Dazu noch ziemlich strikte Compiler-/Error-Einstellungen 
in Eclipse, wo auch Checkstyle und FindBugs drüber gehen. Nach dem Build 
auf dem CI-Server schaut auch nochmal Coverity über das Zeug, das findet 
auch noch das Eine oder Andere. 
Als Sahnehäubchen haben wir auch ein Peer-Review-Tool für größere, bzw. 
kritische (weil zentrale) Änderungen.
Und wenn das Programm durch al diese (semi-)Automatismen durch ist, 
dürfen sich QA-Mitabeiter und Performance-Tester dran versuchen, die 
haben ihre eigenen automatischen Suites für die Nightly Builds und 
versuchen sich explorativ an ausgewählten Builds.
Das klingt alles etwas paranoid, aber es hat uns schon den einen oder 
anderen Tag gerettet, wenn nicht gar vor richtig dickem Ärger bewahrt.

Gruß,
-Wanja-

-- 
..Alesi's problem was that the back of the car was jumping up and down 
dangerously - and I can assure you from having been teammate to 
Jean Alesi and knowing what kind of cars that he can pull up with, 
when Jean Alesi says that a car is dangerous - it is. [Jonathan Palmer]

[toc] | [prev] | [next] | [standalone]


#12982

FromPatrick Roemer <sangamon@netcologne.de>
Date2016-07-09 00:01 +0200
Message-ID<nlp7qu$j5a$1@newsreader4.netcologne.de>
In reply to#12978
Responding to Christian H. Kuhn:
>         switch (poss) {
>         case A:
>             System.out.println("Fall A");
>             break;
>         case B:
>             System.out.println("Fall B");
>             break;
>         }
[...]
> Ein default in der Switch-Anweisung wäre unerreichbarer Code; andere
> Möglichkeiten als A und B können nicht vorkommen.

Trotzdem wäre ein zusätzlicher default-Branch legal und damit eben ein
weiterer Codepfad.

> Insbesondere kann poss
> nicht null sein, das wird durch Konstruktor und setPoss verhindert.

null ist nicht das Problem - auch, weil das eine NPE bei Auswertung des
"poss" triggern müsste, bevor es überhaupt in die Verzweigung ginge.

> Ohne
> Default behauptet JaCoCo aber, dass nur 2 von 3 Branches abgedeckt sind.

Im Bytecode stehen auch drei Branches - javac generiert einen
synthetischen default-Branch. Und der lässt sich von einem expliziten
no-op Default-Branch im Sourcecode nicht unterscheiden. (Und ggfs.
setzen andere Compiler dieses Konstrukt anders um.) Es ist also auch
etwas schwierig für JaCoCo, das "richtig" zu interpretieren.

Wahrscheinlich gibt es andere Coverage-Tools, die entsprechende
Heuristiken eingebaut haben ("bei einem switch über das #ordinal() eines
Enum, ignoriere den default branch, falls alle Werte des Enum bereits
abgedeckt sind und Saturn gerade im dritten Haus steht"), aber die
stolpern dann wahrscheinlich über die Bytecode-Umsetzung von
String-Switches oder etwas ganz anderes. Ein Tool, das unter allen
Umständen weder false positives noch false negatives liefert, wird man
wohl kaum finden - spätestens, wenn Du sowas erwartest, wie bei dem
null-Argument oben ("Es gibt aber doch gar keinen Codepfad, auf dem ein
mutable field in Zustand X versetzt werden kann!").

> Sourcen ändern, um bei
> gleicher Funktionalität Tests zu erfüllen, ist wohl kein guter Gedanke.

Unter Umständen schon, das nennt sich Refactoring und Test-Driven-Design. :)

Hier geht es aber nur darum, ein automatisiertes Tool glücklich zu
machen, das den Codefluss nicht versteht. Und Code schlechter zu machen,
damit das Tool ihn für gut hält, ist wirklich nicht so prima. (Das gilt
genauso für PMD, FindBugs, etc., nur, dass in deren Domäne Ausnahmen
besser konfigurierbar sind.)

> Den Branch-Schwellenwert, der für einen grünen Build erreicht werden
> muss, abzusenken kann auch keine Lösung sein, denn der erträgliche Wert
> hängt zu sehr von der Anzahl der switch- zu sonstigen Verzweigungen ab.

Was soll denn ein solcher Schwellenwert bringen? Bei 80% Abdeckung ist
mein Code ok, bei 79.9% Mist? Boiling Frogs...

IMHO bringt Coverage-Reporting nur was, wenn man sich die Ergebnisse
wiederholt im Laufe der Zeit ansieht, um Trends zu erkennen, und jeweils
in die konkret auffälligen Stellen reindrillt, um Kategorien von
Schwachstellen zu identifizieren.

> Bleibt ein anderes Tool.

Oder die Erkenntnis, dass die Dinger einfach nicht perfekt sein können.
Ich fand Emma/JaCoCo bisher durchaus brauchbar. Kann gut sein, dass
andere Tools relativ dazu besser sind (entsprechendes Feedback dazu hier
würde ich wohl zum Anlass nehmen, mir die auch mal anzuschauen), aber
Wunderdinge würde ich von keinem erwarten.

Viele Grüße,
Patrick

[toc] | [prev] | [next] | [standalone]


#12985

From"Christian H. Kuhn" <qno-news@qno.de>
Date2016-07-11 00:40 +0200
Message-ID<dug173Fd960U1@mid.individual.net>
In reply to#12982
Am 09.07.2016 um 00:01 schrieb Patrick Roemer:
> Trotzdem wäre ein zusätzlicher default-Branch legal und damit eben ein
> weiterer Codepfad.

Nachdem ich darüber meditiert habe, habe ich im gegebenen Fall eine der
legalen Varianten nicht explizit, sondern als default implementiert. Mit
Bauchweh; der Tag mag kommen, an dem ich mich für diese Entscheidung
hassen werde. Ich hab es aber im Quellcode sauber dokumentiert :-)

>> Sourcen ändern, um bei
>> gleicher Funktionalität Tests zu erfüllen, ist wohl kein guter Gedanke.
> 
> Unter Umständen schon, das nennt sich Refactoring und Test-Driven-Design. :)
> 
> Hier geht es aber nur darum, ein automatisiertes Tool glücklich zu
> machen, das den Codefluss nicht versteht. 

Ich habe mich da etwas ... suboptimal ausgedrückt. Zum Glück hast du
mich richtig verstanden :-)

>> Den Branch-Schwellenwert, der für einen grünen Build erreicht werden
>> muss, abzusenken kann auch keine Lösung sein, denn der erträgliche Wert
>> hängt zu sehr von der Anzahl der switch- zu sonstigen Verzweigungen ab.
> Was soll denn ein solcher Schwellenwert bringen? Bei 80% Abdeckung ist
> mein Code ok, bei 79.9% Mist? Boiling Frogs...

100% Abdeckung classes, lines und methods macht schon Sinn. Wenn es
weniger wird, hat man wohl irgendwo irgendwas übersehen. Branches und
Complexity hingegen ... ich neige dazu, die ersten drei bei Jenkins für
den Status auszuwerten, die letzten beiden dagegen nur für Gesundheit.

Ich bin auch im Moment noch in dem Stadium, in dem mir eine
nicht-100%-Abdeckung von was auch immer ein paar interessante Einblicke
in die Funktionsweise von Java vermittelt. Kann durchaus sein, dass
Testabdeckung in nem Jahr nicht mehr die Bedeutung für mich hat, die sie
heute hat.

>> Bleibt ein anderes Tool.
> Oder die Erkenntnis, dass die Dinger einfach nicht perfekt sein können.

Um das herauszufinden, spiele ich gerade mit den Dingern. Für die
statische Analyse hatte ich bislang nur Checkstyle, inzwischen habe ich
mir auch PDM und FindBugs angeschaut. PDM habe ich fast komplett in den
Build integriert, mit wenigen Anpassungen im ruleset.xml. Wenn mal was
ist (die von Eclipse generierten equals(Object)-Methoden überschreiten
fast immer den Schwellenwert irgendeines Komplexizitätsmaßes), schreibe
ich eine Annotation in den Quellcode, und wenn mal jemand (der Jemand
kann ich auch selbst mit ein paar Monaten Abstand sein) den Code sieht,
weiß er, dass das was war, und kann dann entscheiden, ob meine
Entscheidungen für ihn vernünftig sind. FindBugs hingegen ... Die
Warnungen, die da nach Compiler, Checkstyle und PDM übrigbleiben, haben
zuviele false positives, als dass ich das im automatischen Build haben
möchte. Zur händischen Kontrolle ist das aber auch ganz nützlich.

Ansonsten lese ich. Beck liest sich gut, ich hab aber wenig Zeit.
Mezsaros wird noch ein wenig länger dauern, ich habe aber den Eindruck,
dass der mir auch als Nachschlagewerk gute Dienste leisten könnte.
Representation Model ... dass ich bislang mit Swing kein „sauberes“ MVC
gemacht habe, war mir klar, es ist im View doch manches vom Controller
enthalten. Damit bin ich immerhin schon über Autonomous View
hinausgewesen :-) Die Folien machen Sinn, ich bemühe mich gerade um
weitere Literatur. Martin Fowler hat da unter dem für mich nicht
intuitiven Stichwort EAA dev einiges auf seiner Webseite.

mfg
QNo

[toc] | [prev] | [next] | [standalone]


#12998

From"Christian H. Kuhn" <qno-news@qno.de>
Date2016-07-15 23:25 +0200
Message-ID<dut2n4Fi5q5U1@mid.individual.net>
In reply to#12985
Am 11.07.2016 um 00:40 schrieb Christian H. Kuhn:
> 100% Abdeckung classes, lines und methods macht schon Sinn. 

Hat sich dann auch erledigt. Lines und Instructions schaffen die 100%
nicht mehr, seit SystemUtilities.invokeAndWait() Exceptions werfen kann.
Die müssen mit catch abgefangen werden, und die Bereiche kann ich nicht
gezielt mit vernünftigem Aufwand erreichen. Also muss ich nicht nur bei
Branches und Complexity, sondern auch bei Lines und Instructions
Abstriche machen.

Aber bei Classes und Methods bleib ich eisern bei 100% ;-)

lg
QNo

[toc] | [prev] | [next] | [standalone]


#12991

From"Christian H. Kuhn" <qno-news@qno.de>
Date2016-07-15 00:12 +0200
Message-ID<duqh31FtvblU1@mid.individual.net>
In reply to#12978
Am 08.07.2016 um 16:05 schrieb Christian „Ingrid“ Kuhn:
> Cobertura

Eclipse-Plugin lässt sich nur installieren, wenn ich andere Plugins auf
historische Versionen downgrade. Gradle Plugin nach allen öffentlich
verfügbaren Quellen konfiguriert. Ergebnis: JUnit-Tests werfen
Exceptions. Gelöscht.

lg
QNo

[toc] | [prev] | [next] | [standalone]


#12979

FromWanja Gayk <brixomatic@yahoo.com>
Date2016-07-08 21:57 +0200
Message-ID<MPG.31ea24199aa0f45c9896bb@news.aioe.org>
In reply to#12972
In article <du7ob7Fecu7U1@mid.individual.net>, Christian H. Kuhn (qno-
news@qno.de) says...
> 
> Hallo Gemeinde,
> 
> Bevor hier der Löschantrag wegen Inaktivität kommt, mach ich doch lieber
> nochmal Traffic :-)

> Bei QChessClock habe ich nicht den Schimmer einer Vorstellung, wie ich
> die Klasse testen soll. Es gibt keine Getter-Funktionen, mit denen sich
> was überprüfen ließe. Ich kann also testen, dass das Testobjekt nach
> Konstruktor ungleich null ist, sonst nichts. Eine Änderung der
> Schnittstelle nur zu Testzwecken kommt selbstverständlich nicht in
> Frage.

Ich bin bei sowas ganz pragmatisch.
Gibt öffentliche Methoden, die ich testen kann, dann mache ich das 
bevorzugt.
Bietet sich eine protected Methode zum Test an, hae ich die Möglichkeit 
von der zu testenden Klasse eine Klasse abzuleiten und habe so Zugriff 
auf die Methode der Ableitung - die ruft ledglich super auf, also ändere 
ich das Verhalten wenig bis gar nicht, bzw. überschaubar genug, um eine 
informierte Entscheidung zu treffen, inwiefern der Test was taugt.

Bei private Methoden habe ich wenig Schmerzen damit die Sichtbarkeit von 
"private" auf "default" zu ändern, allerdings mit einem Javadoc-
Kommentar, dass die Sichtbarkeit nur wegen JUnit auf "default" gesetzt 
wurde. Da Test und Testobjekt in einem package leben, habe ich Zugriff 
auf die Methode und kann fröhlich testen. Ich erlaube mir as, weil der 
bliche Code, den man so schreibt, ohnehin kein Frameworks für die 
Öffentlichkeit ist, bzw. nicht den Charakter einer Bibliothek für die 
breite Masse hat. Kurz: Man hat den Source der Klasse und den, der sie 
verwendet, in der Regel gut genug unter Kontrolle, um sich damit kein 
eigenes Bein zu stellen.

Ich benutze übrigens EclEmma (Emma- Plugin für Eclipse), um mir dabei zu 
helfen heraus zu finden, ob ich gewisse Fälle vergessen habe.

> Bleibt eigentlich nur, dass die Testklasse QChessClockObserver
> implementiert und auf die Benachrichtigungen des Observable wartet. Der
> Weg ist gangbar, ich habe ihn aber noch in keinem Buch gefunden. Daher
> vermute ich, dass es da eleganteres gibt?

Ich finde nichts Falsches daran eine Klasse so zu testen, dass du eine 
Test-Listener an die Klasse hängst, und die Events abfragst, die da raus 
kommen. Schließlich sind die geworfenen Events Teil der API, bzw. eines 
Methoden-Contracts und gehören deswegen getestet. Wenn du zusicherst in 
einer bestimmten Situation ein Event zu werfen, sollte dein Test auch 
prüfen, ob das geschieht, sofern das für das Programm wichtig ist.

> In noch extremerem Umfang gilt das für die Java-GUI.


[..]

> Auch für den Integrationstest des gesamten
> Systems scheint JUnit das falsche Mittel zu sein.

JUnit halte ich für ein Mittel, welches gut genug dafür ist einzelne 
Komponenten zu testen, auch wenn es UI-Kompoinenten sind. Hierbei musst 
du aufpassen, dass du alles im EDT ausführst, Konstruktion der Panels, 
setzen und abfragen der Listener, erzeugen, abfragen und setzen von 
Werten, das Finden von Buttons und Dropdowns in der Komponenten-
Hierarchie. etc.. und du solltest einen Timeout für alle Aktionen haben, 
der zwar nicht zu knapp bemessen ist (bei mir sind es etwa 10 Sekunden), 
aber du willst, dass einzelne Tests fehlschlagen können, ohne dass die 
dentest auf Ewig raus zögern, weil du einen Test verkackt hast und der 
z.B. ewig auf ein Ergebnis wartet.
Solche UI-Tests kannst du auch auf einem Jenkins-Server laufen lassen, 
wenn du ein virtuelles Display hast (Linux, Display-Variable), auf dem 
die Komponenten gerendert werden können. Problem hierbei ist: Du musst 
sicherstellen, dass Tests niemals gleichzeitig ausgeführt werden und 
dass Frames vollständig geschlossen sind, bevor ein neuer Test gestartet 
wird. Mitunter ist das etwas tricky, aber machbar. Leider ist es auch 
langsam, aber mit dieser Methode habe ich die Test-Coverage von UI-Code 
in unserer Codebase dramatisch erhöht - und natürlich sehe ich im Emma-
Plugin, wo er durch gerannt ist.
Für den Integrationstest einer komplette Applikation halte ich es nict 
für das richtige Mittel, aber einzelne Komponenten (also ne Suchmaske 
mit ein paar Knöpfen, etc), kann man so recht ordentlich testen und 
deckt auch den einen oder anderen, überraschenden Fehler auf.
Es hilft aber ein Flag zu haben, um diese UI-tests zu überspringen, weil 
du in der Zeit, in der die auf deinem Entwicklungsrechner laufen, 
praktisch nicht arbeiten kannst, weil du mausklicks simulierst - 
abgesehen davon, dass sie langsam sind. Du willst alle normalen Tests 
oft und schnell durchführen und die lahmen UI-Tests nur an und zu, vor 
der Kaffeepause oder vor dem Commit laufen lassen, bzw. regelmäßig auf 
dem CI-Server.

Gruß,
-Wanja-

-- 
..Alesi's problem was that the back of the car was jumping up and down 
dangerously - and I can assure you from having been teammate to 
Jean Alesi and knowing what kind of cars that he can pull up with, 
when Jean Alesi says that a car is dangerous - it is. [Jonathan Palmer]

[toc] | [prev] | [next] | [standalone]


Page 1 of 2  [1] 2  Next page →

Back to top | Article view | de.comp.lang.java


csiph-web