Für was schreibt man Unit-Tests?

Marco13

Top Contributor
Hi

Der Titel ist bewußt zweideutig ;) Es geht natürlich nicht um den Zweck von Unit-Tests, sondern um die Frage, für welche Klassen und Methoden und Konstellationen man wie Unit-Tests erstellt.

Die TDD-Verfechter werden jetzt sagen: Man schreibt Tests für alles, und das noch bevor man es entwickelt hat. Andere werden sagen: Man schreibt Tests für alles was wichtig ist ;) . Ich frage mich, wie man in der Praxis wirklich nützliche und sinnvolle systematische Unit-Tests erstellen kann. Bei Websuchen findet man immer diese Trivialbeispiele, die mit der Realität nichts zu tun haben (ich kann das Money-Beispiel nicht mehr sehen :noe: ).

Mal angenommen man wollte für so etwas vermeintlich triviales (!) wie das Java Collections Framework Unit-Tests schreiben. Eine handvoll recht überschaubarer Interfaces. Dann überlegt man und sucht vielleicht einen Ansatz, und schaut mal nach, ob es sowas nicht vielleicht schon gibt (ist ja nicht so abwegig, oder?) und findet dann z.B. in Guava eine abstrakte Basisklasse für den Test eher 'Map': MapInterfaceTest.java - google-collections - Google Collections Library - Google Project Hosting - ~650 (!) Zeilen stupider, repetitiver Trivialcode :noe: (Nur zum Vergleich: Die Implementierung in Form von HashMap hat (einschließlich ihrer abstrakten Basisklasse "AbstractMap"!) nur ca. 400 Zeilen...)

Damit das nicht in der ersten Antwort steht: Mir sind die Vorteile bewußt. Wenn man vorhat, mehrere verschiedene Map-Implementierungen zu schreiben ist man froh, wenn man so einen Test über die neue Implementierung drüberlaufen lassen kann, und sofort eventuelle Fehler findet. Und wenn man Änderungen macht, sieht man sofort, ob man vielleicht etwas kaputt gemacht hat. Es geht wie gesagt nicht um den eigentlichen Zweck von Unit-Tests. Der sollte jedem einleuchten.

Es geht eher darum, wie man das Ziel erreichen kann, systematisch Tests zu schreiben, wenn das Schreiben und Testen (!) solcher systematischer Tests so schnell so absurd aufwändig wird, wie schon in dem einfachen Map-Beispiel (und dann noch List, Set & Co dazu... Ich will mir gar nicht überlegen, was man noch in Test packen müßte, damit er seinen Namen wirklich verdient, wenn man z.B. spezifisches Verhalten von CopyOnWriteArrayList überprüfen wollte (multi-Threaded Unit-Tests?! :autsch: )). Duch die verschiedenen möglichen Aufrufreihenfolgen entsteht m.E. eine kombinatorische Explosion...

Vielleicht mal ganz konkret: Wer würde sich trauen, eine definitive Aussage darüber zu machen, ob der oben verlinkte Test eine 1:1-Entsprechung zu gültigen Map-Implementierungen darstellt? D.h. gibt es eine gültige Implemetierung, die den Test nicht passiert, oder eine ungültige Implementierung, die von dem Test nicht erkannt wird? :bahnhof: Wie soll man das herausfinden?

Oder sind wir vielleicht gerade im "Tal der Tränen", wo Software nicht mehr so einfach ist, dass man sie "per Hand" formal verifizieren kann, aber die Tools und Sprachfeatures zur formalen Defintion von Vor- und Nachbedingungen und Invarianten und dem automatischen Testen noch nicht praktisch einsetzbar sind, und man deswegen die Fleißarbeit auf sich nehmen muss, tausende Zeilen Testcode zu schreiben, der einen ein bißchen beruhigt, von dem man aber DOCH nie weiß, ob er seinen Zweck wirklich im strengsten Sinn erfüllt?

Bin gespannt auf die Antworten :)
 
N

nillehammer

Gast
Hallo Marco,

ich schreibe es so wie ICH es mache ohne Anspruch auf Allgemeingültigkeit. Ich betrachte jetzt mal nur Unit-Tests im engeren Sinne. Unit-Tests im engeren Sinne sind Whitebox-Tests. Sprich ich kenne den Code, den ich teste. Von besonderer Bedeutung sind hier die Verzweigungen. Mein Ziel ist eine möglichst vollständige Zweigüberdeckung meiner Tests. Da liegt es in der Natur der Sache, dass selbst bei nur sehr wenig Zweigen die Länge meiner Unit-Tests die Länge des Produktivcodes um ein Vielfaches übersteigt.

