Testgetriebene Entwicklung: Eine gute / akzeptierte Methode zum Testen von Dateisystemoperationen?

14

Momentan arbeite ich an einem Projekt, das (unter anderem) eine Tabelle basierend auf dem Inhalt eines Dateisystems erstellt und wiederum einige Metadatenänderungen an den gefundenen Dingen vornimmt. Die Frage ist: Wie sollen Tests dazu geschrieben oder eingerichtet werden? Gibt es eine einfache Möglichkeit, dies zu verhöhnen? Oder sollte ich eine "Sandbox" einrichten?

Kirbinator
quelle

Antworten:

13

Wie immer bei TDD mit externen Ressourcen: Sie erstellen eine oder mehrere Schnittstellen zu Ihren Dateisystemoperationen und "verspotten sie". Sie möchten Ihren "Tabellengenerator" und Ihren Metadaten-Änderungscode testen, nicht die Dateisystemoperationen selbst (höchstwahrscheinlich verwenden Sie vorgefertigte Bibliotheksimplementierungen für den Zugriff auf das Dateisystem).

Doc Brown
quelle
TDD empfiehlt nicht, die Implementierung des getesteten Geräts zu verspotten. Siehe (e, g) solnic.eu/2014/05/22/mocking-and-ruby.html
soru
1
@soru: Das wird in dieser Antwort nicht empfohlen. Es wird empfohlen, zuerst Schnittstellen zu erstellen und dann die Schnittstelle zu verspotten . Sie testen also die Geschäftslogik, aber nicht die Dateisystemschnittstelle.
sleske
5
Es hört sich jedoch so an, als ob die Geschäftslogik in Form von Dateien und Verzeichnissen definiert ist. So die Sachen , die das Dateisystem - API - Aufrufe ist die Sachen , die Bedürfnisse zu testen. Das Testen der zugehörigen Nicht-Datei-Geschäftslogik erfordert keine Verspottung. teste es einfach.
Soru
@soru richtig, Sie erstellen also eine dünne Schicht um Dateien und Ordner mit einer Schnittstelle, die so definiert ist, dass alle domänenspezifischen Vorgänge auf der Clientseite stattfinden, und die Implementierungsseite ist trivial genug, um sicher zu sein, dass sie ohne Komponententests funktioniert (Integrationstests werden es tun) noch erforderlich sein). Ähnlich der Idee eines bescheidenen Dialogs in UI-Code oder der Verwendung eines Schein-Repositorys in Code, der auf dauerhafte Objekte angewendet wird.
Jules
2
Wir verzichten also effektiv darauf, die eigentliche Klasse zu testen, die mit dem Dateisystem, der Datenbank usw. interagiert. Stattdessen erstellen wir eine andere Implementierung mit derselben Schnittstelle wie ein Mock / Stub, aber die eigentliche Klasse verlassen wir ohne jegliche Art von Komponententest. weil wir glauben, dass wir es nicht als Unit testen können und stattdessen Integrationstests durchführen sollten, um es zu testen. Ist das richtig?
Andrew Savinykh
11

Was ist falsch daran, ein "Test" -Dateisystem zu haben?

Erstellen Sie eine Vorlagenordner- / Verzeichnisstruktur, die über genügend Inhalt verfügt, um Ihre Vorgänge zu testen.

Kopieren Sie diese anfängliche Struktur während der Einrichtung Ihres Einheitentests (es wird empfohlen, die Vorlage zu komprimieren und in Ihren Testbereich zu entpacken). Führen Sie Ihre Tests durch. Löschen Sie das Ganze beim Abreißen.

Das Problem beim Verspotten ist zum einen, dass Dateisysteme, Betriebssysteme und Datenbanken, die zu Ihrem Projekt gehören, nicht wirklich als externe Ressourcen eingestuft werden. Zum anderen ist das Verspotten von Systemaufrufen auf niedriger Ebene sowohl zeitaufwendig als auch fehleranfällig.

James Anderson
quelle
5
Das Verspotten von Dateisystemoperationen führt zu wesentlich (!) Schnelleren Tests als die Verwendung eines echten Dateisystems. Wenn dies fraglich ist, hängt es von der Implementierung ab. Dennoch denke ich, dass Ihr Vorschlag für die Erstellung automatisierter Integrationstests in Ordnung ist (was ich normalerweise zuerst tun würde, wenn ich kein TDD mache). Das OP hat jedoch speziell nach TDD gefragt, und TDD-Komponententests müssen schnell sein.
Doc Brown
1
Ich denke, Dateisysteme sollten, wenn sie verspottet sind, idealerweise eine ganze API haben, die von einer Gruppe geschrieben und gewartet wird, weil Sie das Rad neu erfinden, wenn Sie mit dem Dateisystem etwas Bedeutendes tun.
Frank Hileman
2
@ Doc Brown - Ich gehe davon aus, dass er Dir-, Lösch- und Umbenennungsoperationen ausführen möchte, die alle Randfälle enthalten, deren Verspottung schmerzhaft wäre. Auch auf moderner Hardware ist das Entpacken einiger kleiner Dateien in ein Verzeichnis nur wenig langsamer als das Laden einer Java-Klasse - es ist schließlich alles IO.
James Anderson
Je mehr ich über das Testdateisystem nachdenke, desto mehr gefällt es mir.
Frank Hileman
3

