Der Sinn hinter dem Testen, bzw. wieso nicht manuell durch debuggen testen?

Jack159

Lieutenant
Registriert
Dez. 2011
Beiträge
766
Hallo,

Ich beschäftige mich gerade etwas mit dem Thema "Testen", speziell mit JUnit. Dort gibt es dann so einfache Beispiele um Tests für beispielsweise eine add(int a, int b)-Methode zu schreiben, welcher dann prüfen soll, ob diese add()-Methode auch korrekt addiert.

Was ich nicht verstehe ist, wozu ich dafür extra einen Test schreiben sollte? Was ist der Sinn dahinter? Ich kann die add()-Methode doch auch einfach manuell testen, in dem ich mir das Ergebnis auf der Konsole ausgebe und damit dann selber prüfe.
 
Guten Tag,

der Sinn hinter den Testen ist eben, dass du es nicht manuell machen brauchst, einfach den entsprechenden JUnit-Test starten und schauen ob alles gut ist. Bei einem Taschenrechner ist es natürlich "egal", wenn du aber an ein Softwareprodukt denkst, wo viele Abhängigkeiten sind macht es deutlich mehr sind.
Und außerdem, denk mal an die Zeit, welche du mit debuggen benötigst. Wenn du als Arbeitgeber das bezahlen willst, tu es gerne, bist nur sehr schnell pleite.

@Limmbo, ja das ist in der Praxis tatsächlich gang und gebe, erst Beschreibung, dann Test und erst dann Entwicklung.
 
Zuletzt bearbeitet:
Naja bei komplexeren Programmen hat man nicht mehr alles im überblick und kann jede Funktion schnell und effektiv Testen. Gerade wenn du hunderte von Methoden hast und man verschiedene Bereiche abfangen musst, wird es mühselig. Daher JUnit Tests. Ich habe gehört aus meinem Software Engeneering Kurs (weiß nich ob es in der PRaxis so gemacht wird) das solche JUnit Tests in der Regel auch VOR dem eigentlichen Programm geschrieben werden sollten. Quasi als Referenz dazu, was man genau erreichen will.
 
Du kannst dir auch eine Kiste Bier kaufen in dem du 20x zum Supermarkt gehst und eine einzelne Flasche mitnimmst. Ist in etwa das selbe.
 
Jack159 schrieb:
Was ich nicht verstehe ist, wozu ich dafür extra einen Test schreiben sollte? Was ist der Sinn dahinter? Ich kann die add()-Methode doch auch einfach manuell testen, in dem ich mir das Ergebnis auf der Konsole ausgebe und damit dann selber prüfe.
Code:
add( 1, 2 );
add( 3, 4 );
add( -1, -5 );
add( 9, -1000 );
add( 1089273981729381273, -178238917298371273 );
add( null, 5 )
Der Typsicherheit wegen hast du hier weniger Tests. In typunsicheren Sprachen kommen dann noch Scherze wie die Übergabe von Arrays, Objekten und sonstigen Späßen hinzu (Strings mit Zahlen, Strings mit Zeichen, Strings mit Unicode-Zeichen, ...).

Viel Spaß beim manuellen Testen, wenn es hunderte unterschiedliche Konstellationen gibt. Willst du alles manuell nachrechnen, besonders mit großen Zahlen, plus Nachkommastellen, evtl. noch die Multiplikation oder die Wurzel?
Limmbo schrieb:
Ich habe gehört aus meinem Software Engeneering Kurs (weiß nich ob es in der PRaxis so gemacht wird) das solche JUnit Tests in der Regel auch VOR dem eigentlichen Programm geschrieben werden sollten.
Sollten ja, müssen nicht. Du bestimmst vorher einfach, was die Funktion machen soll und was für Ergebnisse gewünscht werden (speziell auch mit Sonderfällen u.ä., bspw. 0! = 1, x^0 = 1 usw.). Deswegen schreibst du vorher die Tests und in der Implementation musst du dann nur alles richtig machen. Wenns passt, stimmt natürlich alles überein.
 
Jack159 schrieb:
Was ich nicht verstehe ist, wozu ich dafür extra einen Test schreiben sollte? Was ist der Sinn dahinter? Ich kann die add()-Methode doch auch einfach manuell testen, in dem ich mir das Ergebnis auf der Konsole ausgebe und damit dann selber prüfe.