Aber selbst mit 100% Zweigüberdeckung ist man noch nicht failsafe. Hinzu kommen evtl. noch verzwickte Implementierungsdetails. Häufig macht man mit seinem Code nämlich Zusicherungen an seine Clients, die einem auf den ersten Blick garnicht auffallen. Dazu folgendes Beispiel: Wir schreiben ein API, dass Arrays in Listen umwandelt (ich weiß gibt's schon, nur als Beispiel). Zunächst das Interface:
Java:
public interface ArrayUtil {

  public List<E> asList(E[] array);
}
Implementierung1:
Java:
public ArrayUtilImpl1 implements ArrayUtil {

  public List<E> asList(E[] array) {

    if(array == null || array.length == 0) {
       return new ArrayList();
    }
    ...
Implementierung2
Java:
public ArrayUtilImpl2 implements ArrayUtil {

  public List<E> asList(E[] array) {

    if(array == null || array.length == 0) {
       return Collections.emptyList();
    }
    ...
Du siehst, beide Implementierungen erfüllen den Kontrakt des Interfaces und behandeln null-Values und leere Arrays gleich. Aaaaber selbst mit 100% Zweigüberdeckung hat man nicht alles getestet. Im ersten Fall hat man nämlich die implizit die Zusicherung an seine Clients gemacht, dass sie die Liste ändern können, im zweiten nicht. Also noch ein Testfall mehr, der Modifikationsoperationen auf der zurückgegebenen leeren Liste aufruft.

Langer Rede kurzer Sinn:
Unit-Test ist Whitebox. Du bist der Entwickler. Du weißt, was Dein Code macht. Teste alle Zweige. Mache Dir über implizite Zusicherungen Deines Codes Gedanken und teste auch die. Also: Teste alles!
 
A

Andgalf

Gast
Also ich tue mich jetzt schwer nicht pauschal zu sagen man schreibt Tests für alles, da ich ein TDD verfechter bin :)

So spass beiseite, ich teste auch nicht 100% alles. (z.B. Getter, Setter etc.)Aber dass der Umfang des Testcodes größer ist als der produktive Code himself, halte ich für ziemlich normal.
Klar bedeuted das Mehrarbeit, da dieser Code ja auch gepflegt werden muss. Das ist er meiner Meinung nach aber auch Wert, den neben der Regressionsicherheit stellen Tests auch eine ziemlich gute Dokumentation des Quellcodes dar.

Das man 100% failsave sein kann halte ich für eine Illusion, egal wie gut / umfangreich man tested. Allerdings bringen einen die Tests ein gutes Stück näher ran.
 

Marco13

Top Contributor
OK, beim Vokabular bin ich mir jetzt an einigen Stellen nicht sicher, da gibt es bestimmt ausgefeilte und streng definierte Begriffe...

@Andgalf: Gut, als Programmi^C^C^C^Cessimist geht man natürlich davon aus, dass GENAU die Funktion, die NICHT von den Tests abgedeckt wird, die Probleme verursachen wird ;) aber... ab einem bestimmten Punkt finde ich (auch), dass die Wahrscheinlichkeit, dass der Test fehlerhaft ist, höher ist, als die Wahrscheinlichkeit, dass die Implementierung fehlerhaft ist...

@nillehammer: Das mit den Verzweigungen war mir einerseits bewußt, andererseits hatte ich es jetzt überhaupt nicht im Kopf: Meine Frage bezog sich primär oder ausschließlich auf den Test von Dingen, die durch ein Interface vorgegeben sind. (Auch wenn man dabei "möglichst alle Fälle" abdecken muss, und damit zumindest indirekt (und ohne dass man es selbst merkt) auch versucht, möglichst alle Verzweigungen abzudecken)

Das Beispiel mit der List ist eines von vielen - man könnte schon darüber lange philosophieren und sich dran aufhängen, aber nur als Beispiel: Wenn in der Interface-Definition steht
Java:
/**
 * Returns an unmodifiable List of Foo. It may be empty but is never <code>null</code>.
 * 
 * @return The list
 */ 
public List<Foo> getFoo() { ... }
Dann müßte man testen, ob dort "null" zurückgegeben wird, und ggf. failen - klar.

Dann steht da aber noch "unmodifiable"...

(BTW: Normalerweise bedeutet das ja nur, dass man nicht davon ausgehen kann, dass die Liste modifiable ist. Ich finde, es wäre nicht unbedingt als Bruch der API anzusehen, wenn man dort eine modifiable zurückgibt - die Spezifikation soll ja eigentlich nur klarmachen, dass der Client davon ausgehen muss, dass sie unmodifiable ist, und wenn sich jemand (trotz der Doku!) darauf verläßt, dass sie modifiable ist, nur weil er einmal eine modifiable bekommen hat, ist er eigentlich selbst schuld... aber natürlich würde man der Hygiene halber eine unmodifiable zurückgeben).

... also konkret: Sollte man (auf so einen Kommentar hin) tatsächlich einen Test schreiben, der prüft, ob die zurückgegebene Liste bei ALLEN (!) modifizierenden Methoden (und bei einem Iterator#remove ihres Iterators) eine "UnsupportedOperationException" wirft? Müßte man ja ... eigentlich, wenn man es richtig machen wollte ... ???:L (Das fühlt sich nur so schrecklich irrelevant an... :( )
 

Ark

Top Contributor
Ich achte eher darauf, die Schnittstellen möglichst sauber in (meist imaginären :D) Javadoc-Kommentaren zu definieren. Was da nicht zugesichert wird, gibt's nicht, fertig. ;) Entsprechend nutze ich Schnittstellen nur so, wie sie dokumentiert sind. Kurzum: Ich teste nicht primär, sondern versuche stattdessen schon beim Schreiben von Code jede Einzelheit zu bedenken, die einen Einfluss darauf haben könnte, ob ich meine Zusicherungen erfüllen werde oder nicht. Nach vielen so geschriebenen Zeilen kommt es dann irgendwann tatsächlich zum ersten Start des Programms - und zur ersten NPE. :D Wenn ich da also Fehler mache, dann eher so Fehler bei der Initialisierung von irgendetwas, die dafür sorgen, dass der Prozess mit Pauken und Trompeten vom Betriebssystem einkassiert wird. ^^ Diese Fehler räume ich dann zügig aus, darauf explizit testen würde ich wohl aber eher nicht.

Grobe API-Design-Fehler halte ich für gefährlicher als schlechte Implementierungen, denn Letztere zeugen von Ersterem und erschweren Fehlerkorrekturen - so meine bisherige Erfahrung. Und da helfen auch keine Unit-Tests.

Gründlichere Tests mache ich eher nur bei Dingen, bei denen Fehler z.B. einen schlecht behebbaren Datenverlust nach sich ziehen könnten, und - im Unterschied zum ersten Absatz - bei Stellen, bei denen schon einmal schwerwiegende Fehler aufgetreten sind. Zum Test, ob ein solcher Fehler tatsächlich durch eine Änderung behoben wird, schreibe ich dann mal einen Unit-Test. Falls ich später wieder mit dem Fail-Kandidaten zu tun habe, kann ich mit dem Test gleich testen, ob ich's wieder vergeigt habe. ^^

Also: NPE etc. ? sofort korrigieren, kein eigener Test : schwerer Fehler aufgetreten ? Unit-Test machen, Fehler korrigeren (bis Unit-Test das Okay gibt) : noch keinen Unit-Test machen

So ungefähr mache ich das meistens.

[OT]Gibt es Testframeworks, mit denen man Concurrency-Problemen auf die Schliche kommen kann? So von wegen: Probiere alle möglichen Wettlaufsituationen aus, die entstehen können, wenn n Threads an jeweils denundden Stellen hinreichend lange warten.[/OT]
Ark
 
B

bygones

Gast
Die Frage nach dem was man nun so alles testen muss ist ein Problem von Testafter. Entwickelt man Testgetrieben, so hat man schon die Tests fuer seine Funktionalitaet so wie sie ist.

Eine generelle Antwort noch auf deine Frage:

Test sind fuer mich use-cases, sie bestimmen meine Implementierung, meine Schnitstellen. Sie sind Dokumentation fuer den Entwickler. Die Verantwortung und Funktionalitaet einer Klasse sind durch Tests intuitiver als in einer externen Doku.

den Fehler den viele machen ist die Tests als die Wahrheit zu sehen. Tests stellen nur das momentanen Wissen eines Systems da, sie sind fehlerhaft und nicht vollstaendig - wie jeder andere Javacode. Aber auch hier ist wieder Testfirst ein immenser Vorteil.

Deine Methode getFoo ist etwas ungluecklich in meinen Augen. Testafter ist sie unsinnig - es heisst sie ist nie null - soll man nun alle moeglichen Szenarien erstellen um das zu zeigen? unsinn.
Testfirst ist diese Methode aufgrund einer Notwendigkeit entstanden und genau diese hat auch den Testcase gegeben. Sagt dieser dass die Liste nicht null sein darf, so kann das ein testcase sein ja.

Aber wie schon gesagt, Tests sind fuer mich eine gewisse Sicherheit, Use-case beschreibung und Dokumentation.

genug erstmal... kind quakt :D
 
A

Andgalf

Gast
aber... ab einem bestimmten Punkt finde ich (auch), dass die Wahrscheinlichkeit, dass der Test fehlerhaft ist, höher ist, als die Wahrscheinlichkeit, dass die Implementierung fehlerhaft ist...

Der Argumentation kann ich so nicht folgen. Mit dem Test dokumentiert der Entwickler ja wie sich der produktive Code verhalten soll oder wie er sich seiner Meinung nach verhält, wie kann der Test da falsch / fehlerhaft sein?

Wenn ein Test nun doch fehlerhaft ist, war entweder die ursprüngliche Annahme des Enticklers über die funktionsweise des Codes falsch oder die Spezifikation hat sich geändert.

Im ersten Fall, finde ich es gut zu wissen/erkennen, dass die Annahme falsch war. Meist findet man in dem Zusammenhang andere/weitere Bugs.
Im zweiten Fall ist es ja nur natürlich, das "Wartungsaufwand" entsteht.
 
A

Andgalf

Gast
Ich achte eher darauf, die Schnittstellen möglichst sauber in (meist imaginären :D) Javadoc-Kommentaren zu definieren. Was da nicht zugesichert wird, gibt's nicht, fertig. ;)

Hier nimmst Du mit dem Schlüsselwort "imaginären" eigentlich die Antwort schon vorweg.
Zusätzlich ist es jedoch so, das Javadoc Kommentare zum "verfaulen" neigen. Wenn die Funktionsweise des Codes angepasst wird, wird oft vergessen den Kommentar entsprechend anzupassen und einen falschen Kommentar finde ich sogar noch schlimmer als einen nicht vorhandenen.

Bei Tests kann dir das im Prinzip nicht passieren, denn wenn sich die Funktionsweise des Codes ändert schlägt daraufhin der entsprechende Test fehl und muss angepasst werden. (Continuous Integration setze ich vorraus)
 

Ark

Top Contributor
Wenn ein Test nun doch fehlerhaft ist, war entweder die ursprüngliche Annahme des Enticklers über die funktionsweise des Codes falsch oder die Spezifikation hat sich geändert.
Es könnte aber auch ganz einfach sein, dass man in den Test einen Fehler eingebaut hat. Dieser Fall ist trivial, denn wenn man keinen Fehler in einen Test einbauen könnte, dann auch nicht in irgendeinen Quelltext, wenn man diesen nur als Test bezeichnet. ;)

Oder anders ausgedrückt: Unit-Tests sind auch nur Codestücke, und diese können (wie jedes andere Codestück auch) Fehler enthalten.

Man könnte natürlich auch noch einen Test schreiben, der den Test testet. Und dieser Test-Test kann dann natürlich auch getestet werden … :reflect:

Ark
 
A

Andgalf

Gast
Es könnte aber auch ganz einfach sein, dass man in den Test einen Fehler eingebaut hat.

OMG, über welche Art Fehler reden wir denn hier? Wohl kaum über Compile-Fehler. Insofern ist jeder Fehler auf irgendeine Art eine fehlerhafte Annahme ob nun im Test oder sonstwo. Und mit den Tests dokumentiert man halt die über den produktiven Code gemachten Annahmen.
 
Zuletzt bearbeitet von einem Moderator:
J

JohannisderKaeufer

Gast
OMG, über welche Art Fehler reden wir denn hier? Wohl kaum über Compile-Fehler. Insofern ist jeder Fehler auf irgendeine Art eine fehlerhafte Annahme ob nun im Test oder sonstwo. Und mit den Tests dokumentiert man halt die über den produktiven Code gemachten Annahmen.

Zu Compile Fehlern: Java wird kompiliert, wenn du allerdings eine Skriptsprache hast, wie beispielsweise Ruby, dann hast du diese Gewissheit des Compilers nicht mehr und erhältst Laufzeitfehler die dir in einer anderen Sprache schon beim Compilieren aufgefallen wären.


Bei Stackoverflow gibt es auch eine Interessante Aussage von Kent Beck

tdd - How deep are your unit tests? - Stack Overflow


Kurzum: Testen bis das Budget aufgebraucht oder die Deadline erreicht ist.
 
A

Andgalf

Gast
Zu Compile Fehlern: Java wird kompiliert, wenn du allerdings eine Skriptsprache hast, wie beispielsweise Ruby, dann hast du diese Gewissheit des Compilers nicht mehr und erhältst Laufzeitfehler die dir in einer anderen Sprache schon beim Compilieren aufgefallen wären.
Stimmt über interpretierte Sprachen habe ich da nicht nachgedacht

Bei Stackoverflow gibt es auch eine Interessante Aussage von Kent Beck
Interessant, so eine Aussage ausgerechnet von Kent Beck zu lesen :eek: Sind aber auch ein paar spannende Kommentare drunter .... "because the code a developer produces is not his own"
 
Zuletzt bearbeitet von einem Moderator:

Ark

Top Contributor
Zusätzlich ist es jedoch so, das Javadoc Kommentare zum "verfaulen" neigen. Wenn die Funktionsweise des Codes angepasst wird, wird oft vergessen den Kommentar entsprechend anzupassen und einen falschen Kommentar finde ich sogar noch schlimmer als einen nicht vorhandenen.
Kein Javadoc → keine Zusicherungen. Zumindest handhabe ich das so. Insofern sehe ich das genauso, dass kein Javadoc besser ist als ein falscher.

Was das "Verfaulen" angeht, sehe ich das etwas anders: Ich schreibe in Javadoc nicht, was eine Methode macht, sondern was sie machen sollte. Javadoc-Kommentare sind für mich sozusagen Spezifikation und Dokumentation in einem. Beim Schreiben von Code verlasse ich mich auf diese Kommentare. Wie die Implementierung aussieht, ist mir da egal. (Das mögen andere anders sehen. ^^) Dahingehend kann ein Javadoc-Kommentar also nicht der tatsächlichen Implementierung hinterherhinken, weil der Kommentar gar nicht von der Implementierung abhängt.

Wenn ich merke, dass da etwas nicht funktioniert, muss (sollte ;)) der Fehler an der richtigen Stelle behoben werden. Und wenn ich eben merke, dass der Fehler darin besteht, dass ich die falsche Methode aufrufe, muss ich eben eine geeignetere Methode aufrufen (oder erst implementieren). Aber ich werde keine Methode entgegen ihrer Spezifikation ändern, nur damit mein Code funktioniert oder irgendein Unit-Test durchläuft.

Kurzum: anstatt im Nachhinein lange in einer Debugging-Testing-Schleife zu hängen, versuche ich, Fehler gar nicht erst entstehen zu lassen. Und dies meine ich vor allem damit zu erreichen, Schnittstellen sauber festzulegen. Unit-Tests beschleunigen die Fehlersuche, falls man durch Ändern des Codes etwas kaputtgemacht haben sollte. Aber sie allein sagen mir nicht, wo die Ursache des zu behebenden Fehlers liegt. Da kann nur eine Spezifikation weiterhelfen. Und dadurch, dass ich mich nur auf diese Spezifikation verlasse, kann ich auch sichergehen, dass sich der Fehler auf diese Stelle beschränkt.

OMG, über welche Art Fehler reden wir denn hier?
Tippfehler.

Ark
 

Marco13

Top Contributor
@Ark: Teilweise stimme ich den genannten Zielen zu. Oder andersrum, etwas plakativ: Eine besch****ene API wird nicht dadurch besser, dass man sie gründlich Unit-Testet :D Die implizit angedeutete Strategie ""nur die Fehler zu beheben, die auch auftreten"" funktioniert für vieles, aber für manche Sachen ist es IMHO nicht ausreichend, wenn der "Testbericht" lautet: "Ich hab's gestartet, und es ging" ;)

[OT]
Gibt es Testframeworks, mit denen man Concurrency-Problemen auf die Schliche kommen kann? So von wegen: Probiere alle möglichen Wettlaufsituationen aus, die entstehen können, wenn n Threads an jeweils denundden Stellen hinreichend lange warten.

Tatsächlich gibt es Entwicklungen dazu (wird ja auch immer wichtiger!). Auf der parallel 2012 - Softwarekonferenz für Parallel Programming, Concurrency und Multicore-Systeme - Karlsruhe, 23.-24. Mai 2012 :: Agenda / Programm wurden da einige vorgestellt. Es gab mehrere Vorträge dazu, sowas wie parallel 2012 - Softwarekonferenz für Parallel Programming, Concurrency und Multicore-Systeme - Karlsruhe, 23.-24. Mai 2012 :: Testwerkzeuge für nebenläufige Anwendungen hatte ich mir mal angehört, und das klang schon ganz interessant (diese Tools haben natürlich Grenzen, aber gehen schon recht weit)
[/OT]

@bygones: "Die Frage nach dem was man nun so alles testen muss ist ein Problem von Testafter. Entwickelt man Testgetrieben, so hat man schon die Tests fuer seine Funktionalitaet so wie sie ist."
Hm... vielleicht liegt ein Mißverständnis vor, aber ... so sehe ich das erstmal nicht. Selbst bei TDD muss man seinen Test ja "gegen" etwas schreiben - irgendein gemocktes Interface (sooo kenn' ich mich damit nicht aus :oops: aber soweit ich das bisher verstanden hatte). Sowas wie ein null-Test ist ja nun der einfachste denkbare Fall. Wie würde es aussehen, wenn man z.B. ein "Collection"-Interface definieren wollte? Ja, [c]add,remove,size,contains...[/c] und bei List soll es sich anders verhalten als bei Set, und vielleicht soll es manchmal noch unmodifiable sein, und wenn man die Methoden in unterschiedlichen Reihenfolgen aufruft um ihr Verhalten (mit möglichst großer Abdeckung) zu testen ... wie man an dem im ersten Thread verlinkten Versuch eines gewissenhaften Tests sieht, explodiert der Aufwand für den Test (soweit ich das verstehe: auch bei TDD) da ja recht schnell. (Und BTW: Sieht man einem Test an, ob er durch TDD oder durch 'Testafter' entstanden ist? Eigentlich doch nicht...!?)

@Andgalf: "Mit dem Test dokumentiert der Entwickler ja wie sich der produktive Code verhalten soll oder wie er sich seiner Meinung nach verhält, wie kann der Test da falsch / fehlerhaft sein?"
Das klingt ein bißchen wie das, wobei ich mich auch schon ertappt habe: Ich schreibe eine Klasse, definiere nachher den Test, lasse den Test laufen - und er failt - und dann passe ich den Test an, weil die Implementierung ja richtig ist, was ich schon vorher mit System.out's überprüft habe ;) (So sehr das TDD-Fans im ersten Moment vielleicht weh tut, es ist IMHO schon sehr sinnvoll - wiederum weil ab einem bestimmten Punkt ja egal sein kann, wie ein Test entstanden ist). Ansonsten muss ich Ark zustimmen: Wenn man in einer Implementierung mit 100 Zeilen einen Fehler macht, wäre es schon fast naiv zu glauben, dass man in einem dazugehörigen Test mit 400 Zeilen keinen Fehler macht. Insbesondere bezieht sich "Fehler" hier nicht auf eine NPE oder irgendeinen offensichtlich falschen Test der dann failt, sondern, wie du auch selbst angedeutet hast, auf genau die Fehler, die man nicht bemerkt: Unzulänglichkeiten und unbewußt gemachte Annahmen, die eben eine andere (aber immernoch "richtige") Implementierung failen lassen, oder schlimmer, eine "falsche" Implementierung durchlassen, weil gerade der fehlerhafte Punkt schlicht nicht getestet wird. Zuletzt klang das jetzt schon fast als wolltest du propagieren, dass ein nicht der Spezifikation entsprechendes Verhalten dadurch "zur Spezifikation erhoben" wird, weil anfangs mal ein Unit-Test geschrieben wurde, der genau dieses unspezifizierte Verhalten erwartete ???:L aber das meintest du wohl nicht ;)

Nochmal @Ark: Ich versuche auch so weit wie möglich diesem an "Design by Contract" angelehnten Prinzip zu folgen, und ein Teilaspekt davon ist zumindest das Bemühen um eine gewissenhafte JavaDoc-Dokumentation. Trotzdem gehen Unit-Tests (und der Nutzen, der damit verbunden sein sollte) ja deutlich darüber hinaus. Man kann, wenn man so eine Klasse wie "ArrayList" schreibt, und dort die Methode "subList" anbietet noch so genau schreiben, was die Methode macht: Ob sich das zurückgegebene Objekt wirklich konform zur Spezifikation von "List" verhält, kann man mit letzter Gewissheit nur mit einem erschöpfenden Unit-Test sagen - und ich behaupte: Selbst das nur theoretisch, solange es kein formales Modell gibt, mit dem man mathematisch streng beweisen kann, dass alle (bisher leider bestenfalls als Prosa in den JavaDoc stehenden) Vor- und Nachbedingungen erfüllt sind. Insofern bringen Unit-Tests nur "etwas mehr" Sicherheit, die bei "pragmatischer Gewissenhaftigkeit" eben damit bezahlt wird, dass man SEHR viel Zeit investiert und SEHR viel Code schreibt, dessen mittelbarer oder unmittelbarer Nutzen für mich kaum im Verhältnis zum Aufwand steht.

Die Idee von Unit-Tests ist schön, und der Gedanke, damit Debugging-Zeit zu sparen und sauberere, verlässlichere Software zu schreiben und sie leichter warten zu können ist verlockend, aber wie man Unit-Tests gestalten sollte, um wirklich diesen Effekt zu haben, ohne dass man etliche Tausend Zeilen stupide runterschreiben muss (und am Ende doch nicht zu wissen, ob sie diesen Zweck überhaupt erfüllen) hat sich mir noch nicht erschlossen. Die Vorgehensweise: "Ja, ich teste die Klassen, die ich für wichtig halte, so, wie ich es für richtig halte, und teste dabei das, wovon ich glaube, dass es schiefgehen könnte" wirkt irgendwie nicht so überzeugend, und setzt einen der Gefahr aus, dass man sich von anderen (oder sich selbst) vorwerfen lassen muss, dass die vielen grünen Balken nur ein Potemkinsches Dorf sind, mit dem man sein Gewissen beruhigen und im oberflächlichen Sinn seine Selbstzweifel beseitigen will. (Das klingt jetzt vielleicht als wollte ich Unit-Tests diskreditieren - genau das will ich aber NICHT - ich will nur wissen, wie man sie "richtig" oder "vernünftig" macht, ohne dass man den größten Teil seiner Zeit mit Testcodeschreiben verbringt :rtfm: )
 
S

Spacerat

Gast
Ich würde glatt mal nach fragen, wer diese Unit-Tests letztendlich überhaupt macht. Wenn man so wie Ark vorgeht, entwickelt man seine Methoden doch eigentlich ohnehin spezifikationskonform, zumindest nimmt man das selbst an und sagt, die Methode arbeitet korrekt. Ich würde solche Unit-Tests eher als Exploits betrachten, denn solange einer failt, ist diese Stelle angreifbar. Evtl. kommt diese Idee ja auch aus dieser Szene (Hacker, Cracker usw.), weil Serverausfälle durch Hackangriffe viel teurer werden, als wenn man vorher selber testet bzw. testen lässt, welch' Schindluder mit den eigenen Entwicklungen getrieben werden kann. Unit-Tests sind aus meiner Sicht demnach erst dann erforderlich, wenn eine Implementation mit sensitiven Daten umgehen muss. Unit-Tests sollten sich deswegen auch nicht unbedingt strikt an irgendwelche Spezifikationen halten, sondern eher ein "Ich krieg' dich schon klein!" anstreben.
Kurzum: Untit-Tests sollten auch bzw. vorzugsweise die Dinge testen, die nicht in der Spezifikation stehen aber genau dann failen. Es gibt also keine fehlerhaften Unit-Tests.
Entwickler wären demzufolge auch selbst gar nicht in der Lage, sinnvolle Unit-Tests für ihre eigenen Methoden zu schreiben.
[EDIT]BTW.: @Marco13: Ich weis gar nicht, wieso du mit nicht bygones Meinung jedoch mit Arks Vorgehen konform gehst. Ist Arks Vorgehen etwa nicht das, was man hinlänglich als "Testdriven" bezeichnet?[/EDIT]
 
Zuletzt bearbeitet von einem Moderator:
G

Guest2

Gast
Moin,

Ich schreibe eine Klasse, definiere nachher den Test, lasse den Test laufen - und er failt - und dann passe ich den Test an, weil die Implementierung ja richtig ist, was ich schon vorher mit System.out's überprüft habe ;)

einer meiner Profs meinte mal, wenn man sich selbst dabei ertappt ein System.out einzutippen es allerhöchste Zeit für einen passenden Unit-Test wird, sonnst fängt man schlicht an Zeit zu verschwenden. Und letztendlich ist es auch so, das schreiben eines Unit-Tests dauert nicht viel länger als das schreiben eines System.out. Gleichzeitig bleibt der Unit-Test für "immer" während das System.out kurzzeitiger "Unsinn" ist. Wenn das schreiben des Unit-Tests länger dauert als das System.out, liegt imho die Vermutung nahe das man unter umständen gerade einen verkappten IT-Test schreibt.

Persönlich strebe ich bei Unit-Tests auch nicht unbedingt eine besonders hohe Testabdeckung an. Lieber sind mit IT-Tests, welche die komplette Anwendung oder Bibliothek mit einem Soll-Verhalten vergleichen. Solange die ohne Fehler laufen, gehe ich davon aus, dass der Code im Grunde in Ordnung ist. Wenn nicht, lokalisiere ich den Fehler durch das einstreuen weiter IT- oder Unit-Tests, bis ich den Fehler lokalisiert habe. Und eben nicht durch das einstreuen von System.outs (zumindest nehme ich mir das immer vor :D).

Außerdem sind die IT-Tests perfekte Beispiele für die Dokumentation. Letztendlich sind die meisten meiner IT-Tests nämlich eben genau einfache Beispiele, nur das sie automatisiert ausgeführt werden können und es ein Soll-Verhalten gibt.

Viele Grüße,
Fancy
 

FArt

Top Contributor
Ein Entwickler entwickelt in der Regel mit der Zeit ein Gefühl dafür, welche Stellen wie intensiv zu testen sind, genau wie auch bei Logging, welche Informationen wann und wo in welchem Loglevel interessant sind. Es verhält sich also wie Code allgemein: mit der Zeit schreibt man (hoffentlich) immer besseren Code und Tests.

Ich schreibe Tests für die öffnentliche Schnittstelle vor der Implementierung, um die Anforderungen noch mal klarzustellen und evtl. Lücklen aufzudecken, die dann im Vorfeld noch geklärt werden müssen. Das ist schon mal eine Art Schnittstellendokumentation und muss sich mit allen funktionalen und nichtfunktionalen Anforderungen decken (auch wenn man diese u.U. nicht alle abprüfen kann). Für andere, kleiner Funktionalitäten schreibe ich dann (oft während der Implementierung der Funktionalität) abgeschlossene Tests.

Tests schreiben ist ein iterativer Prozess, genau wie entwicklen an sich, d.h. im Laufe der Zeit werden immer mehr Tests hinzukommen, manche fallen u.U. weg. In der Regel versuche ich jeden Bug mit einem Test zu reproduzieren und dann zu fixen. Das steigert die Codequalität sehr, und man ist sich bei Refaktorierungen eingigermaßen sicher, dass man nichts wesentliches kaputt gemacht hat.

Tests sind aber nur so gut, wie sie praktikabel sind und auch berücksichtigt werden. Wer z.B. fehlschlagende Tests über längere Zeit (z.B. im Jenkins) ignoriert, braucht keine Tests zu schreiben. Wenn die Tests zu lange laufen, wird sie der Entwickler nicht regelmäßig ausführen. Somit sind sie auch nicht mehr sehr wertvoll.
Bei uns kann z.B. keine Release gebaut werden, wenn nicht alle Tests laufen. Eine sinnvolle "Testabdeckung" wird gleichzeitig über Metriken und Reviews sichergestellt.
 
S

Spacerat

Gast
:idea: Oi... jetzt steige ich anscheinend auch langsam dahinter. Unit-Tests sollen einem im Vorfeld sagen, was eine Methode können muss und was sie nicht können darf. Das wäre dann auf jeden Fall was ganz anderes als "Schadensbegrenzung" im Fehlerfall. Klar... in seiner Verzweiflung hilft einem dann evtl. doch noch ein "System.out", wenn man dass, was der Unit-Test abverlangt nicht gedeichselt bekommt. Die "Unit-Tests", die nach dem Implementieren der Methode entstehen (maw.: wie ich sie bisher sah), können dann wohl auch Sache der Hacker bleiben.
 
A

Andgalf

Gast
:idea: Oi... jetzt steige ich anscheinend auch langsam dahinter. Unit-Tests sollen einem im Vorfeld sagen, was eine Methode können muss und was sie nicht können darf. Das wäre dann auf jeden Fall was ganz anderes als "Schadensbegrenzung" im Fehlerfall.

Korrekt, das ist das Prinzip hinter TDD. Wenn man TDD korrekt anwendet, sorgt dies auch für ein schlankes Design, da man ja immer nur genau so viel Code produziert bis alle Tests "grün" sind. Danach wird halt der nächste Test geschrieben, der ein weitere Eigenschaft/Funktionalität absichert die der Code einhalten soll. Naturgemäß ist dieser Test erstmal rot, da man ja Test first vorgeht.

.... mom was mach ich hier eigentlich ich wollte doch gar nicht TDD erklären, dafür gibbet Bücher :oops:

Auf jeden Fall muss ich FArt hier zustimmen:

Das steigert die Codequalität sehr, und man ist sich bei Refaktorierungen eingigermaßen sicher, dass man nichts wesentliches kaputt gemacht hat.

Seit ich bzw. wir Testdriven arbeiten, gibt es keine "Angst" mehr vor Refactorings ... Da wir mit den Tests überprüfen können, ob das ursprüngliche Verhalten des Codes sich nicht verändert hat.
 
B

bygones

Gast
[WALL OF TEXT] ich will nur wissen, wie man sie "richtig" oder "vernünftig" macht, ohne dass man den größten Teil seiner Zeit mit Testcodeschreiben verbringt :rtfm: )
interessanter weise wird testcodeschreiben immer als das Boese betrachtet, waehrend code schreiben das Gute ist. Beides ist code. TDD ist nun mal eben dass man einen grossteil seiner Zeit mit Testschreiben verbringt, aber ist das nun das Problem oder nicht ? Ich denke deine Frage zieht er auf das sinnvolle schreiben bzw. effiziente Schreiben von Tests ab. Und da ist es wie bei jedem code - learning by doing, akzeptieren, dass man eine Schwachstelle hat und daran arbeiten. Je mehr man es macht, desto schneller und besser wird man. Anfangs bremst einen das aus, und das verteufeln dann die meisten, da der Langzeiteffekt missachtet wird.

sorry dass ich nicht auf den ganzen text antworte, ist a) zu viel und b) halte ich solche Diskussion fuer ermuedend. Jede Seite hat Bsp wie und warum es klappt/nicht klappt. Theoretisch etwas zu sagen ist immer leicht. Am sinnvollsten waere es sich zu 2. vor einem Rechner zu hocken und einige Pair-Programming-TDD stunden zu verbringen. Alles andere ist fragwuerdig imo.

