Frage

Ich schreibe eine Komponente, die eine ZIP-Datei angegeben, muss:

  1. Entpacken Sie die Datei.
  2. Finden Sie eine bestimmte DLL unter den entpackten Dateien.
  3. Laden, die durch Reflexion dll und ein Verfahren auf sie berufen.

Ich mag diese Komponente zu Unit-Test.

Ich bin versucht, Code zu schreiben, der direkt mit dem Dateisystem behandelt:

void DoIt()
{
   Zip.Unzip(theZipFile, "C:\\foo\\Unzipped");
   System.IO.File myDll = File.Open("C:\\foo\\Unzipped\\SuperSecret.bar");
   myDll.InvokeSomeSpecialMethod();
}

Aber Leute oft sagen: „Sie Unit-Tests nicht schreiben, die auf dem Dateisystem verlassen, Datenbank, Netzwerk, etc.“

Wenn ich das in einem Unit-Test-freundliche Art und Weise schreiben waren, nehme ich es wie folgt aussehen:

void DoIt(IZipper zipper, IFileSystem fileSystem, IDllRunner runner)
{
   string path = zipper.Unzip(theZipFile);
   IFakeFile file = fileSystem.Open(path);
   runner.Run(file);
}

Yay! Jetzt ist es prüfbar; Ich kann auf die DoIt Methode in Test Doppel (Mocks) füttern. Aber zu welchen Kosten? Ich habe jetzt nur noch definieren 3 neue Schnittstellen mussten diese überprüfbar machen. Und was genau teste ich? Ich teste, dass meine DoIt Funktion mit seinen Abhängigkeiten richtig zusammenwirkt. Es testet nicht, dass die Zip-Datei korrekt entpackt wurde, etc.

Es ist nicht das Gefühl, ich Funktionalität mehr testen werde. Es fühlt sich an wie ich bin nur Klasse Interaktionen zu testen.

Meine Frage ist : Was ist der richtige Weg, um Unit-Test etwas, das auf dem Dateisystem abhängt

?

Bearbeiten Ich bin mit .NET, aber das Konzept könnte Java oder nativen Code anwenden zu.

War es hilfreich?

Lösung

Es gibt wirklich nichts falsch mit diesem, es ist nur eine Frage, ob man es sich um ein Gerät zu testen oder ein Integrationstest nennen. Sie müssen nur sicherstellen, dass, wenn Sie mit dem Dateisystem interagieren können, gibt es keine unbeabsichtigten Nebenwirkungen. Insbesondere stellen Sie sicher, dass Sie nach youself aufzuräumen - löschen Sie alle temporären Dateien, die Sie erstellt - und dass Sie nicht versehentlich eine vorhandene Datei überschreiben, die den gleichen Namen wie eine temporäre Datei haben, geschehen Sie verwendet haben. Verwenden Sie immer relative Pfade und nicht die absoluten Pfade.

Es wäre auch eine gute Idee, in ein temporäres Verzeichnis auf chdir() vor Ihrem Test und chdir() wieder danach ausgeführt wird.

Andere Tipps

  

Yay! Jetzt ist es prüfbar; Ich kann auf die DoIt Methode in Test Doppel (Mocks) füttern. Aber zu welchen Kosten? Ich habe jetzt nur noch definieren 3 neue Schnittstellen mussten diese überprüfbar machen. Und was genau teste ich? Ich teste, dass meine DoIt Funktion mit seinen Abhängigkeiten richtig zusammenwirkt. Es testet nicht, dass die Zip-Datei korrekt entpackt wurde, etc.

Sie haben den Nagel auf den Kopf getroffen. Was Sie testen wollen, dass die Logik Ihrer Methode ist, nicht unbedingt, ob eine echte Datei adressiert werden. Eröffnen Sie bitte benötigen (in diesem Unit-Test) zu prüfen, ob eine Datei korrekt entpackt wird, Ihre Methode nimmt das als selbstverständlich. Die Schnittstellen sind wertvoll für sich allein, weil sie Abstraktionen, die Sie gegen programmieren, anstatt implizit oder explizit auf eine konkrete Umsetzung angewiesen zu sein.

Ihre Frage setzt eine der schwierigsten Teile der Prüfung für Entwickler einfach in sie bekommen:

"Was zum Teufel teste ich?"