Dies ist die Art von Dingen, die Sie auf jeden Fall für den Integrationstest benötigen, da reale Dateisysteme alle möglichen seltsamen Verhaltensweisen aufweisen (wie die Art und Weise, in der Windows das Löschen einer Datei nicht zulässt, wenn ein Prozess, einschließlich des Löschers, diese geöffnet hat).

Der TDD-Ansatz besteht also darin , zuerst den Integrationstest zu schreiben (TDD kennt streng genommen keine unterschiedlichen Konzepte für "Unit-Test" und "Integrationstest"; es handelt sich lediglich um Tests). Sehr wahrscheinlich wird das ausreichen; so Job stoppen, getan, nach Hause gehen .

Wenn nicht, wird es einige interne Komplexität geben, die nicht einfach durch Anordnen von Dateien angemessen getestet werden kann. In diesem Fall nehmen Sie einfach diese Komplexität heraus, ordnen sie einer Klasse zu und schreiben Unit-Tests für diese Klasse . Sehr wahrscheinlich werden Sie feststellen, dass diese gemeinsame Klasse auch in den Fällen Datenbank, XML-Datei usw. verwendet werden kann.

In keinem Fall würden Sie den grundlegenden Kern des Codes, den Sie schreiben, herausnehmen und ihn "verspotten", um Tests zu schreiben, die bestehen, ob das zu testende Gerät falsch ist oder nicht.

Soru
quelle
Diese Antwort relativiert sie für mich - 'unit test' and 'integration test'; they are just tests.ich denke realistisch, dies wird die beste Lösung für meinen Fall sein - ich muss wirklich die Dateisystembibliotheken testen, die ich für Randfälle verwende, und wie die Anwendung darauf reagieren sollte jene. Wenn ich auf eine andere Dateisystembibliothek umsteige, möchte ich nicht einige Mocks / Testcodes neu schreiben müssen, um mit der neuen Bibliothek zu arbeiten, aber eine Testordnerstruktur und Integrationstests würden das viel einfacher machen.
TheDorf
2

Ich verstehe Ihre Frage als "eine gute / akzeptierte Möglichkeit, eine Klasse zu testen, die von Dateisystemoperationen abhängt". Ich gehe nicht davon aus, dass Sie das Dateisystem Ihres Betriebssystems testen möchten.

Um den Aufwand für 'Schnittstellen zu Ihren Dateisystemoperationen und deren "Verspottung"' so gering wie möglich zu halten, empfiehlt es sich, Java - Binärdatenströme oder Textleser (oder ein gleichwertiges Element in c # oder c #) zu verwenden die von Ihnen verwendete Programmiersprache), anstatt Dateien mit Dateinamen direkt in Ihrer von tdd entwickelten Klasse zu verwenden.

Beispiel:

Mit Java habe ich eine Klasse CsvReader implementiert

public class CsvReader {
    private Reader reader;

    public CsvReader(Reader reader) {
        this.reader = reader;
    }
}

Zum Testen habe ich solche Speicherdaten verwendet

String contentOfCsv = "TestColumn1;TestColumn2\n"+
    "value1;value2\n";

CsvReader sut = new CsvReader(java.io.StringReader(contentOfCsv));

oder Testdaten in die Ressourcen einbetten

CsvReader sut = new CsvReader(getClass().getResourceAsStream("/data.csv"));

In der Produktion verwende ich das Dateisystem

CsvReader sut = new CsvReader(new BufferedReader( new FileReader( "/import/Prices.csv" ) ));

Auf diese Weise ist mein CsvReader nicht vom Dateisystem abhängig, sondern von einer Abstraktion "Reader", in der es eine Implementierung für das Dateisystem gibt.

k3b
quelle
2
Das einzige Problem hierbei ist, dass das OP nicht von Dateioperationen, sondern von Dateisystemoperationen und Metadatenoperationen sprach - ich denke, er meinte so etwas wie das Auflisten aller Dateien in einem Verzeichnis, das Aktualisieren einiger EXIF-Informationen in allen Bilddateien usw.
Doc Brown
Das ist richtig.
Kirbinator
1
Sie können IDirectoryLister mit der Methode String [] List (String-Verzeichnis) erstellen. dann kann FakeDirectoryLister diese Methode implementieren, indem nur der neue String [] {".", "..", "foo.bat", "bar.exe"} zurückgegeben wird.
Anders Lindén
0

Erstellen Sie einen Wrapper für die Dateisystemvorgänge. Übergeben Sie in den Tests einen Mock, der dieselbe Schnittstelle wie der Wrapper implementiert. In der Produktion übergeben Sie den Wrapper.

jhewlett
quelle