Das ist dann aber kein Test, sondern nur die Verifizierung der Funktionalität deiner Methode für ein konkretes Argument (oder für eine handvoll Argumente). In Wirklichkeit möchtest du aber ein größeres Spektrum an Eingaben testen (Stichwort Äquivalenzklassentest), dazu gehört auch, dass du testest, für welche Eingabe deine Methode nicht funktioniert oder eine Exception wirft usw.

Das schöne ist, dass du Tests für eine Methode in der Regel nur ein mal schreiben musst, aber so oft ausführen kannst, wie du möchtest. Änderst du was an deinem Code, so lässt du am Ende einfach ohne großen Aufwand den Test drüber laufen. Hättest du da tatsächlich Bock, stattdessen wieder den Debugger anzuwerfen oder auf die Konsole zu schreiben? Wenn deine Codebasis dann mal komplexer wird, bist du froh, wenn du eine Menge an Tests hast die du nach Änderungen einfach drüber laufen lassen kannst um zu schauen, ob noch alles passt.
 
Da ich aus der C/C++ Ecke komme und wir auch selber Code industrialisieren kommen noch ein paar weitere Punkte hinzu (weiß nicht ob man das in Java auch so macht).

Man fängt eben mit Unittests für einzelne Funktionen an. Sprich ich teste die Funktion mit einen Set von Eingabewerte und Resultaten. Diese müssen dann auch eintreten. Wenn du dann den erfolgreichen Test hast, hast du auch gleich für später einen Nachweis und kannst die Funktion auch in anderen Projekten verwenden bzw. nur den vorhandenen Test neu herauskramen.

Außerdem ist ein erfolgreicher Durchlauf auf einer Platform kein Nachweis, dass die Funktion immer richtige Ergebnisse liefert.
Gerade bei Operationen mit Fließkommazahlen kann auf einem Gerät die Ergebnisse noch genau genug sein, auf einem anderen aber schon nicht mehr oder man muss es eben dafür ebenfalls nachweisen.
Wie schon hier mehrfach erwähnt, nimmt dir so ein automatisierter Test dann viel Arbeit ab.
 
Zudem helfen dir Unit Tests beim sogenannten Regression Testing.
D.h. wenn du deine Software erweiterst kannst du schnell und einfach testen ob fruehere Funktionalitaet immernoch vorhanden ist.
 
kurz: der sinn ist es , dass nach den Änderungen im Code, man schnell die schon getesteten funktionen schnell wieder testen kann um sicher zu sein dass etas nicht unabsichtlich geändert wurde,
Unit testinf wird nur bei grösseren Projekten angewandt...
 
Das Argument mit der kombinatorischen Kompelxität würde ich nur bedingt zählen lassen. Ich muss ja trotzdem erstmal für jede Input-Kombination den erwartet Output angeben. Es sei denn, ich habe schon eine Referenzimplementierung, die ich aus irgendwelchen Gründen nicht nutzen kann.
Aber sobald du den Test mehr als einmal laufen lassen musst - und das ist in der Realität immer der Fall - sparst du dir gegenüber dem manuellen Testen Zeit und kannst dir auch sicher sein, dass du beim Auswerten des Tests keine Fehler gemacht hast.
 
M@C schrieb:
Zudem helfen dir Unit Tests beim sogenannten Regression Testing.
Das ist für mich persönlich die wichtigste Funktion. Dinge verändern und ergänzen zu können und mit einem Klick sicherstellen zu können, dass man mit sehr hoher Wahrscheinlichkeit nichts kaputt gemacht hat, erleichtert die Arbeit ungemein. Das geht natürlich nur wenn eine ausreichend hohe Coverage vorhanden ist und die Tests eine ausreichende Qualität haben. Manuelles Testen wird dadurch selbstverständlich nicht ersetzt, aber eben deutlich reduziert.

Unter Umständen können Unit Tests aber auch die Dokumentation ergänzen. Ein Blick auf die Tests zu einem Modul hilft häufig zu verstehen wie die Benutzung aus Sicht des Entwicklers aussehen sollte. Tests ersetzten keine Dokumentation können aber Dinge und Hintergründe erklären, die in Prosa nur beschränkt ausdrückbar sind.
 