Nur noch zu deiner Frage
"(Und BTW: Sieht man einem Test an, ob er durch TDD oder durch 'Testafter' entstanden ist? Eigentlich doch nicht...!?)"
ich behaupte ja (die ausnahmen bestaetigen das). TDD test code ist intuitiver, kuerzer und verstaendlicher.

:idea: Oi... jetzt steige ich anscheinend auch langsam dahinter. Unit-Tests sollen einem im Vorfeld sagen, was eine Methode können muss und was sie nicht können darf. Das wäre dann auf jeden Fall was ganz anderes als "Schadensbegrenzung" im Fehlerfall. Klar... in seiner Verzweiflung hilft einem dann evtl. doch noch ein "System.out", wenn man dass, was der Unit-Test abverlangt nicht gedeichselt bekommt. Die "Unit-Tests", die nach dem Implementieren der Methode entstehen (maw.: wie ich sie bisher sah), können dann wohl auch Sache der Hacker bleiben.
jo - TDD erstellt dir Tests die zeigen, was du machen wolltest, Testafter zeigt dir was du tust. Klingt aehnlich, aber ist im Detail womoeglich ein welten unterschied.
 

Marco13

Top Contributor
@Spacerat: Vielleicht gibt es da subtile Unterschiede in den Begrifflichkeiten. Aber das von Ark beschriebene klang für mich (Ahnungslosen :D ) eher nach "Design by Contract" als nach dem von bygones angesrochenen TDD. Wenn ich das richtig verstanden habe, kamen bei ersterem ja keine echten Unit-Tests drin vor, sondern "nur" eine detaillierte Beschreibung und Spezifikation und deren gewissenhafte Implementierung (wobei sich beides natürlich nicht gegenseitig ausschließt).

Die Beschränkung von Unit-Tests auf sensitive Bereiche sehe ich aber auch nicht so. Ich finde (vielleicht weil es bei mir konkret um etwas ähnliches geht) die Collections ein gutes Beispiel: Man definiert das "Map"-Interface, und implementiert dann die HashMap. Und dann implementiert man die TreeMap, und will schnell sicherstellen, dass sie der Spezifikation genügt (und zwar im strengeren Sinne als: "Ich hab bei der Implementierung ganz doll aufgepasst"). Und das gleiche dann für LinkedHashMap und alle anderen Implementierungen die es gibt und noch geben wird. Und wenn man irgendwo was ändert, läßt man nur noch alle Tests durchlaufen, und sieht, dass man nichts kaputt gemacht hat.

Vielleicht liegt eine der Ursachen für die unterschiedlichen Ansichten in genau diesen konkreten Zielen: Es ging mir weniger um Unit-Tests von irgendwelchen package-privaten, finalen Klassen, sondern eher (aber nicht ausschließlich!) um eine Art Konformitätstest verschiedener Implementierungen eines öffentlichen Interfaces. Und das kann auch derjenige entwickeln, der das Interface defniert, indem er ""alle"" Verhaltensweisen überprüft, die in Interface festgelegt sind.


