Unit Test ... wie aufwändig darf es sein

Hallo Community, kurze Meinungsumfrage (obwohl ich glaube, dass es ausarten kann :D):

ab welchem Aufwand lohnen sich Unittests nicht mehr? Klare Entscheidung muss natürlich sein, für was man die Tests schreibt. Ist es eine Lib, die in zig Projekten eingebunden sein kann, die man ggfs. nicht kennt, dann stellt sich diese Frage gar nicht.

Ich möchte eher auf BLL Projekte hinaus. Solche Projekte, wo ein Knopf in der GUI gedrückt wird, eine BLL Methode triggert und über die DAL was in der DB erledigt. Hier kann es schon auch vorkommen, dass so ein Unittest die 5fache Zeit in Anspruch nimmt, gegenüber der eigentlichen BLL Methode, weil man so viele Daten für den Test in der gemockten DAL erstellen muss. Ist es das wirklich wert? Wo zieht man die Grenze? Immerhin wird diese eine Methode an genau einer Stelle verwendet. Die Qualität der gesamten Anwendung sinkt ja nicht so stark bei einem Fehler darin, als wenn eine Methode 1000 mal verwendet wird und dann Probleme macht.

Dazu kommt noch, dass man ja jedes mal bei DB Änderungen auch den Wartungsaufwand in den Unittests zusätzlich hat.

Wie sieht bei euch die Praxis aus … stupide alles Testen, ohne den Benefit im Hinterkopf zu haben, oder zieht man irgendwo Grenzen? Wenn ja, wo?

Generell sollte man alles Testen, wenn der Unit-Test zu aufwändig ist, ist die Frage, ob die Funktion nicht aufgeteilt werden kann und die einzelnen Teilfunktionen dann kleiner, handlicher und testbarer sind.

Den größten Aufwand sehe ich immer DB / DAL seitig. Zum einen muss ich Daten fürs DAL Mocking bereitstellen, und bei DB Änderungen muss ich durch den ganzen Wust noch mal durch. Klar, große Methoden kann man kleiner machen und diese testen. Aber der eigentliche Aufruf der Methode und die folgenden Asserts, sind ja oft nicht so kompliziert und oft ähnlich.

  1. Db testing ist auch kein unit testing.
  2. Test driven arbeiten hat den Vorteil dass man sich solche Fragen gar nicht stellen muss
  3. Braucht der unit test mehr Aufwand (code und/oder zeit) als SUT, so ist SUT zu untersuchen und zu optimieren/korrigieren

Was verstehst du darunter? Es wird ja keine DB getestet, sondern die Logik zur Abfrage der DB auf BLL Ebene (z.B. User hat „Foo“ eingegeben, also muss BLL sagen: „Foo“ gibt es nicht, wähle was anderes).
Was fraglich ist, ob ich testen muss, wenn ich nach „Foo“ filtere, ob dann nur Datensätze zurück kommen, die „Foo“ enthalten. Das ist ja eher Logik, die auf DAL / DB Ebene läuft und dann eher von UI Tests getestet werden sollten.?!?.

Naja was er eventuell meint ist die anfragen an die DB, die aus dem Code heraus erfolgen zu testen (die DAUs) was bei uns im Projekt auch gemacht wird. Bei den DAUs bauen wir im Projekt eine in Memory DB auf mit Testdaten und testen dann auch ob diese funktionieren. (Und ja das ist schon mehr Overhead als üblich für Unittests) Für alle anderen Klassen wird jedoch keine DB für den Unit Test benötigt. Das ist dann erst für die Integrationstest wieder nötig.

Vielleicht ist mir noch nicht so recht klar, was ich eigentlich in solch einer BLL genau testen sollte :sick:

*** Edit ***

DAU = Dümmster anzunehmender User?

Wir nutzen für unsere Unit- bzw. Integrationstests eine embebbed DB. Für die einzelnen Tabellen gibt es Initializer, die die Daten anlegen. Je nachdem, welche Daten für die jeweiligen Testfälle benötigt werden, zieht man die entsprechenden Initializer an. Man hat den Aufwand einmal und kann aber die Initlializer an verschiedenen Stellen benutzen.
Vorteil ist auch, dass man unäbhingig von der tatsächlich genutzten DB ist, vielleicht aber auch nur wenn man mit einem ORM arbeitet.

Ich glaube @bygones meint das ein Unit Test nur die separate Unit testen soll und nicht noch andere Units, so ist die eine Unit die DB und die andere Unit das DAU