Du bestimmst vorher einfach, was die Funktion machen soll und was für Ergebnisse gewünscht werden (speziell auch mit Sonderfällen u.ä., bspw. 0! = 1, x^0 = 1 usw.). Deswegen schreibst du vorher die Tests und in der Implementation musst du dann nur alles richtig machen.
Wobei man so entweder schon wissen muss, wie genau man die Funktion implementieren wird, um die Spezialfälle, die man testen muss, überhaupt zu kennen. Durch einen Black Box-Test wird das nämlich nicht gewährleistet.

Mal so als Beispiel: Ich habe hier eine Art memcpy-Funktion geschrieben, weil die aus der glibc leider häufig fürchterlich lahm ist. Was da so alles für Fälle eintreten können, die getestet werden müssen:

- Länge 0 (soll natürlich direkt übersprungen werden und keine Daten verändern)
- Ziel hat 16 Byte-Alignment, Länge irgendein Vielfaches von 64. Einfachster Fall.
- Ziel hat 16 Byte-Alignment, Länge ist kleiner als 64.
- Ziel hat 16 Byte-Alignment, Länge ist größer als 64, aber kein Vielfaches von 64
- Ziel hat 16 Byte-Alignment, Länge ist kein Vielfaches von 64, aber von 16
- Ziel hat 16 Byte-Alignment, Länge ist kein Vielfaches von 64 oder 16, aber von 2.
- Ziel hat 16 Byte-Alignment, Länge ist ungerade
- Ziel hat kein 16 Byte-Alignment, Länge ist gerade und größer als die Entfernung zur nächsten 16 Byte-Grenze.
- Ziel hat kein 16 Byte-Alignment, Länge ist kleiner als die Entfernung zur nächsten 16 Byte-Grenze.
- Ziel hat kein 16 Byte-Alignment, Länge ist ungerade

Und wahrscheinlich ist diese Liste nicht einmal vollständig oder korrekt. :freak:

Das weiß man natürlich erst, wenn die Funktion fertig implementiert ist, deswegen kommt man wohl kaum darum herum, den Test zumindest im Laufe der Entwicklung anzupassen.

Zudem helfen dir Unit Tests beim sogenannten Regression Testing.
Das ist wirklich ein sehr wichtiger Punkt. Funktion ändern, Test laufen lassen und feststellen, dass
a) entweder noch alles funktioniert, wie es soll, oder
b) einem früher oder später etwas um die Ohren fliegt, was man so direkt nicht gemerkt hätte.

Wenn Tests schreiben nicht so unglaublich nervig wäre....
 
Zuletzt bearbeitet:
VikingGe schrieb:
- Länge 0 (soll natürlich direkt übersprungen werden und keine Daten verändern)
- Ziel hat 16 Byte-Alignment, Länge irgendein Vielfaches von 64. Einfachster Fall.
- Ziel hat 16 Byte-Alignment, Länge ist kleiner als 64.
- Ziel hat 16 Byte-Alignment, Länge ist größer als 64, aber kein Vielfaches von 64
- Ziel hat 16 Byte-Alignment, Länge ist kein Vielfaches von 64, aber von 16
- Ziel hat 16 Byte-Alignment, Länge ist kein Vielfaches von 64 oder 16, aber von 2.
- Ziel hat 16 Byte-Alignment, Länge ist ungerade
- Ziel hat kein 16 Byte-Alignment, Länge ist gerade und größer als die Entfernung zur nächsten 16 Byte-Grenze.
- Ziel hat kein 16 Byte-Alignment, Länge ist kleiner als die Entfernung zur nächsten 16 Byte-Grenze.
- Ziel hat kein 16 Byte-Alignment, Länge ist ungerade

Und wahrscheinlich ist diese Liste nicht einmal vollständig oder korrekt. :freak:

wenn ich mir die Tests ansehe, sieht man aber direkt dass die Tests nach dem Entwickeln geschrieben wurden, denn wie du schon sagtest, sie decken jeden Spezialfall deiner Implementierung ab. Viele Fälle deiner Tests würden einfach rausfallen, wenn man - wie in einem korrekten Blackbox-Test - annimmt, dass man jegliche Länge angeben kann. Das deckt dann nicht jeden Code-Pfad deiner Implementierung ab, aber dies sieht man ja spätestens in der Coverage-Analyse.

Also für mich sind diese Tests viel zu speziell hingegossen auf eine Implementierung.
 
Zurück
Oben