@Guest2: Diese Aussage mit "Unit-Test statt System.out" hatte ich bei meinen ersten Websuchen dazu auch irgendwo gelesen. Üblicherweise würde ich bei sowas sagen: "Ich finde das unrealistisch". Hier gehe ich so weit zu sagen: "Das IST unrealistisch" (aber vielleicht bewußt, übertrieben, um dem entgegenzuwirken, dass jemand den Aufwand, den man für einen Unit-Test betreiben könnte, stattdessen in einen Haufen System.out-Tests steckt).
Was genau du mit IT-Tests meinst, ist mir nicht ganz klar. Für mich war es schon /bevor ich wirklich an Unit-Tests dachte) selbstverständlich, dass ich zu "komplizierten" Klassen/Methoden eine main erstelle, die diese Funktionalität ganz isoliert (mit System.outs und ggf. dummy-Daten) prüft. Das ist (und wäre auch bei ausführlichen Unit-Tests) einfach Teil meiner Strategie: Wenn man von Anfang an die API benutzt, die man da gerade entwickelt, sieht man, wo es hakt, und das was rauskommt sind gleich praktische Codesnippets. Aber Unit-Tests hätten eben eine höhere Verläßlichkeit (wenn sie systematisch erstellt werden, und viel abdecken) und Wiederholbarkeit, dadurch dass sie automatisiert ausgeführt werden können.

@FArt: Ich schreibe Tests für die öffnentliche Schnittstelle vor der Implementierung, um die Anforderungen noch mal klarzustellen und evtl. Lücklen aufzudecken, die dann im Vorfeld noch geklärt werden müssen. Das ist schon mal eine Art Schnittstellendokumentation und muss sich mit allen funktionalen und nichtfunktionalen Anforderungen decken (auch wenn man diese u.U. nicht alle abprüfen kann). ... Tests schreiben ist ein iterativer Prozess, ...

Das klingt realistisch und praktikabel - aber (sorry) eben auch ein bißchen "hilflos". Natürlich kann einem kein Kochbuch das Denken abnehmen - jeder Entwickler hat da eine Verantwortung, die er wahrnehmen muss. Aber es gibt bei dieser Beschreibung einige Freiheitsgrade, die im schlimmsten Fall ausgenutzt werden können, und die Unit-Tests ad absurdum führen.
Java:
/** ...Returns an unmodifiable List of even numbers, never null... */
List<Integer> getEvenNumbers()  { ... }

void testEvenNumbers()
{
    assertNotNull(someObject.getEvenNumbers());
}
Wenn so ein Test durchläuft, sagt das ja nichts aus. Man müßte noch prüfen, ob die Zahlen even sind
Java:
void testEvenNumbers()
{
    assertNotNull(someObject.getEvenNumbers());
    for (Integer i : someObject.getEventNumbers()) assertIsEven(i);
}
Auch das sagt nichts aus. (Mal abgesehen davon, dass man nicht weiß, ob beim ersten Aufruf dieSELBE Liste zurückgegeben wurde, wie beim zweiten :autsch: ) Aber testen wir noch die nicht-modifizierbarkeit
Java:
void testEvenNumbers()
{
    List<Integer> list = someObject.getEventNumbers()
    assertNotNull(list);
    for (Integer i : list) assertIsEven(i);
    assertThrowsException(list.add(2));
    assertThrowsException(list.remove(0));
    assertThrowsException(list.removeAll(...));
    ...
}
Und den Iterator nicht vergessen:
Java:
void testEvenNumbers()
{
    List<Integer> list = someObject.getEventNumbers()
    assertNotNull(list);
    for (Integer i : list) assertIsEven(i);
    assertThrowsException(list.add(2));
    assertThrowsException(list.remove(0));
    assertThrowsException(list.removeAll(...));
    ...
    Iterator<Integer> iterator = list.iterator();
    assertThrowsException(iterator.remove());
}
Gut, den ersten Eintrag kann man mit dem Iterator schonmal nicht entfernen. Vielleicht aber den zweiten? Ja, ich weiß, spätestens jetzt wird's völlig absurd :autsch: ... aber wäre nicht NUR so etwas ein wirklich systematischer und gewissenhafter Test?

Nochmal anschauen wie die Methode standardmäßig implementiert ist:
Java:
List<Integer> getEvenNumbers()  
{
    return Arrays.asList(2,4,6); 
}
Tja, das lohnt sich dann ja nicht... Außer wenn jemand irgendwann die Methode mal implementiert als
Java:
List<Integer> getEvenNumbers()  
{
    return new SpecialListWhereTheLastElementMayBeRemovedWithAnIterator(2,4,6);
}
oder (naheliegender) als
Java:
List<Integer> getEvenNumbers()  
{
    return Arrays.asList(2,null,6); // Darüber sagt die JavaDoc nichts...
}
und das dann zu Problemen führt.

Bevor da zu sehr drauf rumgehackt wird: Das Beispiel dient nur der Verdeutlichung - im Sinne der Frage im von JohannisderKaeufer verlinkten Stack: Wie tief sind Unit-Tests? .... Und es scheint: Im Zweifelsfall wohl sehr oberflächlich, weil alles andere nicht praktikabel wäre. Ähnlich wie der Kommentar von Kent Beck dort, der ein Zeichen dafür ist, dass wir wirklich in dem im ersten Beitrag erwähnten "Tal der Tränen" stecken.

Jedenfalls erscheint mir das als einziger praktikabler Mittelweg zwischen "gar keine Tests" und "Uber-Tests (wie die angedeuteten)" : Man schreibt oberflächliche Tests für die öffentliche API, und geht nur dort mit Tests (iterativ) weiter in die Tiefe, wo Probleme auftreten. Ich finde aber, dass das den theoretischen Nutzen und die durch solche Tests eigentlich angestrebte Verläßlichkeit praktisch auf null reduziert, und sie "nur" zu reinen Regressionstests werden... :bahnhof:


EDIT: Die neue "Wall of Text" hat sich mit einigen Antworten überschnitten.

@bygones: "Und da ist es wie bei jedem code - learning by doing, akzeptieren, dass man eine Schwachstelle hat und daran arbeiten."

Ja, so hatte ich das jetzt auch mitgenommen. Nicht wirklich zufriefenstellend, aber ... damit wird man sich wohl (bisher noch) abfinden müssen.
 
Zuletzt bearbeitet:
B

bygones

Gast
dein Bsp ist ein bsp fuer einen unsinnigen test.

deine Methode sagt aus [c]testEvenNumbers()[/c], d.h. sie soll testen, dass deine liste gerade zahlen beinhaltet. Mehr nicht.

Es gibt eine Faustregel fuer tests: es gibt ein assert. mehrere Asserts bedeuten meist dass die Methode zu viel macht. Auch testmethoden folgen dem SingleResponsibility Prinzip. Wenn du meinst alle die anderen Funktionalitaeten auch testen zu wollen/muessen, so gehoeren sie in ihre eigene Methode.

weiterhin ist der test fragwuerdig, da du nicht deinen Code testest sondern die java collection, ergo fremden code. (mag natuerlich nur dem bsp geschuldet sein).

Ich seh allgemein die Vorangehensweise anders. Eine Schnittstelle evolviert aus einer Implementierung. Die Tests bestimmen meine Implementierung und diese zeigt dann die Schnittstelle auf. Fuer mich ist der andere Weg zu vorausdenkend und voller Annahme, was wohl eine Schnittstelle wie braucht. Das weiss man aber erst, wenn man use-cases umgesetzt hat. Ergo. Tests bestimmen die Implementierung, die mir dann meine Schnittstelle aufzeigen.

Fuer viele ist diese Weise anscheinend zu revolutionaer und entgegen die (jahre)lange Arbeitsweise, als dass sie das nicht akzeptieren, probieren wollen. Dann braucht man sich nicht wundern, wenn man in Tests und TDD keinen Vorteil sieht. Dann ist es aber ebenso unsinnig sich darauf zu versteifen.

@bygones: "Und da ist es wie bei jedem code - learning by doing, akzeptieren, dass man eine Schwachstelle hat und daran arbeiten."

Ja, so hatte ich das jetzt auch mitgenommen. Nicht wirklich zufriefenstellend, aber ... damit wird man sich wohl (bisher noch) abfinden müssen.
ernsthaft - was hast du erwartet ?
 
Zuletzt bearbeitet von einem Moderator:

Marco13

Top Contributor
deine Methode sagt aus testEvenNumbers() , d.h. sie soll testen, dass deine liste gerade zahlen beinhaltet. ... Es gibt eine Faustregel fuer tests: es gibt ein assert. mehrere Asserts bedeuten meist dass die Methode zu viel macht. ... weiterhin ist der test fragwuerdig, da du nicht deinen Code testest sondern die java collection

Das Beispiel war natürlich suggestiv und unsinnig, aber sollte verdeutlichen, dass ich mir über die "Reichweite" bzw. eben Tiefe der Tests nicht im Klaren bin. Natürlich waren die meisten Teile dieses Tests eigentlich Tests für eine UnmodifiableList. Aber wenn man sagt, dass man dort eine UnmodifiableList zurückgibt, müßte man eigentlich testen, ob das wirklich der Fall ist ... :bahnhof:

(Vielleicht ist das mit dem "Unmodifiable" dort als Beispiel auch nicht so geeignet. Man könnte versuchen den Punkt zu verallgemeinern zu "irgendeine Zusicherung, die in der Ppaxis sehr schwer zu überprüfen ist", aber das würde klingen, als würde dort beim Design noch anderes im Argen liegen...)


Ich seh allgemein die Vorangehensweise anders. Eine Schnittstelle evolviert aus einer Implementierung. ...

Hm... das driftet vielleicht jetzt ab, aber ... ich dachte immer, dass ein Teilaspekt von TDD das Testen eines Interfaces ist - nicht notwendigerweise (aber naheliegenerweise) eines Java-Interfaces, sondern wirklich der Schnittstelle. Ich sehe es aber auch so, dass man dieses nur definieren kann, wenn man sich über dessen Verwendung im Klaren ist. Und ob man sich darüber durch das Vorab-Erstellen von Unit-Tests klar werden kann, oder vielleicht doch nur durch das praktische Erstellen von "KSKBs" ist eigentlich ein anderes Thema.


ernsthaft - was hast du erwartet ?

Einen Thread, der so ähnlich aussieht, wie dieser hier :D Dass es nicht eine 1-Zeilen-Antwort gibt, die alle Fragen klärt und jeden perfekt zufrieden stellt, ist klar. Aber die allgemeine Frage, wie detailliert (oder "tief") Unit-Tests sein sollten fand und finde ich berechtigt, und es ist gut, verschiedene Standpunkte dazu zu hören.

Um das nochmal zu erklären: Ich will eine Bibliothek entwickeln (bzw. habe das schon teilweise), die gewisse Parallelen zu den Collections hat: Einige überschaubare Interfaces, mit teilweise recht spezischen Zusicherungen in den JavaDocs, und einige verschiedene Implementierungen dazu. Ich würde gerne testen, ob alle Implementierungen sich konform zur Spezifikation verhalten. Wenn man dann aber (an dem 'MapInterfaceTest' aus Guava) sieht, wie exorbitant aufwändig schon ein Test für ein einziges relativ einfaches(!) Interface ist, wenn man es gewissenhaft machen will, fällt es mir schwer, mir vorzustellen, wie man das für komplexere Interfaces in der Praxis (und ohne einen 200-Milliarden-Dollar-Konzern im Rücken ;)) tatsächlich machen sollte. Und dieser Thread hat mich jetzt der Überzeugung näher gebracht, dass das schlicht nicht geht, und dass man einige Freiheiten hat, und sich nicht unbedingt Vorwürfe anhören werden muss, wenn der Test nicht "perfekt und allumfassend" ist, solange er "gut" und "zweckmäßig" ist.
 
M

maki

Gast
Hm... das driftet vielleicht jetzt ab, aber ... ich dachte immer, dass ein Teilaspekt von TDD das Testen eines Interfaces ist - nicht notwendigerweise (aber naheliegenerweise) eines Java-Interfaces, sondern wirklich der Schnittstelle. Ich sehe es aber auch so, dass man dieses nur definieren kann, wenn man sich über dessen Verwendung im Klaren ist. Und ob man sich darüber durch das Vorab-Erstellen von Unit-Tests klar werden kann, oder vielleicht doch nur durch das praktische Erstellen von "KSKBs" ist eigentlich ein anderes Thema.
umgekehrt wird ein Schuh draus ;)

Bei TDD werden keine Schittstellen gestestet, sondern Implementierungen, also White Box Tests.
Man "mockt" Abhängigkeiten "weg" indem eben Mock-Implementieruengen für externe Abhängigkeiten verwendet werden, die meist als Interfaces bestehen(oder eben gerade durch TDD "entdeckt" wurden - "Interface Discovery", da wird TDD zum Designtool), aber die testet man eben nicht, sondern man verwendet/entdeckt sie im test.

Das witzige bei TDD ist u.a., dass man sich über die Verwendung externer Abhängigkeiten erst während des schreibens klar wird, man definiert diese Dinge nicht vorher als feste Schnittstelle etc.
 
T

TP@Urlaub