Ihr Beispiel ist nicht sehr interessant, weil es nur einige API klebt rufen zusammen also, wenn Sie für sie eine Einheit Test schreiben, so würde man nur behaupten, am Ende, dass Methoden genannt wurden. Tests wie diese fest Paar Ihre Implementierungsdetails auf den Prüfstand. Das ist schlecht, denn jetzt müssen Sie jedes Mal, wenn Sie die Details Implementierung Ihrer Methode ändern Sie den Test ändern, da die Implementierung ändern Details bricht Ihre Prüfung (en)!

schlechte Tests zu haben, ist eigentlich noch schlimmer als gar keine Tests haben.

In Ihrem Beispiel:

void DoIt(IZipper zipper, IFileSystem fileSystem, IDllRunner runner)
{
   string path = zipper.Unzip(theZipFile);
   IFakeFile file = fileSystem.Open(path);
   runner.Run(file);
}

Während Sie in Mocks passieren können, gibt es keine Logik in der Methode zu testen. Wenn Sie einen Komponententest für diese versuchen würden, es könnte wie folgt aussehen:

// Assuming that zipper, fileSystem, and runner are mocks
void testDoIt()
{
  // mock behavior of the mock objects
  when(zipper.Unzip(any(File.class)).thenReturn("some path");
  when(fileSystem.Open("some path")).thenReturn(mock(IFakeFile.class));

  // run the test
  someObject.DoIt(zipper, fileSystem, runner);

  // verify things were called
  verify(zipper).Unzip(any(File.class));
  verify(fileSystem).Open("some path"));
  verify(runner).Run(file);
}

Herzlichen Glückwunsch, Sie im Grunde Kopie kleistert die Details der Implementierung Ihrer DoIt() Methode in einem Test. Glücklich zu halten.

Wenn Sie Tests schreiben Sie die WAS und nicht die Wie zu testen. Siehe Black Box Testing für mehr.

Die WELCHE der Name des Verfahrens ist (oder sollte es zumindest sein). Das Wie sind all die kleinen Details der Implementierung, das in Ihrer Methode lebt. Gute Tests können Sie die auslagern HOW , ohne brechen die WAS .

Denken Sie an es auf diese Weise, fragen Sie sich:

"Wenn ich die Details der Implementierung dieser Methode (ohne erforderlichen öffentlichen Auftrag zu ändern) wird es meinen Test (s) brechen?"

Wenn die Antwort ja ist, Sie testen Sie die Wie und nicht WAS .

Ihre Frage zu beantworten, um Code zu testen mit Abhängigkeiten Dateisystem, sagen wir, Sie etwas ein wenig interessanter los mit einer Datei hatte und Sie wollten die Base64 codiert Inhalt eines byte[] in einer Datei speichern. Sie können Streams für diese verwenden, um zu testen, dass Ihr Code das Richtige tut, ohne zu überprüfen, die wie sie es tut. Ein Beispiel könnte so etwas wie diese (in Java):

interface StreamFactory {
    OutputStream outStream();
    InputStream inStream();
}

class Base64FileWriter {
    public void write(byte[] contents, StreamFactory streamFactory) {
        OutputStream outputStream = streamFactory.outStream();
        outputStream.write(Base64.encodeBase64(contents));
    }
}

@Test
public void save_shouldBase64EncodeContents() {
    OutputStream outputStream = new ByteArrayOutputStream();
    StreamFactory streamFactory = mock(StreamFactory.class);
    when(streamFactory.outStream()).thenReturn(outputStream);

    // Run the method under test
    Base64FileWriter fileWriter = new Base64FileWriter();
    fileWriter.write("Man".getBytes(), streamFactory);

    // Assert we saved the base64 encoded contents
    assertThat(outputStream.toString()).isEqualTo("TWFu");
}

Der Test verwendet einen ByteArrayOutputStream aber in der Anwendung (mit Dependency Injection) dem eigentlichen StreamFactory (vielleicht genannt FileStreamFactory) würde FileOutputStream von outputStream() zurückzukehren und zu einem File schreiben würde.

Was ist mit dem write Methode hier interessant ist, dass es den Inhalt schrieb aus Base64 codiert, so was das ist für uns getestet. Für Ihre DoIt() Methode, mehr würden diese entsprechend mit einer Integrationstest getestet.