*** Edit ***

Data Access Unit

Wie ist da die Erfahrung deinerseits mit Änderungen an den Initializern? Da kann es durchaus passieren, dass hier ein Sack voll Unittests auf die Schnauze fliegen, weil du eine Zeile hinzufügen musstest.

Das würde ich so nicht unterschreiben. Wenn diese eine Methode einen Fehler hat, werden falsche Daten erzeugt und gespeichert, auf diesen Daten basieren dann wieder andere Methoden und erzeugen neue Daten.
Daten wieder gerade zu ziehen, kann wesentlich aufwendiger sein, als eine vernünftige Testabdeckung zu haben.

*** Edit ***

Sollte ja eigentlich nicht passieren und wenn doch, ist es gut, da ich so sehe, wo alles meine kleine Änderung Einfluss hat, also ist es doch erwünscht das ich das mitbekomme und nicht an A eine kleine Änderung mache und nie Feststelle, das diese kleine Änderung auch auf B,C,D,… wirkt. Somit ist das doch gewünschtes Verhalten

Danke … vielleicht liegt hier ein Verständnis mit der richtigen Grenze vor. D.h., ich prüfe, ob eine IF Abfrage in der Unit funktioniert, aber nicht, ob der SELECT richtig gebaut wird um die Daten dann zu erhalten.
Mal ein Beipiel: BLL liest mir Aufträge aus und der User hat die Möglichkeit die Auftragsnummer zu filtern. Gibt er keine Nummer an, so muss ein Fehler gemeldet werden. Ich prüfe also lediglich, ob der Fehler gemeldet wird, wenn die Nummer fehlt, und ob kein Fehler kommt, wenn er was angibt. Welche Daten kommen und ob die korrekt sind, ist mir egal?

Naja das wenn ich eine Zeile Ändere und es bei vielen Unit Tests zu Fehlern führt,
ist das doch entweder ein gutes Zeichen, diese Änderung soll dort über all Einfluss haben, alles OK
ich stelle fest das die Änderung auch Sachen anpasst / ändert die eigentlich davon unabhängig sein sollen (Seitefeckt einer Änderung) und man kann froh sein ihn entdeckt zu haben

Unit test zu schreiben ohne zu wissen, was man eigentlich testen will, muss ist immer dem Untergang geweiht. Entweder man lässt es ganz oder man erschafft sich ein Monstrum an Testgewirr und verliert den Begriff “unit” aus den Augen.

Ebenso sollte man gut aufpassen nicht andere Verantwortlichkeiten zu testen. Zu testen dass ich wenn ich Foo an die Datenbank schmeisse, dass alle Datensätze mit Foo rauskommen ist keine Verantwortlichkeit deiner Unit, ergo sollte es nicht per unit test deiner Klasse beschrieben werden. Solche Funktionalität werden eher im größeren Szenarien via Integrations/Systemtests betrachtet

Wobei auch das erstellen der DB Abfrage kann auch Logik enthalten. Z.B. kann ein Parameter entscheiden, ob die Abfrage so, oder anders ausgeführt wird. Wird dann die Abfrage in einer separaten Methode durchgeführt und explizit gemockt, damit man gar nicht erst in der DAL landet?

du verwendest B-Bingo-Abkürzungen wie SUT, aber weißt dann nicht was BLL ist? das ist ja spaßig :wink:

visual studio - Why should I do UnitTest for my SQL, DAL and BLL if my App will only call BLL metods? - Stack Overflow
Isolating your BLL from your DAL and unit testing it | Gaui.is
ah, BLL = Business Logic Layer
(edit: war nur Suche nach den Abkürzungen, Inhalt nicht für diesen Thread hier ausgesucht, aber klingt ja alles gar nicht so unpassend, jetzt wo ich auf die Titel schaue…)

[QUOTE=freezly]Danke … vielleicht liegt hier ein Verständnis mit der richtigen Grenze vor. D.h., ich prüfe, ob eine IF Abfrage in der Unit funktioniert, aber nicht, ob der SELECT richtig gebaut wird um die Daten dann zu erhalten.
Mal ein Beipiel: BLL liest mir Aufträge aus und der User hat die Möglichkeit die Auftragsnummer zu filtern. Gibt er keine Nummer an, so muss ein Fehler gemeldet werden. Ich prüfe also lediglich, ob der Fehler gemeldet wird, wenn die Nummer fehlt, und ob kein Fehler kommt, wenn er was angibt. Welche Daten kommen und ob die korrekt sind, ist mir egal?[/QUOTE]
wenn die Klasse sich nicht um die Korrektheit der Daten kuemmert/kuemmern soll, so brauchst du das nicht zu testen.