Gast
@Spacerat: Vielleicht gibt es da subtile Unterschiede in den Begrifflichkeiten. Aber das von Ark beschriebene klang für mich (Ahnungslosen :D ) eher nach "Design by Contract" als nach dem von bygones angesrochenen TDD. Wenn ich das richtig verstanden habe, kamen bei ersterem ja keine echten Unit-Tests drin vor, sondern "nur" eine detaillierte Beschreibung und Spezifikation und deren gewissenhafte Implementierung (wobei sich beides natürlich nicht gegenseitig ausschließt).

[...]

Design by Contract ist übrigens ein geschützer Begriff und es gehört mehr dazu als eine "detaillierte Beschreibung" sowie einer "gewissenhaften Implementierung". Um das besser zu verstehen müsste man sich Eiffel ansehen (die Sprache von Bertrand Meyer) in die Sprache sind die Methoden derer sich DbC bedient integriert, d.h. es ist problemlos möglich Vor-/Nachbedingungen, Varianten, Invarianten im Code zu formulieren welche dann zur Laufzeit immer ausgewertet werden (es sei denn man sagt dem Compiler er soll sie explizit nicht übernehmen). Damit fallen schon mal eine ganze Menge Unit-Tests weg, IMHO sind Unit-Tests in Verbindung mit DbC mehr Integrationstests. Jedenfalls sind die gebotenen Möglichkeiten der Sprache ein ganz anderes Kaliber als nur eine "gewissenhafte Implementierung". Leider bietet Java nichts Großartiges in dieser Richtung ausser vll. das assert-Statement, welches aber nur einen minimalen Teil abdeckt. Man kann die DbC-Geschichte in Java aber bis zu einem gewissen Grad simulieren, z.B. eigene Methoden die die Invarianten abprüfen etc. Das gelbe vom Ei ist das nicht, es hilft aber.
 

Marco13

Top Contributor
@maki: Vermutlich merkt man nur, was es bedeutet, wenn man mal versucht, es selbst anzuwenden ... vielleicht habe ich mich aber auch unklar ausgedrückt. Man KANN ein Interface in diesem Sinn ja nicht "testen", es muss immer eine Implementierung sein ;)

@TP@Urlaub: Ich weiß, dass Eiffel (und vermutlich andere Sprachen) die Idee des Design by Contract stärker in die Sprache selbst integrieren. Ich fand das schon überzeugend, so wie ich es damals im Studium mitbekommen hatte, und versuche, zumindest die Grundideen davon mitzunehmen: Auch wenn man die Conditions nicht formal und automatisch zusichern kann, sehe ich die JavaDoc doch als eine Art "Vertrag", auf den man sich verlassen können sollte. Was darüber hinausgeht (auch in dem Sinne, wie es in Eiffel schon existiert) geht dann in Richtung dessen, was ich mit der Tool-Unterstützung im ersten Beitrag andeuten wollte. Es gibt da auch Forschungsarbeiten dazu, aber ... eben kein Eclipse-Plugin ;) (Wobei ... wenn man danach suchen würde, würde man wahrscheinlich eins finden.... ich glaube es gibt sogar Eclipse-Plugins mit denen man MineSweeper spielen kann :D )
 
G

Guest2

Gast
Was genau du mit IT-Tests meinst, ist mir nicht ganz klar.

IT-Test steht für Integrationstest. Ein Unit-Test sollte immer nur eine möglichst kleine in sich abgeschlossene Einheit testen und dabei unabhängig von anderen Einheiten bleiben. Der IT-Test hingegen testet die Einheiten in ihrem Zusammenspiel oder das Ganze als Gesamtheit.

Beispiel: Angenommen Du hättest diesen Code:

Java:
public class Business {

    private final Database db = new Database();


    public void doit(final int id) {

        db.doit(id);

    }

}

Und Du willst Business.doit() testen. Jetzt könntest Du vermutlich tausende Unit-Tests schreiben, um alle Fälle abzudecken. Das wäre aber Unsinn, schließlich ist Database.doit() bereits mit seinen eigenen Unit-Tests getestet, Du kannst also beim testen von Business.doit() davon ausgehen das Database.doit() korrekt ist. Das maximale was Dich also interessiert ist das Database.doit() aufgerufen wird. Entsprechend sieht der Unit-Test aus (mit jmockit):

Java:
public class TestBusiness {

    @Tested
    Business business;

    @Capturing
    Database db;

    @Test
    public final void testGet() {

        final int id = 42;

        business.doit(id);

        new VerificationsInOrder() {  {

                db.doit(id);

        } };

    }

}

In der jmockit Einführung gibt es ein Beispiel, das komplexer ist und damit wohl näher an der Wirklichkeit.

Bei einem einzigen Fehler im Code sollte im Idealfall auch nur ein einziger Unit-Test fehlschlagen. Wohingegen vermutlich gleich alle IT-Tests fehlschlagen würden.

Als Beispiel für einen IT-Test kannst Du z.B. auch Dein JOCLSample.java nehmen. Dort must Du nur ~3 Zeilen ändern, um es zu einem IT-Test zu machen. Gleichzeitig deckt der IT-Test dann aber 18,3% deines Java Codes ab (ja, ich habs getestet ;))!

Viele Grüße,
Fancy
 
M

maki

Gast
Ach ja, zum Thema Testabdeckung:
Bitte nicht allzu ernst nehmen ;)

These:
Man kann 100% Testabdeckung haben, ohne ein einziges Assert... Abdeckung heisst ja nur, dass da mal ein Thread durchgelaufen ist, nicht nicht dass da auch was getestet/überprüft wurde.....
 

Marco13

Top Contributor
Ach, ein IntegraTions-Test :D Sicher braucht man sowas am Ende auch noch - mit verschiedenen Schattierungen, je nachdem, ob es um eine Bibliothek oder Anwendung geht und so. Aber gerade für die öffentlichen Teile einer API (mit Interfaces, für die potentiell auch andere Implementierungen schreiben können) wären allumfassende Unit-Tests schon nicht schlecht. (Ich hatte das vor längerer Zeit schonmal gedacht: Da wollte ich verschiedene Arten von Listen implementieren (SchuffledList etc) und da wären verbindliche Unit-Tests, die die Collection- bzw. List-Kontrakte prüfen, nicht schlecht gewesen).

Inwieweit sowas wie JMockit für eine Bibliothek in diesem Sinne geeignet ist, muss ich mir näher ansehen. Aber die grundsätzliche Möglichkeit, auch Unit-Tests zu "schachteln", kam mir auch in den Sinn
Java:
void testEvenNumbers() {
    assertAllEven(thing.getEvenNumbers());
    ...
    ExistingCollectionTests.assertUnmodifiableList(thing.getEvenNumbers()); 
}
aber ich könnte mir vorstellen, dass man Gründe nennen kann, warum das "nicht gut" ist...

[ot]
Das JOCLSample erreicht schon 18.7%? Das macht doch gar nichts... (Und ... der interessante Teil ist sowieso der JNI-Teil - und natürlich das Verhalten, das in diesem Sample NICHT vorkommt ;) )
[/ot]
 
G

Guest2

Gast
[ot]
Das JOCLSample erreicht schon 18.7%? Das macht doch gar nichts... (Und ... der interessante Teil ist sowieso der JNI-Teil - und natürlich das Verhalten, das in diesem Sample NICHT vorkommt ;) )

Ja genau, obwohl da nicht soviel passiert, wird trotzdem schon ein beachtlicher Teil getestet. Beim IT-Test wird der JNI-Teil ja auch mitgetestet nur weis ich nicht, wie man da die Testabdeckung messen kann ;).

Gerade das JOCLSample ist schön, da Du darin das CPU-Ergebnis mit dem GPU-Ergebnis vergleichst. Dadurch hast Du dann auch nicht das von Maki angesprochene Problem, sondern stellst sicher das beide zum selben Ergebnis kommen (was dann auch vermutlich stimmt).

Imho sind bei Wrappern IT-Tests auch die einzig sinnvolle Möglichkeit zu testen, da man sonnst den Treiber Mocken müsste und das könnte schnell aufwendiger werden als der Wrapper selbst.[/ot]

Deshalb mag ich auch IT-Tests, man erreicht sehr einfach und sehr schnell eine Testabdeckung, bei der man davon ausgehen kann, dass das im Grunde schon so passt. Gleichzeitig ist das, was in einem IT-Test passiert, normalerweise das Szenario wie der Nutzer die Software auch einsetzt.

Der Nachtteil von einem IT-Test ist allerdings auch das bei einem Test der ~20% des Codes abdeckt im Fehlerfall auch der Fehler in (mindestens) 20% des Codes gesucht werden muss. Beim Unit-Test hingegen sind es im vielleicht nur 0,000001%.

(Neben dem Nachteil das IT-Tests in der Ausführung länger dauern und seltener ausgeführt werden, aber dafür gibt es ja Continuous-Integration ;))

Viele Grüße,
Fancy
 

Marco13

Top Contributor
[ot]
Imho sind bei Wrappern IT-Tests auch die einzig sinnvolle Möglichkeit zu testen, da man sonnst den Treiber Mocken müsste und das könnte schnell aufwendiger werden als der Wrapper selbst.

Hmja, der einzige Zusammenhang in dem mir "Mocken" bisher "hautnah" untergekommen ist, war genau das, in jcuda testing - Byte-Welt Forum , aber wirklich intensiv damit beschäftigt habe ich mich nicht. Gerade bei JOCL/JCuda sind wegen der "Hardwarenähe" die klassischen Unit-Tests auch nur schwer anwendbar: Man kann nicht testen, ob Speicher wirklich auf die Grafikkarte kopiert wurde - außer wenn man ihn wieder zurückkopiert und vergleicht - joa, kann sinnvoll sein, wirkt aber ein bißchen gestelzt...

Da macht sowas wie das JOCLSample IMHO auch mehr Sinn, weil ich finde, dass bei solchen Samples der Vorteil einerseits darin besteht, dass man Testen kann, und das auch bei "komplexeren" Dingen, aber vor allem weil die Samples die Ausgangsbasis für andere Entwicklungen sind. Ein 100-Zeilen-HelloWorld-Sample sagt (gerade bei sowas wie OpenCL) vieeel mehr, als die etlichen 100 KB (!) JavaDoc aus der CL-Klasse ;)
[/ot]



Der Nachtteil von einem IT-Test ist allerdings auch das bei einem Test der ~20% des Codes abdeckt im Fehlerfall auch der Fehler in (mindestens) 20% des Codes gesucht werden muss.

Das fände ich gar nicht soo schlimm. Fehler sind ja nicht die Regel (*duck*) und speziell bei Java sieht man ja ggf. recht leicht, "in welcher Zeile die Exception fliegt" und so. Was ich (im Hinblick auf den ursprünglichen Anlass für diesen Thread) kritischer finde, ist, dass durch einen "IT-Test" zwar vielleicht ein großer Teil abgedeckt wird, aber im Gegensatz zum Unit-Test eben nur auf genau eine Weise - und zwar die gutmütige, von der man weiß, dass sie funktioniert. Ich hab' mal den Spruch aufgeschnappt: Software testen heißt, zu versuchen, einen Fehler zu provozieren. Was passiert, wenn man bei einer Methode "null" übergibt, oder wenn man die Methode "useFoo" VOR "initializeFoo" aufruft? Und passt das, was da passiert, zur Spezifikation? Eine API in diesem Sinne auf "Robustheit" oder einfach "Spezifikationskonformität" zu prüfen ist nur mit Unit-Tests möglich - aber eben so aufwendig, dass man es in der Praxis offenbar kaum machen kann..
 
S

Spacerat

Gast
Ich hab' mal den Spruch aufgeschnappt: Software testen heißt, zu versuchen, einen Fehler zu provozieren. Was passiert, wenn man bei einer Methode "null" übergibt, oder wenn man die Methode "useFoo" VOR "initializeFoo" aufruft? Und passt das, was da passiert, zur Spezifikation? Eine API in diesem Sinne auf "Robustheit" oder einfach "Spezifikationskonformität" zu prüfen ist nur mit Unit-Tests möglich - aber eben so aufwendig, dass man es in der Praxis offenbar kaum machen kann..
Das hat, so wie ich das inzwischen mitbekommen habe, nichts mehr mit Unit-Tests zu tun, sondern ist genau jenes Verfahren, die "Verwundbarkeit" einer Software aufzuspüren (siehe, mein Post mit den Exploits). Mein Fazit also: Unit-Tests werden vom Entwickler vor der eigentlichen Implementierung erstellt und Integrations-Tests werden während der Entwicklung einer SW nacheinander auf grün getestete Units durchgeführt. Unit- und Integrationstests beinhalten keinen Robustheitstest. Das letzte ist nun eher das, was ich mir bisher unter "Unit-Tests" vorstellte. Eine Unit arbeitet ja auch noch dann spezifikationskomform wenn sie Dinge zulässt, die in dieser Spezifikation nicht vorgesehen oder gar bedacht wurden. Das ist genau der Punkt, den Entwickler selber gar nicht testen können nur User probieren hartnäckig aus, was alles geht um Barrieren zu überwinden.
 

mvitz

Top Contributor
Außerdem ist das was Marco vermutlich möchte - nämlich praktisch 100% Testabdeckung für ein Interface, für das es potentiell N Implementierungen gibt - ein ähnliches Problem wie mit Spezifikationen für ganze Programme/Module.

