Hallo Gemeinde,
eine ganz allgemeine Frage: Wie granular sollen die Tests beim Unit-testen sein?
Hintergrund:
Bei einer kleinen Sessions haben wir eine Klasse zu Berechnungen mit dem Collatz-Algorithmus implemetiert:
static void Main(string[] args)
{
int currentValue;
int iterationscounter = 0;
Console.Write("Bitte geben Sie eine Nummer ein: ");
currentValue =Convert.ToInt32(Console.ReadLine());
while (currentValue!=1)
{
if (currentValue % 2 == 1) {
currentValue = currentValue * 3 + 1;
}
else
{
currentValue = currentValue / 2;
}
iterationscounter++;
}
Console.WriteLine("Es wurden "+iterationscounter+" Iterationen benötigt");
Console.ReadKey();
}
und wollten diesen Algorithmus testbar machen.
Aufgrund eines Tipfehlers schlugen die Tests jedoch immer fehl, weshalb wir für jeden einzelnen Schritt
a.) prüfen ob der momentane Wert grade ist
b.) den momentanen Wert halbieren
c.) den momentanen Wert mit 3 multplizieren und das Ergebnis um 1 erhöhen
d.) Arbeitsschritt: b. wenn der momentane Wert grade, sonst c
e.) wiederhole d. solange der momentane Wert !=1
in Methoden und Funktionen refaktorierten.
Die Anzahl der Wiederholungen wurde über ein Property zurückgeliefert, e. wurde als eine öffentliche Methode implementiert und der Test auf die Richitgkeit der Berechnung wurde gegen einem händisch ermittelten Wert für die Anzahl der Iterationen durchgeführt.
Die Klasse sah dann so aus:
public class Collatz
{
int currentValue;
int iterationsCounter = 0;
public int IterationCounter
{
get
{
return iterationsCounter;
}
}
public Collatz(int value)
{
currentValue = value;
}
private void shrink()
{
currentValue = currentValue / 2;
}
private void grow()
{
currentValue = currentValue * 3 + 1;
}
private bool isEven()
{
return currentValue % 2 == 1;
}
private void calculationStep()
{
if (isEven())
shrink();
else
grow();
}
public void work()
{
while (currentValue != 1)
{
calculationStep();
iterationsCounter++;
}
}
}
Sollten nicht alle Methoden, also auch die "privaten" testbar gemacht werden?
Z.B. durch durch Preprocessor Directives?
Viele Grüße
Ernst Jürgen
Man muß nichts wissen,
man muß nur wissen wer es wissen könnte
oder wo es steht😉
Es gibt ganze Religionskriege zu diesem Thema.
Meine Meinung ist, das der von Aussen zugesicherte Kontract getestet werden sollte.
Denn Unittest sollen ja das Refactoring unterstützen und sicherstellen das eine einmal funktionierende Routine später immer noch funktioniert.
Wer mehr testen will, soll das tun, ich mach es jedenfalls nicht.
Fzelle hat schon recht. Wenn etwas "public" gesetzt wurde, heisst das, dass es von aussen verwendet werden darf. Aber auch, dass es getestet werden darf. Zumal es auch ein Interface geben kann, auf dem alle public Methoden aufgeführt werden (Contract).
Ist eine Methode "private" gekennzeichnet, und man sie unbedingt testen will, sollte man sich fragen: Hat sie dann nicht eine eigene Klasse verdient (die richtig benannt ist), die auch getestet werden kann?
Aber da hat jeder seinen Stil und wohl auch seine Meinung.
Siehe auch Unit Test von privaten Methoden
Falls du Internals von aussen testen willst, beachte das "InternalsVisibleTo"-Attribut
Microsoft MVP // Me // Blog // GitHub // @Egghead // All my talks // Speakerdeck
Hallo,
erstmal Dank für eure Response.
Einen Glaubenskrieg oder eine irgendwie anders geartete Auseinandersetzung wollte ich mit meiner Frage in keinester Weise vom Zaun brechen.
Mir drängt sich jedoch auch die Frage auf ob, wenn es ein Anforderung ist, dass eine Methode einen ganzzahligen Wert zurückliefern soll, es micht einen erheblichen Unterschied macht wie dieser Wert zustande gekommen ist.
Das Requirement ist zwar erfüllt (ganzzahligen Wert zurückliefern), aber wenn die Methode mir diesen Wert als 0xff liefert würde mich schon interessieren wie, wieso und warum.
Auch das Testen von Properties, von welchem ich gelesen habe, das man das "NIE" macht, würde ich immer vom Zweck des Property abhängig machen (Never say Never).
Ich habe jetzt für DEBUG, via Preprozessor, öffentliche Wrapper-Methoden erstellt, welche mir das testen einzelner MicroRequirements, wie z.B. die Richtigkeit einer Berechung sehr erleichtert.
Wie ich feststellen konnte, passiert es mir bei vielen kleinen Tests, welche auch "nicht public"-Methoden umfassen, nicht, dass ich "vorne eine Schraube löse und an einer anderen Stelle fällt ein Ohr ab.
Aber das kann ja jeder halten wie er will.
Viele Grüße und viel Spaß beim Coden
Ernst Jürgen
Man muß nichts wissen,
man muß nur wissen wer es wissen könnte
oder wo es steht😉
Deine Klasse stellt nach außen (durch die Unterscheidung in public und andere Methoden) eine Schnittstelle bereit, und getestet werden sollte die Funktionalität dieser Schnittstelle und nicht die konkrete Implementierung.
Das bedeutet auch, dass du bereits beim Design der Klasse festlegst, wie sich ihre Methoden verhalten:
Dann wird der Test geschrieben.
Und DANN schreibst du die Methode. SO herum wird ein Schuh draus - du willst testen, ob eine Methode den beim Design festgelegten Anforderungen genügt. Falls sie das nicht tut, ist Aufgabe des Testes NICHT, herauszufinden, wieso. Dafür gibt es den Debugger.
Aus diesen Grundlagen ergeben sich mehrere Antworten auf deine Fragen:
a) private Hilfsmethoden werden nicht getestet
b) Einzelschritte von Algorithmen werden nicht getestet
c) Debugging ist debugging (#IFDEBUG kannst du also immer verwenden) und kein Testen
d) Properties werden getestet, falls sie eine eigene Logik bereitstellen und public sind
(Anm: eigentlich wäre das nicht Aufgabe einer Property, aber das ist dann wirklich GLaubensfrage)
Deine Frage, ob man zB folgende Methode public machen sollte aus Testgründen:
private void shrink()
{
currentValue = currentValue / 2;
}
bedeutet nichts anderes, als dass du die Korrektheit der Framework-FUnktion " / 2" testen möchtest. Dass man das nicht macht, sollte auf der Hand liegen.
Und noch etwas: wenn der Test nicht automatisiert und mit ausreichend TestCases inkl. Negativtests laufen kann, ist er kein Test.
Grüße,
LaTino
"Furlow, is it always about money?"
"Is there anything else? I mean, how much sex can you have?"
"Don't know. I haven't maxed out yet."
(Furlow & Crichton, Farscape)
Moment mal...
private bool isEven()
{
return currentValue % 2 == 1;
}
Ääääh. What the....
LaTino
"Furlow, is it always about money?"
"Is there anything else? I mean, how much sex can you have?"
"Don't know. I haven't maxed out yet."
(Furlow & Crichton, Farscape)
Nun grundlegend hat FZelle ja recht: „Es gibt ganze Religionskriege zu diesem Thema.„
Da ich aber nicht gläubig bin sondern Agnostiker. Möchte ich ihn aber nicht führen. 😉
Grundlegend gibt ja X Grunde um Unit Test zu schreiben.
Um mal die beiden wohl bekanntesten vorgehens Methoden anzugeben.
Das ist einmal TDD (test-driven development ), du schreibst also erst deinen Test und schaust dann, das deine Methode dir das Zurückgibt was du erwartest. Hier bietet sich im allgemeinen an nur die public Methoden zu Testen. (Es kann auch ausnahmen geben).
Dann haben wir noch das Refactoring, so als Beispiel, die 2000 Zeilen lange Methode, die sich schon seit 20 VQ ( vor Quellcodeverwaltung) im System befindet (Könnte natürlich auch, die geballte Zerstörungskraft, der Auszubildenden, in deinen 2 Wochen Urlaub sein 😉 ). Und dann doch irgendwie, das richtige Ergebnis produziert. Da bietet sich es an die private Methode erst mal als internal zu Deklarieren, um mit Test sicher zustellen, das sie auch in Zukunft das richtige Ergebnis zurück liefert.
Dann gibt es noch die Qualitätsanforderungen. Software für z.B. Flugzuege sollte einfach besser getestet werden, als die private HP.
Und dann gibt es meines Erachtens, noch die Sicht, auf die Produktivität. Ich persönlich stelle mir das immer wie einer Gauß kurve vor. Bis zu einem gewissen Punkt, bringen sinnvolle Unit Tests einiges an Produktivität, man bekommt nicht von Kunden/QS eine kryptische Rückmeldung. Sondern hat Standard Szenarien schon abgedeckt. (Ich kenne jetzt keinen guten Programmiere, der keine Fehler macht und dass nicht auch zugibt. Die schreiben auch alle Unit Tests. Ich kenne nur schlechte Programmiere, die behaupten keinen Fehler zu machen. Und dann oft auch keine Unit Tests schreien.) Umgekehrt wenn man wirklich für alles einen Unit Test schreiben will. Kann es bei Komplexeren Szenarien, bedeuten das man „Tagelang“ damit verbring auch jede Möglichkeit durch zudenken. Und wenn sich dann was ändert, müssen gegebenen Falls alle Unit Tests angepasst werden. Was dann nicht wirklich Produktiv ist.
Als Fazit zu den Punkten. Meines Erachtens gibt, keine Aussage, wenn du deine Unit Tests so schreibst, ist es immer Richtig. Dazu gibt es einfach zu viele Faktoren. Man muss halt verstehen, was man macht.
Um damit mal den Übergang zu privat/public zu machen. Wenn du für Unit Tests eine private Methode sichtbar machen musst. Solltest du erst mal dein Klassendesigne hinterfragen, im allgemeinen ist es ein Anzeichen dafür, das da was schief läuft. Es gibt aber durch aus Punkte an dehnen es Sinnvoll ist die Testbarkeit von Methoden herzustellen. Z.B. wenn es wirklich eine komplexer Teil eines Algorithmus ist, der Zentrale Berechnungen macht auf dehnen der Rest aufbaut und nicht Public sein soll. (Als kleine Anmerkung. Es gibt auch die Möglichkeit, die Methode als Protected zu deklarieren und bei deinen Unit Tests von ihr zu Erben. Das werden die wenigsten Leute machen, die deine Klasse nur Nuten wollen. Hat natürlich noch ein paar Harken.)
Man muss halt einfach verstehen was man macht.
Wie Sagt man es schön als Entwickler: „ Zwei mal Denken, einmal Schreiben!“
Sollte man mal gelesen haben:
Hallo,
@LaTino
private bool isEven()
{
return currentValue % 2 == 1;
}
diesen Fehler habe ich vorsätzlich, in den ursprünglichen Code, eingebaut.
@all
Ich weiß, dass ich Nichts weiß!
Und deshalb frage ich und versuche auf viele Arten Antworten auf meine Fragen zu bekommen.
Ich habe es schon mehr als einmal erlebt, das ganze Projekte in die Tonne getreten wurden, weil Entwickler "IHREN"-Code bis aufs Blut "beschützt" haben und ein refactoring einer "2000 Zeilen"-Methode grundweg ausgeschlossen wurdeoder der Kollege das Unternehmen verlies und die Methode unwartbar war und der Auftrag oder der Release-Termin nicht haltbar war.
Bei meinen Recherchen bin ich unteranderem auf solche Vorgehensweisen wie
Viele Grüße
bigeddie
Man muß nichts wissen,
man muß nur wissen wer es wissen könnte
oder wo es steht😉
Uncle Bob hat ein paar gute Videos gemacht: https://cleancoders.com/category/advanced-tdd
Kosten zwar Geld, sind aber sehr lang und auch recht unterhaltsam (wenn auch ein bisschen übertrieben für meinen Geschmack 😉). Lohnt sich aber.
Ich persönlich mache die Granularität auch davon abhängig wie oft sich Code ändert. Business Logik ändert sich bei uns einfach oft. Wir sind ein Startup, Anforderungen ändern sich, man probiert ein Feature aus, verwirft es dann wieder usw. Ich habe auch auf einem sehr granularen Level angefangen, wurde damit einfach nicht glücklich. Das hat so viel Zeit für Refactoring von Tests gebraucht, dass ich irgendwann frustriert alle Tests weggeschmissen habe. Das ist also auch nicht Sinn der Sache.
ImageTools for Silverlight: http://imagetools.codeplex.com | http://www.silverdiagram.net | http://www.cleancodedeveloper.de b:::
Hallo,
@LaTinoprivate bool isEven() { return currentValue % 2 == 1; }
diesen Fehler habe ich vorsätzlich, in den ursprünglichen Code, eingebaut.
Ah, verstehe. Bei solchen Aufrufen, die (wie bei Collatz) potenziell in sehr großen Schleifen oder rekursiv aufgerufen werden, lohnt sich übrigens (wert & 1 == 0) statt (wert % 2 == 0). Selbes Ergebnis, etwas weniger Ausführungsschritte. Nur nebenbei, weil ich's grad sehe.
"Furlow, is it always about money?"
"Is there anything else? I mean, how much sex can you have?"
"Don't know. I haven't maxed out yet."
(Furlow & Crichton, Farscape)
lohnt sich übrigens (wert & 1 == 0) statt (wert % 2 == 0) Vorsichtig mit solchen Äußerungen.
C# .Net: Fastest Way to check if a Number is Odd Or Even:::
Ja, kenne ich. Ist aber nur begrenzt nachvollziehbar. Hier copy & paste von seinem Code, selbes Ergebnis wie auch schon zu Hause:
Starting T1: Modulus %
Time: 00:00:09.6699222
##########################
Starting T2: Bitwise &
Time: 00:00:05.9972761
Also nahezu doppelt so schnell. Keine Ahnung, was er da gemacht hat. Wird dort auch schon in den Kommentaren heftig kritisiert.
LaTino
"Furlow, is it always about money?"
"Is there anything else? I mean, how much sex can you have?"
"Don't know. I haven't maxed out yet."
(Furlow & Crichton, Farscape)