derzeit arbeite ich an einem Projekt, bei dem Dateien geschrieben werden. Das Problem, das sich mir stellt, betrifft das Testing der Klasse, die die Dateien erzeugt.
Ich arbeite mit TestNG, Mockito und PowerMock (um statische Methoden, genauer gesagt die now()-Methode der DateTime-Klasse aus der Joda-Time-Bibliothek zu mocken).
Zum prüfen, ob Verzeichnisse oder Dateien existieren, verwende ich die FileAssert-Klasse von TestNG.
Das Hauptproblem ist jetzt aber, dass umfangreiche Initialisierungs- bzw. Aufräumarbeiten anfallen, bei denen ich dafür sorgen muss, dass die Testverzeichnisse und Dateien nicht existieren.
Gibt es einen einfachen Weg, um die Aufräumarbeiten nach den Tests zu machen? Derzeit nutze ich die @AfterSuite-Annotation um die Verzeichnisse zu leeren. Das funktioniert unter Windows aufgrund von File-Locks allerdings nicht zuverlässig (eine Datei bleibt immer übrig).
Also “zum testen” bedeutet bei mir, dass die (Unit-)Tests plattformunabhängig in einem CI-Server ausgeführt werden. Unter Windows gibt es eben Probleme wegen der Locks und der Code um die Dateien zu löschen fühlt sich für meinen Geschmack ein wenig “verbose” an.
Edit: mit den Locks gäbe es natürlich kein Problem, wenn ich die Verzeichnisse vor dem Testdurchlauf leeren ließe, aber dann bleiben nach den Tests ebendiese Ordner bestehen, was ich unschön finde.
Ich würde für jeden Testlauf ein neues Verzeichnis (vielleicht unter /tmp) anlegen, in das alle Testverzeichnisse und Dateien geschrieben werden. Das ist dann auf jeden Fall leer.
Genau das mache ich auch - es ist im übrigen auch ein Test der Suite, ob das Objekt die Ordner korrekt anlegt - aber hinterher bleiben wie gesagt die “Leichen” zurück. Und das möchte ich vermeiden.
Weil Windows eine Datei gelockt hat und diese nicht löschen will? Dann wurde wurden vielleicht nicht alle Dateien geschlossen und du hast irgendwo einen Fehler in deinem Code. Das wäre natürlich schlimm. Ansonsten würde ich mir um Dateileichen im temp-Verzeichnis keine großen Gedanken machen. Die werden schon irgendwann abgeräumt, z.B. durch einen cron-Job. Keine Ahnung, wie Windows das handhabt.
Hmm. Ich nutze zum Schreiben in die Dateien einen FileWriter. Den schließe ich nach Verwendung auch mit close(). Mehr kann ich doch nicht tun, um den Filelock freizugeben, oder?
Das sollte reichen. Welcher Fehler kommt denn, wenn du versuchst, diese Datei zu löschen? Mach mal ein kleines Beispielprogramm, wo nur eine Datei mit dem FileWriter geschrieben und anschließend gelöscht wird.
Leider liefert die delete()-Methode für die eine Datei einfach nur false zurück. Ich werde mal ein Minimalbeispiel machen und auch mit nio.Files versuchen, die Fehlerursache herauszubekommen.
Ich schreib dann nochmal
Na toll. Das Problem lässt sich nicht zuverlässig reproduzieren, es scheint ein Timingproblem zu sein. Ich habe den Fehler gerade nur im Debugmodus reproduzieren können. Files.delete führt dann zu einer Exception: “Der Prozess kann nicht auf die Datei zugreifen, da sie von einem anderen Prozess verwendet wird.”.
Hat jemand eine Idee, wie ich das Timingproblem ohne auf unbestimmte Zeit warten zu müssen lösen kann?
Was das ganze halt unschön macht, ist, dass meine Testklasse dadurch immer weiter aufgebläht wird…
“A good setup does not rely on the teardown” ist eine Grundregel für Tests.
Du kanst in deiner setup Methode nicht davon ausgehen dass die vorherigen Teardowns sauber durchgelaufen sind, stattdessen sorge einfach dafür, dass alles was der Test braucht in der setup Methode bereitgestellt wird.
tfas Tipp mit den Temp Verzeichnissen ist da genau richtig, jedesmal einen neuen Order erzeugen wohin die Dateien geschrieben werden, dann ist es auch nciht wichtig (für den Test) ob die alten Dateien wirklich gelöscht wurden.
@cmrudolph
Das heißt, es ist Threading im Spiel? Das ist natürlich nicht einfach mit dem Testen. Soll denn die Datei vom produktivbem COde gelöscht werden? Dann würde ich versuchen, erstmal einen Test zu schreiben, der das prüft.
@Majora
ich habe das im ersten Moment für einen Scherz gehalten. Allerdings glaube ich, dass das vorerst ein Overkill wäre. Es ist wieder ein neues Tool, in das ich mich einarbeiten muss. Und noch läufts nicht soo gut mit dem TDD
@tfa
Ja, die Anwendung ist (leicht) nebenläufig. Die Kommunikation zwischen den Threads erfolgt hauptsächlich über BlockingQueues. Eigentlich ist es sogar nur eine recht einfache Producer-Consumer-Anwendung.
Die Dateien sollen vom produktiven Code nicht gelöscht werden, das möchte ich eigentlich nur für den Testcode machen. Aber vielleicht ist das alles viel zu viel Aufwand und ich arbeite doch lieber nur mit dem Temp-Verzeichnis und lasse das OS das Aufräumen übernehmen. Das hält zumindest den Testcode sauber.
@cmrudolph
Und echte Unit-Tests sind nicht möglich? Dann gäbe es weder Files noch Threads. Wenn nicht nach TDD (oder wenigstens fast) entwickelt wurde, kann das Mocken natürlich aufwendig werden.
Da habe ich mich vielleicht etwas zu schwammig ausgedrückt. Im Unittest gibt es tatsächlich keine Threads. Allerdings ist mir kein gangbarer Weg eingefallen, wie ich das echte Erzeugen von Dateien vermeiden kann. Denn injizieren von File-Objekten dürfte wohl nicht unbedingt guter Programmierstil sein…
Hmm. Dann wird der Test aber deutlich komplizierter als die zu testende Funktionalität => bad smell.
Mit Powermock funktioniert das durchaus, da kann ich die verwendeten Methoden der File-Klasse überschreiben. Das setzt dann aber wieder viel zu viel Wissen über den internen Code voraus. Wenn ich dann doch mal auf nio umstelle, dann funktionieren die Tests schon nicht mehr.
Derzeit präferiere ich die Lösung mit dem Tempverzeichnis auch.
Ist die Schwelle zu einem Integrationstest denn schon überschritten, weil ich FS-Interaktionen habe? Denn eigentlich soll das ein Unittest sein.
Die Interaktion mit dem Dateisystem lässt sich meines Erachtens auch nicht vermeiden, weil das spezielle an dieser Klasse ist, dass die ankommenden Daten eben in Dateien geschrieben werden.