Man kann für diese unendlich viel Zeit aufwenden und anschließend sind sie leider doch nicht 100% korrekt. Dies gillt sogar für Spezifikationen die nur einen Use-Case beinhalten häufig, wen man nun also versucht im vorhinen alle Möglichkeiten zu betrachten, kann meiner Meinung nach nie etwas entstehen, was zu 100% zutrifft.

Aus diesem Grunde denke ich, dass es einfach nicht praktikable ist Unit-Tests (für ein Interface) zu schreiben, die alle Möglichkeiten abdecken und wenn man es versucht, dann kommen eben sehr große Tests heraus, die dann durchaus mehr Code haben als die ein oder andere Implementierung selber.
 

mvitz

Top Contributor
...
Das fände ich gar nicht soo schlimm. Fehler sind ja nicht die Regel (*duck*) und speziell bei Java sieht man ja ggf. recht leicht, "in welcher Zeile die Exception fliegt" und so. ...

Hier musst du allerdings beachten, dass das Argument natürlich nur für Exceptions gillt, wenn allerdings zum Schluss "lediglich" ein falscher Wert heraus kommt, dann musst du dich schon durch die kompletten 20% hangeln ;-)
 

Marco13

Top Contributor
@Spacerat: Wenn du so von "Exploits" redest klingt das immer nach Böswilligkeit - aber ich bin mir gar nicht sicher, ob du das meinst (ich meine es jedenfalls nicht :D ). Es geht nur darum, dass eine Implementierung eines Interfaces, die nicht der Spezifikation entspricht schlicht falsch ist, und eben nicht (wie es eigentlich sein sollte) überall dort verwendet werden könnte, wo man das interface eigentlich verwenden kann - weil sich derjeinge, der das interface verwendet, eben ggf. auf den Vertrag verläßt.

Mal noch ein willkürlich-suggestives Beispiel: Bei einer Map muss
V Map#put(K key, V value)
den Wert zurückgeben, der vorher unter dem gegebenen Key gespeichert war. Irgendjemand legt dort z.B. irgendwelche Streams rein, und verläßt sich auf das angegebene Verhalten:
Java:
Map<String, OutputStream> map = ...
...

OutputStream oldStream = map.put(id, newStream);
if (oldStream != null) oldStream.close();
Das funktioniert. Wenn dort nun aber eine blöde Map-Implementierung verwendet wird, die dort immer "null" zurückgibt, dann bleiben die alten Streams, die aus der Map geflogen sind, ungeschlossen, was früher oder später zu Problemen führt.

Ein Unit-Test, den jemand für "Map" schnell aus dem Ärmel schütteln würde, würde vielleicht prüfen, ob das Element nach dem "put" enthalten ist und so, aber ... er sollte/müßte sowas eigentlich auch abprüfen:
Java:
Map<Key, Value> map = createMap();
Key key = new Key();
Value value0 = new Value();
Value value1 = new Value();
Value oldValue0 = map.put(key, value0);
assertNull(oldValue0);
Value oldValue1 = map.put(key, value1);
assertEquals(oldValue1, value0);
Aber sowas für jedes noch so kleine (aber eben vielleicht bei der Verwendung extrem wichtige!) Detail zu schreiben ist kaum machbar.

So, und nachdem ich das geschrieben habe, habe ich nochmal in die MapInterfaceTest.java - google-collections - Google Collections Library - Google Project Hosting geschaut :) Dort steht es, ab Zeile 967
Java:
public void testPutExistingKey() {
    final Map<K, V> map;
    final K keyToPut;
    final V valueToPut;
    try {
      map = makePopulatedMap();
      valueToPut = getValueNotInPopulatedMap();
    } catch (UnsupportedOperationException e) {
      return;
    }
    keyToPut = map.keySet().iterator().next();
    if (supportsPut) {
      int initialSize = map.size();
      map.put(keyToPut, valueToPut);
      assertEquals(valueToPut, map.get(keyToPut));
      assertTrue(map.containsKey(keyToPut));
      assertTrue(map.containsValue(valueToPut));
      assertEquals(initialSize, map.size());
    } else {
      try {
        map.put(keyToPut, valueToPut);
        fail("Expected UnsupportedOperationException.");
      } catch (UnsupportedOperationException e) {
        // Expected.
      }
    }
    assertInvariants(map);
  }
Es IST natürlich machbar ;) Aber wie schon gesagt: Der Aufwand ist enorm, und ich kann mir kaum vorstellen, dass jemand mit Sicherheit sagen (geschweige denn beweisen) könnte, dass dort alle diese Fälle abgedeckt sind... auch wenn das obige Beispiel und Methodennamen wie [c]testValuesRetainAllNullFromEmpty[/c] schon darauf hindeuten, dass sich da jemand SEHR (SEHR!) viel Mühe gegeben hat :eek:
 
S

Spacerat

Gast
Ob nun böswillig oder nicht, sei mal dahingestellt. Es gibt Leute die machen's von sich aus und Leute die den Auftrag haben, den anderen zuvor zu kommen. "Ein Unit-Test bestimmt die Implementierung", deckt dabei aber nicht die Möglichkeiten des "an der Spezifikation vorbei" verwendens ab, die durch diese entstehen oder entstehen könnten. Wenn es z.B. nicht in der Spezifikation der Map steht, dass der zuvor enthaltene Wert zurückgegeben wird (eigentlich Möglichkeitsform, denn faktisch soll er ja lt. Spec zurückgegeben werden), dann muss er auch nicht zurückgegeben werden. Der Unit-Test schliesst dann grün und das, was man mit dem ungeschlossenen Stream nun so alles anstellen kann, muss der, der diese Lücke aufgetan hat, auch erst noch herausfinden. Das ist der feine Unterschied zwischen Spezifikation und (ihrer) Verwundbarkeit, also zwischen Unit-, Integrations- und Robustheits- bzw. Negativtest (frisch ergoogelt ;)).
Evtl. verwechselst du ja, wie ich zuvor auch, immernoch Unit- mit Negativ-Tests.
 
Zuletzt bearbeitet von einem Moderator:

Antoras

Top Contributor
Aber sowas für jedes noch so kleine (aber eben vielleicht bei der Verwendung extrem wichtige!) Detail zu schreiben ist kaum machbar.
Sollte die Frage nicht eher lauten was die extrem wichtigen Details sind? Mit genügend Erfahrung weiß man doch was der eigene Code leisten können sollte. Eine kleine, übersichtliche und seiteneffektfreie Funktion muss deutlich weniger getestet werden als eine komplette Klasse, die aus nichts als Seiteneffekten besteht.
Die von dir verlinkte Testklasse testet die typischen Funktionen einer Map - ich sehe nicht was daran sonderlich aufwendig sein soll. Die Größe des Codes kommt von ständigen Überprüfungen auf Null-Werte und vom Fangen von Exceptions. Würde man das weglassen hätte man doch vollkommen übersichtlichen Testcode, der nur die einzelnen Funktionen einer Map testet?

Dass man das nicht weglassen kann liegt aber an Java und nicht am Testcode. Wäre der Java-Compiler typsicherer würde er es überhaupt nicht zulassen Nullable types in die Map einzufügen. Und das ständige Werfen von Exceptions macht die Sache auch nicht einfacher. Hätte man dann noch weniger Möglichkeiten überall Seiteneffekte zu produzieren müsste man auch weniger testen, weil die Menge der Fehlerfälle weniger wäre. 100% korrekten Code kann man vermutlich nie erreichen mit purem Code kommt man aber schon deutlich näher ran.

Schon mal daran gedacht, ob es nicht sinnvoller Wäre zu verbieten, dass eine Schnittstelle z.B. eine Exception werfen oder Null-Werte entgegennehmen darf, anstatt es zu erlauben und sich dann Gedanken machen zu müssen wie man dann darauf reagieren muss?
 
S

Spacerat

Gast
Schon mal daran gedacht, ob es nicht sinnvoller Wäre zu verbieten, dass eine Schnittstelle z.B. eine Exception werfen oder Null-Werte entgegennehmen darf, anstatt es zu erlauben und sich dann Gedanken machen zu müssen wie man dann darauf reagieren muss?
Tut man das an entsprechenden Stellen wo es erforderlich ist nicht sogar schon? Wenn man es in der Doku einer Schnittstelle spezifiziert, wird die Schnittstelle darauf hin doch schon eingeschränkt. Einschränkungen aber bedeuten doch weniger "Freiheit" bei der Implementation. Evtl. macht's mehr Sinn, so etwas in der konkreten Implementation, welche letztendlich ja auch getestet wird, zu spezifizieren ohne dabei die Spezifikation der Schnittstelle zu verletzen ("even more specified"). Wenn dann einer versucht, die Schnittstelle negativ zu testen, tut er dies ohne Kenntnis der tatsächlichen Implementation und scheitert (hoffentlich) evtl. genau deswegen. Unit-Tests hingegen sind alle weiterhin grün.
 

Marco13

Top Contributor
@Spacerat: "Wenn es z.B. nicht in der Spezifikation der Map steht, dass der zuvor enthaltene Wert zurückgegeben wird ... dann muss er auch nicht zurückgegeben werden. Der Unit-Test schliesst dann grün und das, was man mit dem ungeschlossenen Stream nun so alles anstellen kann, muss der, der diese Lücke aufgetan hat, auch erst noch herausfinden."

Ja. Es steht aber da. Aus gutem Grund. Und jeder, der das liest, denkt, dass er sich darauf verlassen kann. Und wenn jemand eine Map implementiert, die sich anderes (also falsch) verhält, kann das unabsehbare Folgen haben. Deswegen wäre es IMHO gut, wenn man es testen würde, bevor man seine Map-Implementierung auf die Menschheit losläßt ;) Wenn dort stünde: "This method may return arbitrary values" dann wäre klar, dass man sich da auf nichts verlassen kann, und das wäre auch in Ordnung.
...:reflect:
assertIsArbitrary(map.put(k));
:joke:


@Antoras: "Schon mal daran gedacht, ob es nicht sinnvoller Wäre zu verbieten, dass eine Schnittstelle z.B. eine Exception werfen oder Null-Werte entgegennehmen darf, anstatt es zu erlauben und sich dann Gedanken machen zu müssen wie man dann darauf reagieren muss?

An einigen Stellen hat man beim Design Freiheitsgrade. Es gibt einige APIs wo ganz pauschal drübersteht: "None of the arguments of any method may be 'null'", und dann braucht man sich darum auch keine weiteren Gedanken zu machen. Zugegeben, das Beispiel (bei dem ich sowas als Test auf "Robustheit" bezeichnet habe) war so gesehen etwas unpassend, bzw. hat vielleicht (auch wegen des Satzes mit dem "Versuch Fehler zu provozieren") etwas falsches suggeriert. Es ging mir nicht darum, eine API absichtlich falsch zu verwenden, und zu erwarten, dass sie auf die gutmütigst-mögliche Weise reagiert ;) sondern wirklich nur darum, alles zu überprüfen, was von der API zugesichert wird. So gesehen gibt es für ein (wieder mal sehr einfaches und suggestives) Beispiel drei Möglichkeiten:
Java:
1. : /** @param key The key. This may never be null */
2. : /** @param key The key */
3. : /** @param key The key. This may be null. */
Im 1. Fall muss man den Aufruf mit 'null' nicht in den Unit-Test packen, weil es nicht zugesichert wurde.
Im 2. Fall weiß man nicht, was man machen soll. Der Kommentar ist so gesehen "schlecht", aber bei sowas kann/sollte man wohl davon ausgehen, dass es eigentlich der 1. Fall ist
Im 3. Fall muss man prüfen, ob die Methode einen Aufruf mit 'null' übersteht. Wenn sie das nicht tut, ist sie schlicht falsch implementiert.

(Der Kommentar weiter oben bezog sich nur auf die angesprochenen Integrationstests, wo man so eine Methode wohl eher nicht mit "null" aufrufen würde, sondern NUR mit einem richtigen Key - auch wenn zugesichert ist, dass es auch mit "null" funktionieren sollte)
 
S

Spacerat

Gast
Ja. Es steht aber da. Aus gutem Grund. Und jeder, der das liest, denkt, dass er sich darauf verlassen kann. Und wenn jemand eine Map implementiert, die sich anderes (also falsch) verhält, kann das unabsehbare Folgen haben. Deswegen wäre es IMHO gut, wenn man es testen würde, bevor man seine Map-Implementierung auf die Menschheit losläßt ;) Wenn dort stünde: "This method may return arbitrary values" dann wäre klar, dass man sich da auf nichts verlassen kann, und das wäre auch in Ordnung.
Stimmt. Es steht da und man sollte es auch testen. Den tut man es nicht, verstösst man gegen ein Spezifikation und ist damit ohnehin schon sehr viel früher als geplant dem Wohlwollen dubioser User ausgeliefert. Glücklicherweise gibt es dafür ja Unit-Tests, welche man halt schreibt, bevor man mit der Implementation beginnt. XD
Ich bin kürzlich im übrigen über eine derartige (Un)Spezifikation gestolpert.
Java:
// AudioInputStream (Auszug)
    /**
     * Obtains the length of the stream, expressed in sample frames rather than bytes.
     * @return the length in sample frames
     */
    public long getFrameLength() {
        return frameLength;
    }
Die Frage: Darf man hier 0 oder gar negative Werte zurückgeben? Für die Antwort - welche wohl "aber sicher doch" lautet, meines Erachtens aber "NEIN!" lauten müsste - könnt ihr euch ja mal z.B. JLayers MP3SPI ansehen - dort ist die frameLength stets unspezifiziert.
 
