Groups | Search | Server Info | Keyboard shortcuts | Login | Register [http] [https] [nntp] [nntps]
Groups > de.comp.lang.java > #12972 > unrolled thread
| Started by | "Christian H. Kuhn" <qno-news@qno.de> |
|---|---|
| First post | 2016-07-07 21:19 +0200 |
| Last post | 2016-07-11 01:13 +0200 |
| Articles | 20 on this page of 21 — 5 participants |
Back to article view | Back to de.comp.lang.java
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 →
| From | "Christian H. Kuhn" <qno-news@qno.de> |
|---|---|
| Date | 2016-07-07 21:19 +0200 |
| Subject | Unit-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]
| From | Peter <peter@localhost.com> |
|---|---|
| Date | 2016-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]
| From | Michael Paap <feunews@mpaap.de> |
|---|---|
| Date | 2016-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]
| From | "Christian H. Kuhn" <qno-news@qno.de> |
|---|---|
| Date | 2016-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]
| From | Wanja Gayk <brixomatic@yahoo.com> |
|---|---|
| Date | 2016-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]
| From | Patrick Roemer <sangamon@netcologne.de> |
|---|---|
| Date | 2016-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]
| From | Wanja Gayk <brixomatic@yahoo.com> |
|---|---|
| Date | 2016-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]
| From | "Christian H. Kuhn" <qno-news@qno.de> |
|---|---|
| Date | 2016-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]
| From | Patrick Roemer <sangamon@netcologne.de> |
|---|---|
| Date | 2016-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]
| From | Wanja Gayk <brixomatic@yahoo.com> |
|---|---|
| Date | 2016-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]
| From | "Christian H. Kuhn" <qno-news@qno.de> |
|---|---|
| Date | 2016-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]
| From | Patrick Roemer <sangamon@netcologne.de> |
|---|---|
| Date | 2016-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]
| From | "Christian H. Kuhn" <qno-news@qno.de> |
|---|---|
| Date | 2016-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]
| From | "Christian H. Kuhn" <qno-news@qno.de> |
|---|---|
| Date | 2016-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]
| From | Wanja Gayk <brixomatic@yahoo.com> |
|---|---|
| Date | 2016-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]
| From | Patrick Roemer <sangamon@netcologne.de> |
|---|---|
| Date | 2016-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]
| From | "Christian H. Kuhn" <qno-news@qno.de> |
|---|---|
| Date | 2016-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]
| From | "Christian H. Kuhn" <qno-news@qno.de> |
|---|---|
| Date | 2016-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]
| From | "Christian H. Kuhn" <qno-news@qno.de> |
|---|---|
| Date | 2016-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]
| From | Wanja Gayk <brixomatic@yahoo.com> |
|---|---|
| Date | 2016-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