der code ist zwar miserabel aber erfüllt die Unit Tests also denkt man sich "wunderbar das funktioniert also weiter gehts"
ich muss ja nicht nachdenken, wenn die tests funktionieren passt ja alles.
dann komm ich zum nächsten problem
2.
wenn man zu viele UnitTests hat ist es fast unmöglich irgendwas an den Anforderungen zu ändern da man ansonsten den halben Tag rein steckt die Unit Tests so anzupassen dass sie den neuen anforderungen entsprechen
ich verballere mehr zeit damit die Unit Tests an neue anforderungen anzupassen als ich damit verbringe unit tests neu zu schreiben oder die methode zu schreiben
3.
die "verwaltung" der unit tests
wie zb Testdateien erzeugen , doku , kommentare, ANFORDERUNGS Anpassungen fressen einfach zu viel zeit
irgendwas mache ich so dermaßen falsch dass es gerade so rauscht...
Sorry, im Augenblick stehe ich nur da und frage mich: Was für Tests wurden da geschrieben, dass da so ein Quatsch an Code raus gekommen ist? Zumal der Code nicht übersetzt mit diesem int.maxValue, aber ok, da meinstest Du ein Integer.MAX_VALUE. Aber darum soll es nicht gehen. Wie kommt man zu so einem Code? Das schafft ja noch nicht mal unser Mr. Ich mache mir ständig neue Accounts.
Bei TDD ist es existenziell, dass kleine Schritte gegangen werden. Dann ein Test der fehlschlägt und dann eine minimale Anpassungen, damit der Test nicht mehr fehlschlägt. Und dann kommt ein Refactoring.
wenn man zu viele UnitTests hat ist es fast unmöglich irgendwas an den Anforderungen zu ändern da man ansonsten den halben Tag rein steckt die Unit Tests so anzupassen dass sie den neuen anforderungen entsprechen
Wenn der Code sauber unterteilt wurde und die Aufgaben sauber deligiert wurden und bei Unit Tests nur die eigentliche Methode getestet wird und nicht mehr, dann passiert sowas nur, wenn die Anforderungen entsprechend großflächig geändert wurden. Ansonsten hast Du eben lokale Anpassungen und das sollte dann auch nur bei den entsprechenden Tests aufschlagen.
Hier ist also speziell die Single Responsibility Principle zu nennen sowie Mocking!
Da wäre die Frage, was genau Du da treibst. Meine Erfahrung ist, dass Unit Tests durchaus Zeit kosten, aber diese Zeit wird später um ein vielfaches herein geholt. Alleine schon, was es kostet, wenn beim Kunden Probleme auftreten und man diese erst einmal rekonstruieren muss.
Aber bei Dir läuft ganz offensichtlich irgendwas gewaltig schief. Und das kann dann tatsächlich erklären, dass Du da natürlich auch in Zeitprobleme kommen wirst. (Wobei das nichts mit den Unit Tests zu tun hat. Ungetesteten Code von Dir möchte ich - bei dem Beispielcode - gar nicht erst sehen!)
Bei den Threads von @mihe7 gefällt mir nicht, dass da die Anforderungen nicht festgelegt sind. Das ist aus meiner Sicht ein Unding und auch unrealistisch. Aber für Dich evtl. gut, denn da müssen dann tatsächlich die unittests angepasst werden (Weil z.B. die Rückgabe sich ändert und statt List<Integer> dann plötzlich List<String> zurückgegeben wird).
Der Code ist wahrscheinlich noch nicht mal Java. Sieht mir wie C# aus. Da gibt's ein int.MaxValue und auch eine OverflowException (die es in Java nicht gibt).
Sorry, im Augenblick stehe ich nur da und frage mich: Was für Tests wurden da geschrieben, dass da so ein Quatsch an Code raus gekommen ist? Zumal der Code nicht übersetzt mit diesem int.maxValue, aber ok, da meinstest Du ein Integer.MAX_VALUE. Aber darum soll es nicht gehen. Wie kommt man zu so einem Code? Das schafft ja noch nicht mal unser Mr. Ich mache mir ständig neue Accounts.
Also der Code war erst einmal kein Test sondern wohl ein Code, den Du dann getestet hast.
Bau doch mal irgend ein reelles Beispiel, das sowohgl Tests also auch die daraus resultierenden Implementationen zeigt. Dann kann man da in der Regel am ehesten helfen.
Test Driven bedeutet ansonsten ja in erster Linie, dass man erst den Test schreibt. Das muss aber nicht in so vielen kleinen Schritten passieren, wie es z.B. bei den Threads von @mrBrown gezeigt wurde. Wobei das etwas Erfahrung ist, um zu sehen, was Sin macht und was kein Sinn macht. Die Implementierungen müssen trivial bleiben.
Beispiel: Es wird ein isEval gebraucht. Dann überlege ich schon vorab, wie das denn zusammen gehört - und dann ist das von mir aus eine Math Utility Klasse. Und dann überlege ich ich mir dieses Thema direkt. Und dann ist da direkt ein Test wie:
publicclassMathUtil{publicstaticbooleanisEven(finalint value){return value %2==0;}}
Edit: Bei dem Code habe ich auf die JavaDoc Kommentare verzichtet. Die sind aber angebracht, da diese beim Unit Test beschreiben, was die Spezifikation ist (Das finde ich einen super Ort!) und man will den eigenen Code ja dokumentieren (JavaDoc generieren und fertig!)
Die Schrittweite ist aber halt so gewählt, dass da wirklich nur minimaler Code geschrieben werden muss. Da kommt also nur eine kleine Anpassung oder eine kleine Methode bei raus. Da entstehen keine zig Methoden, mehrere Klassen und und und ....
Wenn ich feststelle: Ich muss da diverse Fälle beachten (Mehrere if Statements), dann stoppe ich, aus dem Test werden direkt 2 oder mehr Tests (Von denen ich dann aber nur einen beachte!) Man arbeitet daran so, wie der Vater die Klöße isst: Einen nach dem anderen!
Was also für mich immer dazu gehört ist die Planungsphase. Ohne geht es doch auch gar nicht. TDD setzt doch eigentlich voraus, dass es eine gewisse Planung gibt. In der Regel ist dies ein Agiler Ansatz:
Man hat eine Art Backlog. Da sind dann Features / User Stories / ... gesammelt.
Davon hat man eine gewisse Menge für eine Iteration vorgesehen. Dazu wurden die aber bereits eingeordnet nach Aufwand, Abhängigkeiten u.s.w.
Wenn man die "gezogenen" Einträge hat, dann werden Tasks geplant. Du hast also einen Task zu implementieren, der nur so bis 4h dauern sollte (Absolutes Maximum 2 Tage!). Also vom Aufwand her überschaubar.
Das hat man aber doch nur, wenn man ein Gesamtbild hat. Nur wenn ich ein Gesamtbild habe, weiss ich, was ich brauche.
Damit habe ich ein überschaubaren Task, den ich bearbeiten muss. Dabei stelle ich dann fest: Ich muss irgendwo abprüfen, ob eine Zahl gerade ist. Also mache ich diese Methode. Das mache ich aber doch nicht irgendwie ("Ach, ich soll da war mit einem Auto machen - packe ich das also einfach in die Klasse "Car". So geht doch hoffentlich niemand vor, denn SOLID gilt ja bei jeder Entwicklung.) Wobei das sogar auch ginge. Aber nach dem erfolgreichen Test (nach der "rot" und "grün" Phase) würde dann das Refactoring kommen - damit würde dann spätestens auf die SOLID Principles geachtet.
Edit: mrBrown und nicht mihe7 - sorry für die Verwechslung und Danke für den Hinweis.
Ach je, irgendwie werfe ich euch beide immer durcheinander ... aber schlimm ist ja, dass ich die Links erst geöffnet hatte - da hätte ich es sehen können / müssen.
Sorry und schande Asche auf mein Haupt.
Edit: Habs in meinem Beitrag noch korrigieren können. Und habe hier noch Rechtschreibung angepasst. Und die Redewendung ist "Asche auf mein Haupt" meine ich, oder?
der code ist zwar miserabel aber erfüllt die Unit Tests also denkt man sich "wunderbar das funktioniert also weiter gehts"
ich muss ja nicht nachdenken, wenn die tests funktionieren passt ja alles.
Das ist ein elementarer Fehler. TDD besteht aus drei Schritten:
a) Schreibe roten Testfall
b) Schreibe Code der den Testfall grün macht
c) Refaktore den Code, bis er ordentlich ist ohne die Tests kaputt zu machen.
Und der dritte Schritt ist der essentielle Schritt, ohne den kommt Murks raus. Die beiden anderen Schritte stellen nur sicher, dass man auf dem Weg zu ordentlichen Code Netze & doppelten Boden hat.
2.
wenn man zu viele UnitTests hat ist es fast unmöglich irgendwas an den Anforderungen zu ändern da man ansonsten den halben Tag rein steckt die Unit Tests so anzupassen dass sie den neuen anforderungen entsprechen
ich habe bis jetzt 1en sinnvollen test geschrieben
ich hab was geändert und plötzlich hat die methode null zurück gegeben und ohne den Test hätte ich es nicht gefunden... hätte irgendwo in git commits rum schaufeln müssen und suchen "wie wars denn vorher"
Tests dienen zu deutlich mehr Dingen, als nur sicherstellen "Bei Änderung macht niemand was kaputt". Auch wenn ich persönlich mit TDD nicht warm werde, ist der Gedanke dahinter sinnvoll.
Tests dienen dazu:
Sicherzustellen das bei Änderungen nichts kaputt geht
Das ist der offensichtlichste Punkt und der, auf dem am Anfang der meiste Fokus liegt
Sicherstellen, das man an alles in der Implementierung gedacht hat
Unabhängig ob man mit TDD arbeitet oder Tests danach schreibt. Die Überlegung "Was sind erlaubte Eingaben und was erwarte ich eigentlich als Ergebnis" ist eine Sichtweise, die extrem wichtig ist. Mal ganz banales Beispiel, Quicksort für ein Array zu schreiben ist nicht schwer. Aber wenn man die Tests schreibt, sollte man auch mal testen:
Was ist, mit einem Array der Länge 0?
Was ist, wenn das Array null ist?
Was ist, wenn das Array null-Elemente enthält?
Vor allen Dingen die Randfälle (wie Länge 0) oder Sonderfälle (null) übersieht man gerne bei der Implementierung. Ich kann die Fälle nicht mehr zählen, wo die Tests mir Flaws in meiner Umsetzung aufgezeigt haben. Und das deutlich schneller, als die Anwendung zu bauen, auf einen Server zu deployen und die Konstellationen von Hand durchzuklicken.
Eine saubere, vernünftige API sicherstellen
Das sehe ich als größten Benefit von TDD. Man stellt sicher, dass der Code auch wirklich getestet wird. Schreibt man erst Code und dann Tests stellt man fest, dass man gewisse Stellen mit Tests nicht rankommt, weil das so tief in private Methoden ist, dass es nicht testbar ist. Das heißt, die API ist klar und ich kann alles gewünschte Verhalten über die API meiner "Unit" ansteuern. Und es gibt kein internen Verhalten, was nicht ansteuerbar ist
Dokumentation
Javadoc veraltet, wird nicht gepflegt. Weil niemand stellt fest, wenn es irgendwann mal falsch ist. Mit sauberen Unit-Tests hat man gleich eine Dokumentation einer Schnittstelle. Schaut man sich mal z.B. die Dokumentation von https://commons.apache.org/proper/c...StringUtils.html#center-java.lang.String-int- an, stellt man fest, im Prinzip besteht die Hälfte des Javadocs eigentlich aus Unit Tests:
Intern machst du assert um sicherzustellen, dass die Konstellation ok ist.
Ein klassisches Problem ist oft z.B. LocalData.now()
Java:
publicMyPerfectClass(){publicintberechneAnzahlTag(){LocalDate datum =LocalDate.now();// Ganz viel Code der aus datum was berechnet}
Funktioniert der Code am 29.2? Am 31.12? Am 1.1?
So nicht testbar, weil du diese Konstellation nicht ansteuern kann. Und das am 1.1/31.12 oder 29.2 Code kaputt geht ist ein Klassiker.
Schreibt man TDD, sorgt man automatisch dafür, dass das Datum für Tests beeinflussbar ist. Und wenn es nur sowas ist:
Java:
publicMyPerfectClassTestbar(){protectedLocalDategetCurrentDate(){returnLocalDate.now();}publicintberechneAnzahlTag(){LocalDate datum =getCurrenDate();// Ganz viel Code der aus datum was berechnet}
da ich in die Admin schiene gegangen bin hab ich nur durch "persönliche" Projekte sachen mitgekriegt für normale software entwicklung dementsprechend bin ich blank aufgestellt wenns tiefer rein geht
Das erklärt dann zumindest zum Teil deine Probleme mit den Unit Tests. Bin jetzt nur noch auf dem Handy, aber ich versuche, morgen mal etwas mehr dazu zu schreiben.
Das erklärt dann zumindest zum Teil deine Probleme mit den Unit Tests. Bin jetzt nur noch auf dem Handy, aber ich versuche, morgen mal etwas mehr dazu zu schreiben.
Vorausgesetzt, es ist wirklich C#-Code, könnte man im passenderen Ökosystem recherchieren: mycsharp.de -> Artikel -> [Artikel] Unit-Tests: Einführung in das Unit-Testing mit VisualStudio (weiteres generelles Stichwort auch: parametrische Tests)
Einen ganz wichtigen Punkt, der schon etwas angeklungen ist, möchte ich noch vertiefen. Der Fokus, was getestet werden soll!
Man will genau einen spezifischen Punkt der Spezifikation testen. Dann bedeutet dies aber auch: Wirklich nur genau diesen einen spezifischen Punkt!
Das ist ganzwichtig, denn dadurch wird erreicht, dass eben Änderungen sich nur lokal auswirken.
Ich konstruiere ein kleines Beispiel:
- Wir haben einen FizzBuzzValue Generator. Der bekommt also eine Zahl als Parameter und gibt einen String zurück. Von der Implemenmtation her etwas wie (Auszug):
Java:
publicStringgenerate(finalint number){if(number < GENERATE_MIN_VALUE){thrownewIllegalArgumentException("Number must be at least "+ GENERATE_MIN_VALUE);}if(isFizz(number)&&isBuzz(number)){return FIZZBUZZ_VALUE;}if(isFizz(number)){return FIZZ_VALUE;}if(isBuzz(number)){return BUZZ_VALUE;}returnString.valueOf(number);}
- Nun haben wir noch eine Klasse, die eine Liste von FizzBuzzWerten erzeugt:
classFizzBuzzListGeneratorTest{@ParameterizedTest@CsvSource(value ={"0","1, 1","2, 1,2","3, 1,2,Fizz"})publicvoidtestGenerateList(int upTo,@AggregateWith(ListAggregator.class)List<String> result){FizzBuzzListGenerator generator =newFizzBuzzListGenerator();assertEquals(result, generator.generateList(upTo));}staticclassListAggregatorimplementsArgumentsAggregator{@OverridepublicObjectaggregateArguments(ArgumentsAccessor arguments,ParameterContext context){List<String> result =newArrayList<>();for(int i =1; i < arguments.size(); i++){
result.add(arguments.getString(i));}return result;}}}
Das beschriebene Problem ist jetzt aber: Wir testen nicht nur den FizzBuzzListGenerator.generateList() sondern wir testen auch den FizzBuzzGenerator.generate. Wenn sich der FizzBuzzGenerator ändert, dann brechen wir nicht nur den Unit Test vom FizzBuzzGenerator (was korrekt wäre - die Änderung wäre ja eine Änderung der Spezifikation. Bei durch 4 teilbaren Werten kommt noch ein "Stop" dazu. 4 -> "Stop", 12 -> "FizzStop", 20 -> "BuzzStopp", 60 -> "FizzBuzzStop" ....)
wenn man zu viele UnitTests hat ist es fast unmöglich irgendwas an den Anforderungen zu ändern da man ansonsten den halben Tag rein steckt die Unit Tests so anzupassen dass sie den neuen anforderungen entsprechen
Damit haben wir zwei Konstruktoren - einen, der so wie der bisherige Konstruktor ein Standard Objekt braucht. Und einen, der der halt den Generator entgegen nimmt.
2. Nun können wir beim Unit Test einen neuen Generator unterschieben. Und da ist es am einfachsten, dass wir einfach die Werte 1:1 durchreichen:
Wir mocken also den FizzBuzzGenerator. Und wenn generate mit irgend einem int aufgerufen wird, dann antworten wir: Vom Aufruf (i ist der invocation Parameter von der Answer.answer Methode) den ersten Parameter geben wir als String zurück.
Das lenient() am Anfang wird hier nur benötigt, da beim erstenb Testfall der Generator nicht aufgerufen wird. da wirft Mockito dann eine Exception, denn unnötige Vorgaben mag Mockito per default nicht. Und mit dem lenient() davor ist es dem Mockito bei der Regel egal.
Und nun ist uner Ergebnis: Den FizzBuzzGenerator können wir so viel anpassen, wie wir wollen. Der Unit Test vom FizzBuzzListGenerator wird davon nicht beeinflusst.
Refactorings, die denkbar sind: Man kann das alles noch über ein Interface machen. Dem List Generator ist der ValueGenerator ja egal - daher kann man da ein funktionales Interface bauen und es entkoppeln. Und das ist etwas, das einem doch schon fast in den FIngern juckte, wenn man den Code da so gesehen hat.
Refactorings, die denkbar sind: Man kann das alles noch über ein Interface machen. Dem List Generator ist der ValueGenerator ja egal - daher kann man da ein funktionales Interface bauen und es entkoppeln. Und das ist etwas, das einem doch schon fast in den FIngern juckte, wenn man den Code da so gesehen hat.
Das ist übrigens der Punkt, den ich als größten Benefit von TDD sehe (auch wenn ich selber damit wie gesagt nicht warm werde).
Man will in Regel kleine Funktionalitäten testen. Die kleinste, testbare Einheit ist eine Methode. => Man schreibt nur kleine Methoden und splittet das Problem auf.
Man will spezifische Funktionalität testen und unabhängig von andere Komponenten testen. => Man sorgt dafür, dass die anderen Komponenten nicht im Bauch irgendwo erzeugt werden, sondern sauber und klar an einer Stelle definiert werden. Am besten auch von außen reingegeben. Hier kommt dann auch die Dependency Injection zum tragen, die das unterstützt. Man hat dann also eine Komponenten Architektur die transparent ihre Abhängigkeiten macht.
Man will seine eigene Funktionalität testen und nicht andere Komponenten mit testen. => Man tauscht andere Komponenten durch Mock/Dummy-Implementierungen aus. Um sicherzustellen, dass man immer noch das richtige testet, wird es essentiell, dass die Schnittstelle dieser Komponente klar definiert ist. Ansonsten weiß man nicht, ob die Mock/Dummy-Implementierung was sinnvolles tut. Das wiederum sorgt dafür, dass man ggf. Interfaces anlegt und das die APIs von anderen Komponenten klar und verständlich sind.
Das ganze macht das Testen aber auch alles andere als trivial und der Sprung von "Ich mache einen Unit-Test meiner add(int a, int b) Methode" hinzu "Ich verbessere und sichere meine Architektur mit Unit-Tests" ist extrem groß. Deswegen auch nicht aufgeben - man lernt immer was dazu.
Auch ich hab trotz zig Jahren Software-Entwicklung durch diesen Thread noch mal den ein oder anderen Blickwinkel bekommen, der mir vorher so nicht präsent war. Man lernt halt nie aus
Wir mocken also den FizzBuzzGenerator. Und wenn generate mit irgend einem int aufgerufen wird, dann antworten wir: Vom Aufruf (i ist der invocation Parameter von der Answer.answer Methode) den ersten Parameter geben wir als String zurück.
Das lenient() am Anfang wird hier nur benötigt, da beim erstenb Testfall der Generator nicht aufgerufen wird. da wirft Mockito dann eine Exception, denn unnötige Vorgaben mag Mockito per default nicht. Und mit dem lenient() davor ist es dem Mockito bei der Regel egal.
Und nun ist uner Ergebnis: Den FizzBuzzGenerator können wir so viel anpassen, wie wir wollen. Der Unit Test vom FizzBuzzListGenerator wird davon nicht beeinflusst.
der mock versucht es unwichtig zu machen wie klasse X implementiert ist wenn Y ein objekt davon braucht
ohne mock würde ich ja bei der X änderung auch Y zerstören und mit dem mock umgehe ich das dann?
ich habe mal gesucht nach beispielen die ich in der vergangenheit hatte wo ich probleme hatte zum bleistift hier.
was hätte man anders machen können, sieht nicht richtig oder hilfreich aus
im prinzip habe ich eine mathe formel für meine Widerstands klasse
Code:
float Divider;
float clampedReductionMultiplier;
public float Calculate(float armor)
{
var divider = Math.Clamp(Divider, 0f, 100f);
var clampedReductionMultiplier = Math.Clamp(ReductionMultiplier,0f,1f);
return Math.Clamp((armor / (armor + divider)) * clampedReductionMultiplier ,0.1f,0.9f);
}
Ich finde, dass das kein gutes Beispiel ist.
<
Wenn ich eine Gleichung x^2+px+q auf Nullstellen berechne, dann kann ich drei Lösungsfälle haben:
keine Lösung(D<0)
eine Lösung(D=0)
zwei Lösungen(D>0)
mit D=(p^2)/4-q
Offensichtlich sollte ene Testsuite diese drei Fälle absichern sowie einige Fälle, bei denen x1 und x2 bekannt ist, und die bere3chneten mit den erwarteten Werten verglichen werden.
und da wirds dann wieder schwer sinnvolle sachen zu machen
das fühlt sich schon während dem bauen nutzlos an, in c# hab ich ncrunch was mir wenigstens halbwegs garantiert dass ich nichts zerschieße
was aber nicht hilft wenn meine tests von vorne herein kaum was abdecken
Hast Du mal ein TDD Buch durchgearbeitet? Kent Becks "Test Driven Development by example" geht wirklich an Beispielen alles durch. Da bekommst Du dann mit, wie der Code aussieht und auch die Tests. Selbst wenn man nicht Test Driven arbeitet, schreibt man Code, der testbar ist.
Aber das ändert nichts daran, dass man lesbaren Code schreiben soll. Dein Code aus #35 ist absolut unleserlich. Was soll der Code sinnvolles machen? Das ist so nicht verständlich.
float Divider;
float clampedReductionMultiplier;
Du hast da also irgend einen State, der irgendwas aussagt.
`public float Calculate(float armor)
Super - es wird was berechnet. Das erklärt die Mathematische Formel natürlich direkt.
var divider = Math.Clamp(Divider, 0f, 100f); Also eine lokale Variable die genau so heißt wie der State der Instanz - nur mit kleinem Buchstaben. Und die magische Grenzen 0 - 100. Wieso gelten die bei divider und nicht bei Divider?
var clampedReductionMultiplier = Math.Clamp(ReductionMultiplier,0f,1f); Hier ist wenigstens ein klarer deutlicher Name. Aber warum Grenz von 0 - 1?
Diese zwei Zeilen haben irgend eine Business Logik, die nirgends erläutert wird. Was genau machst Du da? Das wären dann ggf. Methoden mit sinnvollem Namen.
return Math.Clamp((armor / (armor + divider)) * clampedReductionMultiplier ,0.1f,0.9f); Eine Berechnung - super. und wieder eine Begrenzung auf in Intervall ...
Und da sind dann einfache Regeln umzusetzen:
Keine Magic Numbers! Im code hat es keine 0f, 1f u.s.w. zu geben!
Vernünftige Bezeichner
Es wird nur eine Sache gemacht. Hier sind mehrere Dinge mit den ganzen Begrenzungen der Eingaben (das nenne ich mal so, auch wenn es ja ein State der Instanz ist).
Und jetzt ist mir @LimDul zuvor gekommen mit seiner Antwort, aber bei den Unit Tests musst Du anschauen, was es denn für Fälle gibt. Da sind dann die Grenzfälle wichtig. Also tatsächlich Werte, die Außerhalb der Grenzen liegen und angepasst werden (z.B.).
Und dann ganz wichtig: Dokumentiere den Code! Methoden und so werden dokumentiert! Das ist wichtig, denn Visual Studio zeigt Dir das dann auch an. Und es hilft, das dann auch zu verstehen, was Du da überhaupt machst.
Woher kommt diese Formel, nach der berechnet wird? Was ist das Ziel davon? Was die Anforderungen dahinter?
Und ein weiterer Gedanke kommt mir gerade: Kann es sein, dass Du einfach zu viel durcheinander machst? Du bis in PowerShell unterwegs, C#, Java, ... Du machst vieles etwas, aber vermutlich nichts richtig. Konzentriere Dich auf eines und meistere das! Dann hast Du eine Baustelle und kannst diese nach und nach immer weiter ausbauen. Kannst da dann immer mehr Dinge dazu nehmen.
Aber das ist ein Eindruck, der evtl. auch täuschen kann. Aber ich bin da relativ strikt: Ich mache eine Sache. Wenn ich diese eine Sache mache, dann bleibt alles Andere liegen soweit es geht. Die Themen sind einfach zu komplex!
Bei mir wäre das konkrete Beispiel: .Net MAUI mit Blazor ist etwas, das ich gerne machen würde um da rein zu kommen. Aber es gab auch eine Entscheidung zu Quarkus statt Spring Boot und dann ist das halt jetzt das Thema. Da bleibt Visual Studio halt jetzt weg und Quarkus ist das Thema. Und die Komplexität ist direkt da, denn da kommen dann ja auch viele Themen mit wie Keycloak, Docker, ....
es sollte die auswirkung der rüstung darstellen, als widerstand
1. es muss ein prozentualer wert sein der den schaden verringert
2. umso mehr rüstung man hat umso schlechter skaliert es
3. man muss mindestens 10% schaden bekommen: schaden *(1- widerstand)> schaden *0.14. man darf nicht den schaden um 0% verringern ( mögliche 0 division die nur ärger macht )
das war so der plan fürs erste.
dann bin ich nach geogebra gegangen und habe die formel aufgestellt, hier mal mit * 100 dass man es besser sieht was passiert
für die die es ausprobieren wollen:
d = 50
u = 1
f(x) = ((x)/(x+d)) u*100
x wäre hier die rüstung und Y der prozentuale reduzierer
( die klasse "Resistance" für die Werte wurden aus einer xsd generiert was die properties angelegt hat )
dann hab ich irgendwelche punkte der kurve genommen und diese in den hier vorliegenden test geschrieben.
und dann die formel in C# eingebaut, das war das vorgehen soweit
@LimDul
ich hätte es nicht hingekriegt die grenzfälle zu betrachten, ich tu mich da eigentlich ziemlich schwer
bei einer Mathe formel wäre es da eigentlich das einfachste wenn man das jetzt mit deinen beispielen betrachtet.. und nicht mal da krieg ich es hin
wie denkt man dass man die grenzfälle raus bekommt? sollte ich mir den dämlichst möglichen fall überlegen?
Kann es sein, dass Du einfach zu viel durcheinander machst? Du bis in PowerShell unterwegs, C#, Java, ... Du machst vieles etwas, aber vermutlich nichts richtig. Konzentriere Dich auf eines und meistere das!
ich muss in der Arbeit mit powershell und bash hantieren, da das die "als standard" gesetzten sprachen sind.
C# und java darf ich nicht hernehmen weil es kein anderer aus dem Team lesen kann. Damit bin ich da an Powershell gebunden.
Im privaten Bereich mache ich primär mit Godot C# aber für die "häuslichen anwendungen" nehme ich javafx her da ich das am besten kann von allen GUI Bibliotheken, ich sollte da wahrschienlich doch eher auf MAUI ( wo ich noch nichts gemacht habe ) umsteigen dass ich den java teil weg habe
Es wird nur eine Sache gemacht. Hier sind mehrere Dinge mit den ganzen Begrenzungen der Eingaben (das nenne ich mal so, auch wenn es ja ein State der Instanz ist).
ich muss gestehen ich hab nicht erwartet dass geogebra es verträgt mehrere buchstaben zu benutzen.
ich wüsste ja nicht mal wie man es beschreiben sollte...
kurvigkeit => wie schnell kommt man auf den maximal wert der kurve
faktorerhöhung => "schiebt" die kurve in Y richtung, also reduziert den maximal wert der kurve
ich weis nicht mal wie man diese kurve überhaupt nennt oder wie man faktoren mathematisch beschreibt was die tun
PS:
ich muss auch noch die schadens berechnung machen und da versuche ich dann das hier anzuwenden.. mal schauen wie das hinhaut
ich habe noch etwas an den Anforderungen geschraubt dass eine Obergrenze eigentlich komplett egal ist, nur die Prozentuale rückgabe muss im Bereich liegen
Hier mal das ganze so wie es jetzt rum steht.. ich habe versucht die Tests zu machen die Limdul gesagt hat und auch versucht den Code "lesbarer" zu machen nachdem was Konrad gesagt hat
Vor allem: Du kannst in Visual Studio vor der Klasse oder vor Methoden einfach /// eintippen und schon bekommst Du ein grobes Skeleton mit Summary, Parametern und return und so.
Und die Arange, Act und Assert Kommentare kannst Du weg lassen. Das ist die Erklärung, wie Unit Tests aufgebaut sind - das muss man nicht ständig dazu schreiben. Du kannst die Blöcke einfach mit Leerzeile trennen und dann hast Du die Struktur ja auch deutlich genug gemacht.
Wäre es nicht trotzdem angenehmer, die Bezeichner an so etwas wie eine UswStory zu binden?
Dem Charakter entsteht eine enemyDamage.( durch den Feind ausgelösten Schaden). Durch eine Rüstung wird dieser Schaden deren Stärke armorStrongness reduziert, wobei die Stärke der Verrringerung vom materialFaktor abhängt. Es ergibt sich dann als Ergebnis übrigens die effectiveDamage.
Diese camelCase-Bezeichnung fände ich hilfreich, um das Programm und die Bezeichner zu verstehen.
ich habe jetzt nochmal versucht doku zu schreiben
das </remarks> gibts in dotnet gar nicht, das wurde von xsd.exe generiert was auf mono ausgelegt war
ich habe versucht die kommentare halbwegs so zu setzen
C#:
/// <summary>/// Calculates the Effectiveness of an Armor/// </summary>[System.CodeDom.Compiler.GeneratedCodeAttribute("xsd","0.0.0.0")][System.SerializableAttribute()][System.Diagnostics.DebuggerStepThroughAttribute()][System.ComponentModel.DesignerCategoryAttribute("code")][System.Xml.Serialization.XmlTypeAttribute(AnonymousType=true, Namespace="ResistanceCollection")]publicpartialclassArmorEffect{privateconstfloat LOWEST_MULTIPLIER =0.1f;privateconstfloat LOWEST_DIVIDER =10f;privateconstfloat LOWEST_RESISTANCE =0.1f;privateconstfloat HIGHEST_RESISTANCE =0.9f;privatestring nameField ="DEFAULT";privatefloat reductionFactor =0.8f;privatefloat scalingDivider =50f;/// <summary>/// This Name links the ArmorType to it's according Resistance Type/// </summary>publicstring Name {get{returnthis.nameField;}
init {if(String.IsNullOrEmpty(value))return;this.nameField =value;}}/// <summary>/// The <see cref="ReductionMultiplier"/> property is used to define how well an armor scales./// The higher this value is, the the higher is the maximum of reduction./// If the value passed in is less than the <see cref="LOWEST_MULTIPLIER"/>, then the <see cref="LOWEST_MULTIPLIER"/> value will be set./// </summary>publicfloat ReductionMultiplier
{get=> reductionFactor;
init => reductionFactor =BindToLowest(value, LOWEST_MULTIPLIER);}/// <summary>/// The Divider property is used to define how fast armor reaches it's approximate maximum./// The lower this value is, the faster it will reach the maximum.>/// If the value passed in is less than the <see cref="LOWEST_DIVIDER"/>, then the <see cref="LOWEST_DIVIDER"/> value will be set./// </summary>publicfloat Divider {get=> scalingDivider;
init => scalingDivider =BindToLowest(value, LOWEST_DIVIDER);}/// <summary>/// Transforms the Armor to the effective Reduction of damage./// This value is Clamped by <see cref="BindToResistanceBorders(float)"/>/// </summary>/// <param name="armor">The amount of Armor that needs to be transformed to a Percentage</param>/// <returns>The Percentage of how much this Armor can reduce</returns>publicfloatGetEffectivePercentage(float armor){returnBindToResistanceBorders((armor /(armor + scalingDivider))* reductionFactor);}/// <summary>/// clamps the value between <see cref="LOWEST_RESISTANCE"/> and <see cref="HIGHEST_RESISTANCE"/> so it will always be an percentage between these two values./// </summary>/// <param name="value"></param>/// <returns></returns>privatefloatBindToResistanceBorders(floatvalue){return Math.Clamp(value, LOWEST_RESISTANCE,HIGHEST_RESISTANCE);}privatefloatBindToLowest(floatvalue,float lowestValue){if(value< lowestValue)return lowestValue;returnvalue;}}
das mit dem Pascal case ist von C# so gedacht..man sollte das so machen
Erst einmal hat das nichts mit der c# Sprache zu tun - für C# sind das ja einfach nur Kommentare. Der C# Compiler kann aber die XML Tags nutzen, um eine XML Dokumentation zu erzeugen (So man dies im Projekt aktiviert).
Ich finde, dass immer noch getestet wird, ob das Programm richtig rechnet. Und das tut es. Ich würde das mal anders denken.
Der Gegner kann einen Schlag mit einer Stärke von 1 bis 10 ausführen. Der Held hat eine Gesundheit von 0 bis 10. Nach dem Schlag beträgt die Gesundheit bei einem ungeschützten Helden:
neue Gesndheit= alte Gesundheit-Schaden
Trägt der Held eine Rüstung, so wird
neue Gesundheit=alte Gesundheit-(Schaden-Rüstung)
oder
neue Gesundheit=alte Gesundheit-Schutz
wobei neue Gesundheit<=alte Gesundheit.
Dabei ist der Rüstung-Wert in einem Bereich von 0 bis 10 zu finden.
Schutz hat dann auch einen Wertebereich von 0 bis 10.
Man kann nun tatsächlich sagen, dass eine Rüstung aus Kupfer leichte Schläge besser wegsteckt als schwere, jedoch im ganzen weniger Effektiv ist als eine Eisenrüstung. Eine Eisenrüstung steckt sowohl leichte als auch schwere Schläge gut weg.
Und hier kommen die Testfälle ins Spiel. Hier muss man eben eine Formel finden, die das vernünftig abbildet, wenn man die obigen Wertebereiche nutzt.