Zuletzt bearbeitet von einem Moderator:

Marco13

Top Contributor
Falls der subtile Hauch von Ironie, den ich da zu erkennen glaubte, wirklich vorhanden war: Jaja, man wird doch wohl noch fragen dürfen :oops: ;)

Die Frage: Darf man hier 0 oder gar negative Werte zurückgeben? Für die Antwort - welche wohl "aber sicher doch" lautet, meines Erachtens aber "NEIN!" lauten müsste - könnt ihr euch ja mal z.B. JLayers MP3SPI ansehen - dort ist die frameLength stets unspezifiziert.

Ja, genau das ist sie: AudioSystem#NOT_SPECIFIED (also -1). Wie wenn man diesen Konstruktor aufruft:
public AudioInputStream(TargetDataLine line)
Constructs an audio input stream that reads its data from the target data line indicated.
The format of the stream is the same as that of the target data line, and the length is
AudioSystem#NOT_SPECIFIED.
Aber es stimmt, dass sowas schon dabei stehen sollte....
 
J

JohannisderKaeufer

Gast
Ein Test der gegen ein Interface (Map) erstellt wird um damit verschiedene Implementierungen (HashMap, LinkedHashMap) zu testen ist KEIN Unit-Test im eigentlichen Sinne.

Warum?
Wenn ich das V-Model als Grundlage nehme dann habe ich in etwa dieses Vorgehen (etwas vereinfacht)
Code:
Anforderungsanalyse  |      Acceptance-Tests
  Systementwurf      |    Integrations-Tests
    Implementierung  |  Unit-Tests

Von links oben nach mitte unten und von dort nach rechts oben.

Ein Unittest soll eine Einzelne Klasse testen. Abhängigkeiten werden in der Regel gemockt.
Ein Integrationstest testet eine Komponente, also das Zusammenspiel zwischen den einzelnen Klassen. Abhängigkeiten werden nur noch in sofern gemockt, wie es nötig ist. z.B. wenn Paypal eingebunden wird, dann wird das gemockt, da ja nicht unbedingt echtes Geld fließen soll.
Ein Acceptance-Test beinhaltet alles, was der Kunde in der Anforderungsanalyse zum besten gibt, was die Software letztendlich können soll.

Alle Punkte kann man Manuell testen. Hat man früher auch so gemacht, da hat dann ein Praktikant oder Werksstudent eine Liste bekommen, vielleicht sogar Excel, da Stand dann drin was getestet werden soll und wie die Eingaben sind und was rauskommen soll.

Alle Punkte kann man oftmals auch automatisch testen. Eine Beispielconfiguration.
Unit-Tests, JUnit + Mocking. Testen auf Klassenebene
Integrations-Tests, JUnit, Testen auf Komponentenebene
Acceptance-Tests, Cucumber, Testen auf Systemebene, Einzelne Use-cases.

Mit Cucumber kann man auch Behavior-Driven-Development machen. Cucumber abstrahiert einerseits und kann intern z.B. auch JUnit nutzen.

Ein Beispiel für ein Cucumber Feature - Use-Case

Code:
Feature: Serve coffee
  In order to earn money
  Customers should be able to 
  buy coffee at all times

  Scenario: Buy last coffee
    Given there are 1 coffees left in the machine
    And I have deposited 1$
    When I press the coffee button
    Then I should be served a coffee

Intern wird hinterlegt was "Given there are 1 coffes left in the machine" bedeutet und dann bei der Ausführung wird dann daraus ein entsprechender JUnit-Test generiert. Man könnte allerdings auch direkt in JUnit Acceptance-Tests implementieren.

Und genau hier würde ich einen Test wie den zu Anfangs genannten (Map -> MapImpl) einordnen, zu den Acceptance-Tests. Und bei einem Acceptance-Test wird nicht überprüft ob die Implementierung korrekt ist, sondern ob die Anforderungen erfüllt sind.


Ein Wort zu Integrationstests die für hohe Testabdeckung sorgen. Wenn zwei Komponenten falsch implementiert sind, kann sich ein Fehler aufheben. Wenn zwei Komponenten fälschlicherweise ein Falsches Vorzeichen zurückgeben und eine andere Komponente diese Werte multipliziert, so kommt ein korrektes Ergebnis raus. Wenn der Fehler nun auffällt und an einer Stelle behoben wird, dann wird das Endergebnis falsch.


Dann wurde hier oft noch die Komplexität die entsteht, wenn möglichst umfangreich getestet werden soll erwähnt.
Um die Komplexität zu reduzieren, gibt es ein paar Vorgehensweisen, sie wird aber immer in einer Größenordnung bleiben, die ein komplettes Testen unmöglich macht.
Wenn man komplett testen könnte, dann hätte man bereits eine Spezifikation auf Grund derer man ein Programm schreiben könnte, das in endlicher Zeit eine möglichst Gute Implementierung für das beschriebene Problem liefert.
(Kam so ähnlich mal auf BR-alpha, ein Prof. von der LMU? (jedenfalls München))

Man kann nur soweit Testen um zu der Entscheidung zu kommen, "Setze ich diese Software ein oder lass ich es bleiben" und das sind letztendlich Acceptance-Tests und nicht Unittests.
 
B

bygones

Gast
wie schoen zu sehen, dass dies ein tolles Bsp ist wie man vom Gedanken eines Unit-Tests auf Design Fragen, Probleme und Diskussion kommt :) Was so alles ein einfacher Test bewirken kann...

@Marco
du meintest mal Fehler seien nicht die Regel und werden ja dank Java schnell aufgezeigt (also im Sinne von "wo in welcher zeile). Tests sind fuer die Sicherstellung, dass solche Fehler nicht auftreten. Es bringt dem Kunden nix wenn er von Fehler in Fehler rennt und du im sagen kannst "ach zum Glueck seh ich wo er auftritt".
Gut moeglich dass diese Aussage aus dem Kontext gerissen wurde...
 

FArt

Top Contributor
Das klingt realistisch und praktikabel - aber (sorry) eben auch ein bißchen "hilflos". Natürlich kann einem kein Kochbuch das Denken abnehmen - jeder Entwickler hat da eine Verantwortung, die er wahrnehmen muss. Aber es gibt bei dieser Beschreibung einige Freiheitsgrade, die im schlimmsten Fall ausgenutzt werden können, und die Unit-Tests ad absurdum führen.

Java:
/** ...Returns an unmodifiable List of even numbers, never null... */
List<Integer> getEvenNumbers()  { ... }
[/QUOTE]

Du führst den Test mit diesem Beispiel ad absurdum, weil du hier zwei Sachen übersiehst:

1.) Eine (nicht statische) Methode steht nicht für sich alleine sondern lebt nur im Kontext der Klasse und somit im Lebenszyklus der Instanz.
Es bringt somit schon sehr viel, wenn ich genau diese Paramter abprüfe: Liste, nicht null, unveränderbar.
Aber es muss ja noch weiter gehen: wie kommen Werte in die Liste, ist das umschließende Objekt unveränderbar (oder muss es das sein) usw. Die öffentliche Schnittstelle macht also noch viel mehr aus als nur die drei Angaben in der Beschreibung der Javadoc, sondern auch der technischen Dokumentation bzw. des technischen Konzepts.

2.) Du gehts viel zu weit: du testest in diesem Test auch noch die Implementierung der unveränderlichen Liste. Das ist aber Aufgabe des Tests für die Liste, nicht des Verwenders. Das gilt dann für Iterator usw.

Die Implementierung spiegelt den Entwurf und die Tests prüfen das ab. Das ist das Minimum, was Tests erfüllen sollten.
 
Zuletzt bearbeitet:
S

Spacerat

Gast
Du führst den Test mit diesem Beispiel ad absurdum...
Das gibt mir grad' viel zu Denken, was mir vorher noch gar nicht so aufgefallen ist... also nicht der Inhalt oder Korrektheit dieser Aussage sondern...
@Marco13: Willst du Unit-Tests an fremden APIs ausführen? Lass das! Das haben andere (eben die Entwickler dieser APIs) schon getan oder gelassen. Du kannst darauf nur noch Negativ-Tests loslassen und den Entwicklern evtl. zeigen, wo sie sich irrten.
 
M

maki

Gast
@Marco13: Willst du Unit-Tests an fremden APIs ausführen? Lass das! Das haben andere (eben die Entwickler dieser APIs) schon getan oder gelassen. Du kannst darauf nur noch Negativ-Tests loslassen und den Entwicklern evtl. zeigen, wo sie sich irrten.
Im TDD Umfeld nennt man solche Tests "learning tests", bei einer neuen API schreibt man sowieso "tests", in diesem Falle eben Unittests.
Hat zB. den Vorteil dass man gleich Beispeile wie eine API zu verwenden bzw. was man glaubt wie eine API zu verwenden ist und wie sie sich zu verhalten hat.
Bei einem API Update können solche Tests einem vielleciht(?) auch sagen ob ein Upgrade sicher wäre..
 

Marco13

Top Contributor
@JohannisderKaeufer: Ein paar interessante Einordnungen und Definitionen (ich weiß, das hätte man sich auch alles selbst aneignen können, indem man 20 Bücher liest :oops: aber... manchmal muss man Prioritäten setzen: In Anbetracht der Tatsache, dass es (in meinem Fall) keinen "Kunden" und keine "Vorgaben" und keine "Integeration" (im eigentlichen Sinn) gibt, reduziert es sich wirklich darauf, zu testen, ob ein Implementierung korrekt ist...)

Mit den zur Verfügung stehenden Infrastrukturen zu Acceptance-Tests und Cucumber kann ich im Moment nicht viel anfangen. Aber wenn man das mal auf eine für ein privates Projekt vielleicht angebrachte, pragmatische Ebene runterbricht, würde ich dazu tendieren, die Frage nach der Bennenung mal hinten an und das Ziel in den Vordergrund zu stellen - bezogen auf

"Man könnte allerdings auch direkt in JUnit Acceptance-Tests implementieren. ... Und genau hier würde ich einen Test wie den zu Anfangs genannten (Map -> MapImpl) einordnen, zu den Acceptance-Tests. Und bei einem Acceptance-Test wird nicht überprüft ob die Implementierung korrekt ist, sondern ob die Anforderungen erfüllt sind."

Vielleicht etwas ... naiv formuliert: Auf rein technischer Ebene (speziell da es um eine Bibliothek geht) sehe ich jetzt die einzige Anforderung eben darin, dass die Implementierung korrekt ist :D

[ot]
"Wenn man komplett testen könnte, dann hätte man bereits eine Spezifikation auf Grund derer man ein Programm schreiben könnte, das in endlicher Zeit eine möglichst Gute Implementierung für das beschriebene Problem liefert."

Da wird AFAIK auch schon dran geforscht: Aus formalen Anforderungsbereschreibungen Software zu generieren, die die Anforderungen erfüllt
[/ot]


@bygones: "du meintest mal Fehler seien nicht die Regel und werden ja dank Java schnell aufgezeigt"
Da stand so ein "*duck*" dabei ;) Ich weiß, dass so eine Aussage leicht fehlinterpretiert werden könnte. Es ist nur i.a. so, dass in Java bestimmte Fehlerklassen (durch Exceptions) leichter zu entdecken sind, was bei anderen Sprachen u.U. nur "Corrupted Memory" zur Folge hätte.


@FArt: "1.) Eine (nicht statische) Methode steht nicht für sich alleine sondern lebt nur im Kontext der Klasse und somit im Lebenszyklus der Instanz. Es bringt somit schon sehr viel, wenn ich genau diese Paramter abprüfe: Liste, nicht null, unveränderbar. ... Die öffentliche Schnittstelle macht also noch viel mehr aus als nur die drei Angaben in der Beschreibung der Javadoc, sondern auch der technischen Dokumentation bzw. des technischen Konzepts."

Dem stimme ich zu. Es bringt schon viel die erste "Ebene" zu testen, aber es gibt noch viel, was dadurch nicht erfasst wird - das meinte ich mit den Fragen nach der angemessenen Tiefe der Tests und der kombinatorischen Explosion, die durch die unterschiedlichen möglichen Aufrufreihenfolgen der Methoden entsteht - bzw. allgemein der möglichen Explosion der Größe des Zustandsraumes eines Objektes, den man ja theoretisch komplett testen können wollte...

"2.) Du gehts viel zu weit: du testest in diesem Test auch noch die Implementierung der unveränderlichen Liste.

Der Punkt wurde schon angesprochen: Um die Funktion zu überprüfen, die behauptet, sie gäbe eine unveränderliche Liste zurück, müßte man eigentlich auf das zurückgegebene Objekt alle Tests anwenden, mit denen man man auch isoliert die Implementierung einer unveränderlichen Liste überprüfen würde - lapidar formuliert: Man weiß ja nicht, welche Implementierung man bekommt.

Ganz konkret (aus einem anderen Zusammenhang, und noch SEHR in der Planung, also nicht zuuu ernst nehmen) : Ich habe den Fall, dass ich an einer Stelle eine Map zurückgeben möchte. Man kann bei dieser Map keine neuen Einträge hinzufügen. Also müßte "put" eine UnsupportedOperationException werfen. Es würde an dieser Stelle aber durchaus Sinn manchen, wenn man existierenden keys in dieser Map neue Werte zuweisen könnte. Man könnte dort eine Implementierung zurückgeben, die genau das erlaubt. So eine Map würde aber bei einem gewissenhaften Unit-Test auf jeden Fall durchfallen, weil die 'put' Methode "nicht unterstützt" und gleichzeitig "nicht nicht unterstützt" ist.