Generell kann man keine Aussage machen was zu testen ist, was nicht. Dies hängt vom Kontext und von den Requirements ab. Wenn es die Verantwortlichkeit der Klasse ist, sicher zustellen dass das SELECT richtig gebaut wird, muss dies getestet werden. Ist eine precondition dass das SELECT richtig ist, so nicht. Der Autor der Unit sollte das wissen bzw das Team etc. Ins Blaue generelle Regeln aufstellen die sich template mäßig ausführen lassen - das gibts nicht

jo, wollte auch mit was um mich schmeissen :smiley:

Wenn sich an der Datenstruktur etwas ändert, muss man eben nicht nur die Initializer anpassen sondern eben auch alles Weitere. Das ist aber meist auch nicht mehr Aufwand, als man eh an Code-Anpassungen vornehmen muss.

Wie Unregistered auch schon sagt, ist doch super wenn es überall knallt nach der Änderungen. Genau dafür schreibt man doch die Tests. Besser als wenn Seiteneffekte erst auftreten wenn der Kunde die Software nutzt, weil nach der Änderung auf der Oberfläche nur getestet wurde, wo man wusste das es Probleme geben könnte.

Wenn ich dich richtig verstehe hast du eine Klasse die mit einem Parameter aufgerufen wird und diese soll dann (wenn dort ein Wert gesetzt ist) einen Aufruf machen und wenn nicht einen Fehler Werfen.
Nun würde ich für den Test die Methode die Aufgerufen wird Mocken (z.B. durch Frameworks wie http://mockito.org/) und da kann man dann schauen ob der Aufruf mit dem richtigen Parameter gemacht wurde (java - How to use ArgumentCaptor for stubbing? - Stack Overflow).
Somit einmal mit dem Gültigen wert Foo aufrufen und in dem Mock schauen ob er aufgerufen wurde (mit dem passenden SQL Statement) und einen gültige Antwort zurück geben und dieses dann auch nochmal checken ob das auch deine Methode dann passend weiter zurück reicht und einmal ohne Eingabe aufrufen, schauen ob die Exception fliegt und dein Mock auch nicht aufgerufen wurde.

Wie bereits von anderen erwaehnt, ein isolierter Unit Test testen nur eine Methode einer Klasse, sonst nix.
Die Abhaenigigkeiten, “Collaborateure” werden weggemockt.

Je mehr Abhaengigkeiten eine Klasse hat, umso schwieriger ist sie zu testen durch einen Unittest, desto aufwaendiger der Test.

“schlechtes Design” ist Code der schwer zu testen ist, “gutes Design” ist Code der leicht zu testen ist.
Bei gut designtem Code sollte man nicht mehr als 15-20% der Zeit zum schreiben der UTs brauchen wie es dauert den Prodcode zu schreiben.

Isolierte Uni Tests sind Whitebox Tests.
Unit Tests sollten nur brechen, wenn:
Sich die Implementierung des SUTs (Subject under Test, die zu testende Klasse) geaendert hat, oder die Schnittstellen der Abhaengigkeiten, weil diese ja gemockt werden.
Unittests laufen sehr sehr schnell, tausende innerhalb von ein paar Sekunden.

Was macht Code schwer testbar durch Unittests?
Hohe Komplexitaet, sieht man an vielen Abhaengigkeiten, langen Methoden, geschachtelten if/else/schleifen etc.

Jeder Test der einen Spring Kontext nutzt, aufs Netzwerk/Datenbanken/Dateisystem zugreift ist ein Integrations Test, die sind sehr unterschiedlich zu Unit Tests.
Integrationtests sind im idealfall Blackbox Tests.
Integrationstests brechen aus vielen Gruenden, zB. langsame DBs, Deadlocks in DBs oder Threads, falschen Datenstaenden in DBs, vollen Dateisystemen oder fehlenden Berechitgungen auf diesen, bis hin zu langsamen/ausfallenden Netzwerken.
Integrationstests sind sehr aufwaendig in der Pflege aus diesen Gruenden.
Integrationstests laufen oft langsam, je mehr umso langsamer, ein paar hundert/tausende koennen schon Minuten/Stunden laufen wenn sie nicht optimiert sind.