Testing



  • DStefan schrieb:

    Mitarbeiter::calcResturlaub()
    

    Unter Berücksichtigung von:

    - Wechselnden Arbeitsverträgen mit unterschiedlichen Urlaubsansprüchen im Betrachungszeitraum.

    - Übernahme von Urlaub aus Vorjahren, aber nur, falls ein Antrag vorliegt und genehmigt wurde.

    - Automatischem Inkrement des Anspruchs nach Betriebszugehörigkeit, wobei die Betriebszugehörigkeit, der maximale Anspruch und die Höhe des Inkrements von einem Dutzend (oder so) Bedingungen abhängen.

    - Bereits genommenem Urlaub

    - Krankheitstagen innerhalb des bereits genommenem Urlaubs.

    - usw.

    Dieses Beispiel fällt mir spontan ein. Wobei hier nicht bloß die Implementierung des produktiven Codes, sondern auch der Tests verdammt viele Tage in Anspruch genommen haben.

    Dies ist übrigens auch ein Beispiel für eine Funktion, die ich um ungefähr 500% unterschätzt hatte 😉

    Sie ruft also so um die 10 Hilfsfunktionen auf, die die Daten zusammentragen und Zwischenergebnisse berechnen?



  • volkard schrieb:

    DStefan schrieb:

    Mitarbeiter::calcResturlaub()
    

    Unter Berücksichtigung von:

    - Wechselnden Arbeitsverträgen mit unterschiedlichen Urlaubsansprüchen im Betrachungszeitraum.

    - Übernahme von Urlaub aus Vorjahren, aber nur, falls ein Antrag vorliegt und genehmigt wurde.

    - Automatischem Inkrement des Anspruchs nach Betriebszugehörigkeit, wobei die Betriebszugehörigkeit, der maximale Anspruch und die Höhe des Inkrements von einem Dutzend (oder so) Bedingungen abhängen.

    - Bereits genommenem Urlaub

    - Krankheitstagen innerhalb des bereits genommenem Urlaubs.

    - usw.

    Dieses Beispiel fällt mir spontan ein. Wobei hier nicht bloß die Implementierung des produktiven Codes, sondern auch der Tests verdammt viele Tage in Anspruch genommen haben.

    Dies ist übrigens auch ein Beispiel für eine Funktion, die ich um ungefähr 500% unterschätzt hatte 😉

    Sie ruft also so um die 10 Hilfsfunktionen auf, die die Daten zusammentragen und Zwischenergebnisse berechnen?

    Ungefähr, ja. Willst du darauf hinaus, dass man die Hilfsfunktionen getrennt hätte testen können? Die meisten waren private, so dass das nicht ging.

    Stefan.



  • Die meisten waren private, so dass das nicht ging.

    #define private public
    


  • DStefan schrieb:

    volkard schrieb:

    Sie ruft also so um die 10 Hilfsfunktionen auf, die die Daten zusammentragen und Zwischenergebnisse berechnen?

    Ungefähr, ja. Willst du darauf hinaus, dass ...

    Gar nicht. Ich wollte den vagen Eindruck geklärt haben, ob Du da einen 500-Zeiler gebaut hast.

    Man hätte durch

    Und hier kann es vorkommen, dass man zur Implentierung einer einzigen Methode einer Klasse Tage braucht.

    den Eindruck gewinnen können.



  • DStefan schrieb:

    Sorry, aber Unit-Tests sind zum Testen aller und nicht bloß der kleinsten Einheiten da. Wenn eine Einheit über andere Einheiten implementiert ist, dann ist der Code, der diese "umschließende" Einheit testet, ein Unit-Test. Warum auch nicht? Es kommt doch nicht auf die Größe an, damit etwas ein Unit-Test ist!

    Angenommen Du hast Einheit A, B und C. Einheit C benutzt Einheit B und Einheit B benutzt Einheit A. Du schreibst einen Unittest um Einheit C zu testen. Damit testest Du implizit auch A und B. Nun gibts irgendwann Änderungen und der Test zu C schlägt fehl. Du weisst nun zwar, dass irgendwo ein Fehler ist, siehst aber nicht umbedingt auf den ersten Blick, ob der Fehler in A, B oder C liegt.

    Deswegen ist der Sinn von Unittests eben doch, die kleinsten Einheiten eines Programms zu testen. Das interessante daran ist, dass solche Unittests nicht nur helfen, Fehler schneller zu finden und zu beheben. Sie helfen sogar dabei, das Programm besser zu strukturieren! Es hilft dabei Seiteneffekte zwischen Programmteilen zu verhindern, weil sich diese eben isoliert nur schwer testen lassen.

    The goal of unit testing is to isolate each part of the program and show that the individual parts are correct.



  • volkard schrieb:

    DStefan schrieb:

    volkard schrieb:

    Sie ruft also so um die 10 Hilfsfunktionen auf, die die Daten zusammentragen und Zwischenergebnisse berechnen?

    Ungefähr, ja. Willst du darauf hinaus, dass ...

    Gar nicht. Ich wollte den vagen Eindruck geklärt haben, ob Du da einen 500-Zeiler gebaut hast.

    Man hätte durch

    Und hier kann es vorkommen, dass man zur Implentierung einer einzigen Methode einer Klasse Tage braucht.

    den Eindruck gewinnen können.

    Oh nein! 500-Zeiler kommen mir nicht ins Haus. Jedenfalls nicht in produktivem Code. Wenn man z.B. eine neue Lib ausprobiert schreibt man schonmal eher, wie einem der Schnabel gewachsen ist. Ob du's glaubst oder nicht, ich bin ein Code-Ästhet 😉

    Stefan.



  • byto schrieb:

    DStefan schrieb:

    Sorry, aber Unit-Tests sind zum Testen aller und nicht bloß der kleinsten Einheiten da. Wenn eine Einheit über andere Einheiten implementiert ist, dann ist der Code, der diese "umschließende" Einheit testet, ein Unit-Test. Warum auch nicht? Es kommt doch nicht auf die Größe an, damit etwas ein Unit-Test ist!

    Angenommen Du hast Einheit A, B und C. Einheit C benutzt Einheit B und Einheit B benutzt Einheit A. Du schreibst einen Unittest um Einheit C zu testen. Damit testest Du implizit auch A und B. Nun gibts irgendwann Änderungen und der Test zu C schlägt fehl. Du weisst nun zwar, dass irgendwo ein Fehler ist, siehst aber nicht umbedingt auf den ersten Blick, ob der Fehler in A, B oder C liegt.

    Deswegen ist der Sinn von Unittests eben doch, die kleinsten Einheiten eines Programms zu testen. Das interessante daran ist, dass solche Unittests nicht nur helfen, Fehler schneller zu finden und zu beheben. Sie helfen sogar dabei, das Programm besser zu strukturieren! Es hilft dabei Seiteneffekte zwischen Programmteilen zu verhindern, weil sich diese eben isoliert nur schwer testen lassen.

    Du hast Recht, wenn du sagst, dass Unit-Tests helfen, Programme besser zu strukturieren. Oder zumindest helfen können, denn natürlich kann man auch mit Unit-Tests den größten Mist verzapfen.

    Den ersten Teil deines Beitrags aber finde ich nicht plausibel. Natürlich müssen A und B getestet werden. Wenn aber C die beiden anderen Teile verwendet, muss es auch für C einen Unit-Test geben.

    Denn erstens kann C ja die Teile falsch verwenden, und es ist denkbar, dass A und B für sich genommen zwar fehlerfrei sind, im Zusammenspiel aber nicht funktionieren. Ein "Kooperationsfehler" käme also nur im Test von C an's Licht.

    Zweitens enthält C immer auch Code, und sei es nur (was nach meiner Erfahrung eher selten vorkommt) solchen Code, der die verwendeten Teile aktiviert (Variablen anlegen, Methoden von A und B aufrufen). Deshalb muss es auch dann für C einen Unit-Test geben, wenn C ausschließlich A und B verwendet.

    Ich spreche mich hier ausdrücklich nicht für implizite Tests aus! Die braucht man nicht und die soll man auch nicht schreiben. Aber ich sehe keinen Grund dafür, nur kleinste Einheiten per Unit-Tests zu testen.

    Übrigens: Was ist eigentlich eine "kleinste Einheit"? Nur eine, die selbst keine anderen Einheiten verwendet?

    Stefan.



  • _P schrieb:

    Die meisten waren private, so dass das nicht ging.

    #define private public
    

    Also wenn schon, dann so:

    class MyClass {
    
       // ...
    
    #ifdef WE_ARE_TESTING
       public:
    #else
       private:
    #endif
    
       // ...
    
    };
    

    Mit deinem Vorschlag könnte man alles einfach public machen, oder?

    Letztlich halte ich eine wie auch immer geartete "Veröffentlichung" privater Teile für keine gute Lösung. Aus verschiedenen Gründen.

    Stefan.



  • DStefan schrieb:

    Ich spreche mich hier ausdrücklich nicht für implizite Tests aus! Die braucht man nicht und die soll man auch nicht schreiben.

    Aber was sind implizite Tests?



  • volkard schrieb:

    DStefan schrieb:

    Ich spreche mich hier ausdrücklich nicht für implizite Tests aus! Die braucht man nicht und die soll man auch nicht schreiben.

    Aber was sind implizite Tests?

    Im Sinn von bytos Beschreibung: Man testet C, das B verwendet, das A verwendet, und glaubt, dass man mit dem Test von C auch A und B getestet hat. Implizit halt.

    Stefan.



  • DStefan schrieb:

    Den ersten Teil deines Beitrags aber finde ich nicht plausibel. Natürlich müssen A und B getestet werden. Wenn aber C die beiden anderen Teile verwendet, muss es auch für C einen Unit-Test geben.

    Schon richtig. Aber wenn Du für obigen Teil Unittests für A, B und C schreibst, dann wird A implizit zweimal mitgetestet (von B und C) und B einmal (durch C). Besser wäre es, solche Seiteneffekte im Vorhinein zu verhindern. Sind A, B und C frei von Seiteneffekten, können sie unabhängig voneinander getestet werden. Die Integration von A, B und C kann dann über Integrationstests (siehe Unterschied zu Unittests!!) getestet werden.

    Häufig kann man Seiteneffekte nicht gänzlich verhindern. Aber auch dafür gibts ne Möglichkeit, nämlich Mock-Objekte. Durch Mock-Objekte kann man das mehrfache implizite Testen immer gleicher Programmteile verhindern. Das ist vor allem in größeren Projekten mehr als sinnvoll, weil man dadurch die Ausführungsgeschwindigkeit der Tests minimieren kann.

    Denn erstens kann C ja die Teile falsch verwenden, und es ist denkbar, dass A und B für sich genommen zwar fehlerfrei sind, im Zusammenspiel aber nicht funktionieren. Ein "Kooperationsfehler" käme also nur im Test von C an's Licht.

    Zweitens enthält C immer auch Code, und sei es nur (was nach meiner Erfahrung eher selten vorkommt) solchen Code, der die verwendeten Teile aktiviert (Variablen anlegen, Methoden von A und B aufrufen). Deshalb muss es auch dann für C einen Unit-Test geben, wenn C ausschließlich A und B verwendet.

    Wie gesagt: es gibt einen Unterschied zwischen Unittests, die per Definition nur eine kleine Einheit testen (idR eine Methode/ Funktion) und Integrationstests, die das Zusammenspiel der einzelnen Module einer Anwendung testen. Integrationstests sind meistens aufwändiger und haben eine längere Ausführungsgeschwindigkeit, vor allem wenn periphäre Systeme wie Datenbanken mit ins Spiel kommen.

    Übrigens: Was ist eigentlich eine "kleinste Einheit"? Nur eine, die selbst keine anderen Einheiten verwendet?

    idR ist damit eine einfache Methode oder Funktion gemeint.

    Was Du alles schreibst, ist ja nicht falsch. Aber Du fasst das alles unter Unittests zusammen und das ist so nicht ganz korrekt.



  • byto schrieb:

    Aber wenn Du für obigen Teil Unittests für A, B und C schreibst, dann wird A implizit zweimal mitgetestet (von B und C) und B einmal (durch C). Besser wäre es, solche Seiteneffekte im Vorhinein zu verhindern. Sind A, B und C frei von Seiteneffekten, können sie unabhängig voneinander getestet werden. Die Integration von A, B und C kann dann über Integrationstests (siehe Unterschied zu Unittests!!) getestet werden.

    Häufig kann man Seiteneffekte nicht gänzlich verhindern. Aber auch dafür gibts ne Möglichkeit, nämlich Mock-Objekte. Durch Mock-Objekte kann man das mehrfache implizite Testen immer gleicher Programmteile verhindern. Das ist vor allem in größeren Projekten mehr als sinnvoll, weil man dadurch die Ausführungsgeschwindigkeit der Tests minimieren kann.

    Entschuldige byto, aber das klingt doch ziemlich verrückt!

    Nach deiner ursprünglichen Definition ist Einheit C implementiert über die Einheit B und diese wiederum über Einheit A. Du schreibst weiter, dass ein Test von Einheit C "implizit" die Einheiten A und B testet - und das hältst du für falsch. Nach deiner Definition dürfte man somit ausschließlich einen Unit-Test für Einheit A schreiben, denn dies ist eine "kleinste Einheit". Einheit B testet schließlich auch "implizit" - nämlich Einheit A.

    Du siehst selbst, wohin das führt und forderst deshalb, solche "Seiteneffekte" zu vermeiden. Wo das nicht geht, möchtest du "Mock Objekte" einsetzen.

    Zunächst einmal ist der Begriff "Seiteneffekt" in diesem Zusammenhang nicht sehr glücklich gewählt. Üblicherweise bezeichnet man als Seiteneffekt etwas ganz andres. Bleiben wir lieber bei "impliziter Test".

    Dann deine Schussfolgerung oder Behauptung, durch den Test von C würden A und B implizit mit getestet. Das stimmt meines Erachtens doch gar nicht. Oder wenigstens nicht zwangsläufig. Der Code von A und B wird ausgeführt, das ist richtig. Aber wenn man's richtig macht, wird dieser Code im Test von C nicht getestet! Er muss halt nur ausgeführt werden, damit C funktioniert.

    Auch was die Mock-Objekte angeht, stimme ich nicht mit dir überein. Mock-Objekte verwendet man, um in Unit-Tests langwierige oder während der Tests nicht verfügbare Operationen zu simulieren. Man kann sie auch verwenden, um Tests reproduzierbar zu machen. In allen Fällen aber bieten Mock-Objekte einen simulierten Ausschnitt der Umgebung der zu testenden Software. Das können Datenbank-Zugriffe sein (weil z.B. der Status der Daten nicht unter der Kontrolle des Testers liegt) oder Zugriffe auf externe Software (etwa Web-Services, die bestimmte Daten liefern). Ich habe auch schon Mock-Objekte geschrieben, die einen VoIP-Dialer simulieren - schließlich will man ja nicht, dass bei jedem Unit-Test irgendwelche Leute angerufen werden 😉

    Du schlägst vor, man solle Mock-Objekte auch verwenden, um "Seiteneffekte" beim Testen, also "implizites" Testen zu vermeiden. Aber dazu müsste man doch beim Test von C die Einheiten B und A entfernen, so dass MockB und MockA zum Einsatz kommen! C muss also auf den Unit-Test eigens vorbereitet werden! Und das alles nur, um zu verhindern, dass beim Test von C Code in A und B ausgeführt wird, der ja bereits getestet ist. Ich sage dir, um sowas zu tun bräuchte ich schon einen verdammt guten Grund. Und die von dir vorgestellte "reine Lehre" des Unit-Testings ist aus meiner Sicht überhaupt kein Grund.

    Nun gut. Angenommen, wir hätten jetzt C dazu gebracht, im Unit-Test statt B und A MockB und MockA zu verwenden. Wir schreiben unseren Test und er läuft. Aber was haben wir jetzt getestet? (Das ist ein Problem, das immer auftritt, wenn man Mock-Objekte verwendet.) Wir haben getestet, dass C zum Beispiel MockB korrekt verwendet. Aber dies geschieht im Sinne von MockB und nicht im Sinne von B selbst. Alle relevanten Änderungen in B müssen peinlichst in MockB nachvollzogen werden, sonst ist unser Unit-Test von C nicht mehr korrekt. Wir nehmen unter Umständen eine Heidenarbeit auf uns, testen C nicht in seiner "natürlichen", sondern nur in einer idealisierten Umgebung, und das alles nur, um nicht mit C implizit auch A und B zu testen?

    Mir geht das zu weit. Ich möchte, im Gegenteil, alle Unit-Tests möglichst so schreiben, dass sie den Code testen, der später auch prouktiv wird. Natürlich geht das nicht immer, wie ich oben schon sagte, aber ich finde, man sollte eine Menge dafür tun.

    Stefan.


Anmelden zum Antworten