@Spacerat: "Willst du Unit-Tests an fremden APIs ausführen?"
Nein. Es ist nur so, dass das, was ich entwickle, in mancher Hinsicht ähnlich zu den Collections ist, und ich auf der Suche nach "Inspiration" für geeignete Tests eben das Guava-Map-Test-Monster gefunden habe, wo jemand genau das in einer Form gemacht hat, die ich für gut und richtig und eigentlich erstrebenswert, in der Praxis aber leider kaum so konsequent umsetzbar halte.
 

FArt

Top Contributor
In Anbetracht der Tatsache, dass es (in meinem Fall) keinen "Kunden" und keine "Vorgaben" und keine "Integeration" (im eigentlichen Sinn) gibt, reduziert es sich wirklich darauf, zu testen, ob ein Implementierung korrekt ist...).
Ganz und gar nicht. Du programmierst ja nicht ins Leere, sondern machst dir Gedanken über die Funktionalität und über die Anforderungen. Also im Prinzip bist du der Kunde, der erst mal definieren muss, was denn überhaupt rauskommen soll.
Ohne klares Ziel, wird kein klares Ergebnis heraus kommen, somit können auch keine klaren Tests geschrieben werden.
Das ist ok, solange man vor sich hin prototypt, aber dabei sollten dann langsam die Anforderungen entstehen, die man sofort in Tests gießt, bevor man weiter macht.

Ganz konkret (aus einem anderen Zusammenhang, und noch SEHR in der Planung, also nicht zuuu ernst nehmen) : Ich habe den Fall, dass ich an einer Stelle eine Map zurückgeben möchte. Man kann bei dieser Map keine neuen Einträge hinzufügen. Also müßte "put" eine UnsupportedOperationException werfen. Es würde an dieser Stelle aber durchaus Sinn manchen, wenn man existierenden keys in dieser Map neue Werte zuweisen könnte. Man könnte dort eine Implementierung zurückgeben, die genau das erlaubt. So eine Map würde aber bei einem gewissenhaften Unit-Test auf jeden Fall durchfallen, weil die 'put' Methode "nicht unterstützt" und gleichzeitig "nicht nicht unterstützt" ist.
Und genau das ist gut so, wenn die Methode behauptet, die Map werde nicht modifizierbar.

Deine spezielle Map-Implementierung wird passend dokumentiert und natürlich getestet (put mit neuem Key -> Fehler; put mit altem Key -> Update).
Eine Methode, die dieses Map zurückliefert, dokumentiert dies... und fertig.

Wenn jemand lediglich gegen Map arbeitet, kann es nunmal passieren, dass das eine oder andere nicht geht... ist aber auch beabsichtigt, denn das liegt in der Natur der Sache.
 

Marco13

Top Contributor
Die Anforderungen sind sehr abstrakt und allgemein. Bei den Collections hat sich ja auch keiner gedacht: "Ich will eine Lagerverwaltung implementieren", sondern "Ich definiere ein Interface zur allgemeinen Speicherung von Daten, für das es verschiedene Implementierungen geben kann (die sich aber alle genau so verhalten sollen, wie es in Interface definiert ist)". Und genau um letzteres zu prüfen, reicht es IMHO nicht, in allen Implementierungen einrach "nachzusehen"....
 
Ähnliche Java Themen
  Titel Forum Antworten Datum
B Algorithmus für Arbeit mit fehlenden Listenelementen? Allgemeine Java-Themen 1
kodela Eingabe für TextArray bedingt sperren Allgemeine Java-Themen 3
Karl_Der_Nette_Anfänger Hat wer ne Lösung für verknüpfte Postleitzahlen? (Baum/Wurzel Struktur) Allgemeine Java-Themen 11
R 11 GB File lesen ohne zu extrahieren Filedaten Bereich für Bereich adressieren dann mit Multi-Thread id die DB importieren Allgemeine Java-Themen 3
G KeyListener für JTextField Allgemeine Java-Themen 5
webracer999 Library für Textsuche (z. B. include/exclude, and/or)? Allgemeine Java-Themen 5
I Module-Info für Jar erzeugen Allgemeine Java-Themen 7
krgewb Java-Bibliothek für ONVIF Allgemeine Java-Themen 1
B Simpler Eventlistener für Tastaturtaste bauen? Allgemeine Java-Themen 13
_user_q Eingegebenen Text Zeile für Zeile ausgeben lassen Allgemeine Java-Themen 11
E Key für TOTP Algorythmus(Google Authentificator) Allgemeine Java-Themen 0
S Formel für Sonnenwinkel in ein Programm überführen Allgemeine Java-Themen 11
M pfx-Zertifikat in Tomcat für SSL-Verschlüsselung nutzen Allgemeine Java-Themen 14
R Best Practice Erfahrungswerte für eine Migration von JSF nach Angular (oder anderes JS-Framework) Allgemeine Java-Themen 1
B HeapSort für Array of Strings funktioniert nur teilweise Allgemeine Java-Themen 3
jhCDtGVjcZGcfzug Klassen Was genau passiert hier? Kann mir das jemand bitte Zeile für Zeile erklären? Allgemeine Java-Themen 1
rosima26 Bester Sortieralgorithmus für kurze Arrays Allgemeine Java-Themen 40
S Mit Methoden kann man definieren für was <T> steht. Geht das auch irgendwie für Variablen? Allgemeine Java-Themen 12
MangoTango Operatoren while-Schleife für Potenz Allgemeine Java-Themen 3
B Lottospiel, genug Reihen tippen für 3 Richtige (Spaß mit Arrays)? Allgemeine Java-Themen 46
B Mit welchen Datentypen und Strukturierung am Besten dutzende Baccaratspiele Shcritt für Schritt durchsimulieren? Allgemeine Java-Themen 26
D Klassendesign für einen Pascal Interpreter Allgemeine Java-Themen 6
I OCR Library für Belegerkennung Allgemeine Java-Themen 7
farah GetterMathod für Farbkanäle Allgemeine Java-Themen 6
B Welcher Datentyp für sehr große Zahlenbereiche? Allgemeine Java-Themen 1
S Webservices für binäre Daten? Allgemeine Java-Themen 5
G Licence-Header für InHouse entwickelten Source Allgemeine Java-Themen 8
M Schleife für einen TicTacToe Computer Allgemeine Java-Themen 5
O git ignore für Intellji braucht es die .idea Dateien? Allgemeine Java-Themen 8
F Java Script für das Vorhaben das richtige? Allgemeine Java-Themen 9
M wiviel Java muss ich für die Berufswelt können ? Allgemeine Java-Themen 5
Robertop Datumsformat für GB ab Java 16 Allgemeine Java-Themen 1
Thallius Verschiedene entities für gleichen Code…. Allgemeine Java-Themen 8
OnDemand Zentrale "Drehscheibe" für verschiedene APIs Allgemeine Java-Themen 14
S Übergabe eines Sortierkriteriums für ein Artikel Array mittels BiPredicate<Artikel, Artikel> Allgemeine Java-Themen 13
F Streams als Alternative für dieses Problem ? Allgemeine Java-Themen 15
D SHA-3 für Java-version 1.8 Allgemeine Java-Themen 1
N Validator für einen SQL-Befehl Allgemeine Java-Themen 22
Muatasem Hammud Erstellung von Testdaten für Arrays Allgemeine Java-Themen 6
B Logikfehlersuche, das perfekte Lottosystem für 3 Richtige mit Arraylists? Allgemeine Java-Themen 61
G Methoden für die Zukunft sinnvoll? Allgemeine Java-Themen 4
M API für PLZ Umkreissuche Allgemeine Java-Themen 3
1Spinne JDK 8 für Eclipse installieren Allgemeine Java-Themen 5
Tobero Meine Funktion für das beinhalten eines Punktes in einem Kreis funktioniert nicht Allgemeine Java-Themen 5
L Methoden Parser für gängige Datumsformate? Allgemeine Java-Themen 1
H Interface PluginSystem ClassNotFound exception für library Klassen Allgemeine Java-Themen 10
N relativier Pfad für sqlite-Datenbank in Gradle/IntelliJ Allgemeine Java-Themen 2
buchfrau Anagram für beliebiges Wort Allgemeine Java-Themen 2
TonioTec Api für Datenaustausch zwischen Client und Server Allgemeine Java-Themen 0
W Suche Ursache für NPE - woher kommt sie? (Hilfe beim Debugging) Allgemeine Java-Themen 19
Kirby.exe Distanz Map für die Distanztransformation erstellen Allgemeine Java-Themen 1
F PI Regler für Heizung Allgemeine Java-Themen 7
8u3631984 Generelle Log4j.xml für alle Module Allgemeine Java-Themen 5
M Wie übergebe ich den Zähler für die Anzahl Rekursionsschritte korrekt? Allgemeine Java-Themen 2
B Login für User, der im Hintergrund Schedules ausführt Allgemeine Java-Themen 16
L RegEx für Teile einer Berechnung Allgemeine Java-Themen 14
S Java-Task-Management-Tool für Windows und Mac selber programmieren Allgemeine Java-Themen 4
M Java 2D Array für ein Grid erstellen ? Allgemeine Java-Themen 2
Z Welches GUI Framework für Java ist aktuell? Allgemeine Java-Themen 16
N Convert.FromBase64 von C# für Java Allgemeine Java-Themen 11
N fixed-keyword von C# für Java Allgemeine Java-Themen 6
O Suche Scripter für alt:V Project! Allgemeine Java-Themen 0
S Interface Design von HookUp oder Callback Methoden für eigenes Framework Allgemeine Java-Themen 9
O Suche Unterstützung für ein OpenSource-Projekt (grafischer Editor) Allgemeine Java-Themen 13
Kirby.exe Software für Graphische Visualisierung Allgemeine Java-Themen 20
B OOP Auslöser für NullPointerException Allgemeine Java-Themen 3
L Generator für einen Parser implementieren Allgemeine Java-Themen 13
DonMalte Ambitioniertes Projekt für Einsteiger & Motivierte Allgemeine Java-Themen 0
Kirby.exe Movement System für Spiel Allgemeine Java-Themen 13
Kirby.exe Framework für Game Design Allgemeine Java-Themen 8
W Alternative für Threads Allgemeine Java-Themen 6
S Rückgabe einer HttpURLConnection für eine Seite einlesen bei der man eingeloggt ist..? Allgemeine Java-Themen 5
Elyt Compiler-Fehler Datei kann nicht erstellt werden. Die Syntax für den Dateinamen etc. ist falsch. Allgemeine Java-Themen 2
Thallius Rätsel für Windows Profis Allgemeine Java-Themen 8
D OOP Gemeinsamen ID-Raum für zwei Klassen implementieren Allgemeine Java-Themen 7
D Input/Output Implementierung eines CommandHandlers/Parsers für viele Eingaben Allgemeine Java-Themen 26
Thallius Alternative für SwingWorker Allgemeine Java-Themen 5
I Lohnt sich heutzutage der Aufwand einer Portierung für MacOS Allgemeine Java-Themen 8
L Klassen Algorithmus für das folgende Problem entwickeln? Allgemeine Java-Themen 30
J Datenstruktur für eine Map erstellen Allgemeine Java-Themen 2
H OOP Setting(config) für Applikation sicheren? Allgemeine Java-Themen 9
OnDemand PDF Libary für Formulare Allgemeine Java-Themen 7
S Warmup für Lineare-Suche mit Zeitmessung Allgemeine Java-Themen 2
T Allgemeine Frage: GUI für 3D-Visualisierung Allgemeine Java-Themen 5
M Brainstorming für mein Projekt Allgemeine Java-Themen 30
K OOP Suche Hilfe + Erklärung für eine Hausaufgabe Allgemeine Java-Themen 1
F Was ist der Dateityp meines Parameters für die Main Methode. Allgemeine Java-Themen 6
C Bibliotheken für Algorithmische Geometrie Allgemeine Java-Themen 2
C Daten für Klassifikationsverfahren gewinnen Allgemeine Java-Themen 6
C code oder Bibliotheken für 2-Center Problem Allgemeine Java-Themen 4
I Overlay für Spiele Allgemeine Java-Themen 5
B Suche nach einem Testprogramm für meine BA Allgemeine Java-Themen 0
I GUI für kleine Pop-Ups unter Windows Allgemeine Java-Themen 1
A NetBeans Suche Programmierer für eine Belegarbeit Allgemeine Java-Themen 11
HarleyDavidson Best Practice Wohin mit der Konfigurationsdatei für Desktopapplikationen? Allgemeine Java-Themen 3
R MAC-Adresse eindeutig für einen PC ? Bezug zu Netzwerk, wieso ? Allgemeine Java-Themen 7
N Java API für CardDav und CalDav gesucht Allgemeine Java-Themen 4
R Idee für Methodenrumpf Allgemeine Java-Themen 5
O Suche größeres Beispiel für WebserverAnwendung mit Java Allgemeine Java-Themen 2
K Anregungen für Bilderanalyse in Java Allgemeine Java-Themen 1

Ähnliche Java Themen

Neue Themen


Oben