Ich bin zurückhaltend, um meinen Code mit Typen und Konzepten verschmutzen die nur existieren, Unit-Tests zu erleichtern. Sicher, wenn es das Design saubere und besser als so großartig macht, aber ich denke, das ist oft nicht der Fall.

Meine Meinung dazu ist, dass die Komponententests so viel tun würden, wie sie können, die nicht zu 100% Deckung sein kann. In der Tat kann es nur 10% betragen. Der Punkt ist, sollten Sie Ihre Unit-Tests schnell sein und haben keine externen Abhängigkeiten. Sie könnten testen Fälle wie „diese Methode löst ein Argument, wenn Sie in null für diesen Parameter übergeben“.

Ich würde dann Integrationstests (auch automatisiert und wahrscheinlich mit dem gleichen Einheit Test-Framework) hinzufügen, die externen Abhängigkeiten und Test End-to-End-Szenarien wie diese.

Wenn Codeabdeckung gemessen werden, also ich beide Modul- und Integrationstests.

Es ist nichts falsch mit dem Dateisystem treffen, sollten Sie es nur einen Integrationstest eher als ein Gerät zu testen. Ich würde den hart codierten Pfad mit einem relativen Pfad wechseln und einen Testdata Unterordner erstellen, um die Reißverschluss für die Unit-Tests enthalten.

Wenn Sie Ihre Integrationstests zu lange dauern, dann laufen sie trennen, so dass sie nicht so oft wie Ihre schnellen Unit-Tests ausgeführt werden.

Ich bin damit einverstanden, manchmal denke ich, Interaktion basierte Tests zu viel Kupplung führen kann und endet oft nicht genug Wert bereitstellt. Sie wollen wirklich die Datei hier nicht nur um zu testen unzipping überprüfen Sie die richtigen Methoden aufrufen.

Eine Möglichkeit wäre es, die unzip Methode zu schreiben Inputstreams zu nehmen. Dann könnte das Gerät zu testen, eine solche Inputstream von einem Byte-Array konstruieren ByteArrayInputStream verwenden. Der Inhalt dieses Byte-Array eine Konstante in dem Unit-Test-Code sein könnte.

Dies scheint eher ein Integrationstest zu sein, wie Sie auf einem bestimmten Detail (das Dateisystem) werden abhängig, die in der Theorie ändern könnte.

Ich würde abstrakt der Code, der mit dem Betriebssystem in seiner eigenen Modul (Klasse, Montage, Glas, was auch immer) beschäftigt. In Ihrem Fall, dass Sie eine bestimmte DLL, wenn gefunden zu laden, so eine IDllLoader Schnittstelle und DllLoader Klasse. Haben Sie Ihre App die DLL aus dem DllLoader mit der Schnittstelle und Test erwerben, die .. Sie nicht verantwortlich für die unzip Code sind afterall nicht wahr?

Unter der Annahme, dass „Dateisystem-Interaktionen“ sind im Rahmen getestet selbst, schaffen sie ihre Methode mit Strömen zu arbeiten, und testen Sie diese. einen Filestream öffnen und an die Methode übergeben kann Ihre Tests weggelassen wird, wie FileStream.Open gut durch den Rahmen Schöpfer getestet wird.

Sie sollten nicht testen Klasse Interaktion und Funktion aufrufen. stattdessen sollten Sie Integrationstests in Betracht ziehen. Testen Sie das gewünschte Ergebnis und nicht der Dateiladevorgang.

Für Unit-Test würde ich vorschlagen, dass Sie die Testdatei in Ihrem Projekt (EAR-Datei oder gleichwertig) umfassen dann einen relativen Pfad in den Unit-Tests verwenden, das heißt „../ Testdaten / Testdatei“.

Solange Ihr Projekt korrekt exportiert / importiert als Ihr Gerät zu testen funktionieren soll.

Wie bereits gesagt wurde, ist die erste als Integrationstest in Ordnung. Die zweiten Tests nur, was die Funktion soll tatsächlich tun, das ist alles ein Unit-Test tun sollten.

Wie gezeigt, sieht das zweite Beispiel ein wenig sinnlos, aber es gibt Ihnen die Möglichkeit zu testen, wie die Funktion, um Fehler in einem der Schritte reagiert. Sie haben keinen Fehler in dem Beispiel der Überprüfung, aber im realen System können Sie haben, und die Abhängigkeit Injektion würden Sie auf Fehler alle Antworten testen. Dann haben die Kosten gewesen wert.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top