Unit Testing File I / O

Czytając istniejące wątki związane z testowaniem jednostkowym tutaj na Stack Overflow, nie mogłem znaleźć jednego z jasną odpowiedzią na temat operacji wejścia/wyjścia plików testowych jednostkowych. Dopiero niedawno zacząłem szukać testów jednostkowych, wcześniej zdając sobie sprawę z zalet, ale mając trudności z przyzwyczajeniem się do pisania testów. Założyłem swój projekt, aby używać NUnit i Rhino Mocks i chociaż rozumiem stojącą za nimi koncepcję, mam mały problem ze zrozumieniem, jak używać Makiety Obiektów.

W szczególności mam dwa pytania, na które chciałbym odpowiedzieć. Po pierwsze, jaki jest właściwy sposób na operacje wejścia/wyjścia plików testowych? Po drugie, w moich próbach uczenia się o testach jednostkowych natknąłem się na dependency injection. Po skonfigurowaniu i uruchomieniu Ninject zastanawiałem się, czy powinienem używać DI W moich testach jednostkowych, czy po prostu tworzyć instancje obiektów bezpośrednio.

Author: Shaun Hamman, 2009-10-07

5 answers

Zobacz Tutorial do TDD za pomocą Rhino Mocks i SystemWrapper .

SystemWrapper owija wiele z System.IO klasy w tym File, FileInfo, Directory, DirectoryInfo, ... . Możesz zobaczyć pełną listę .

W tym tutorialu pokazuję, jak zrobić testowanie z MbUnit, ale jest to dokładnie to samo dla NUnit.

Twój test będzie wyglądał mniej więcej tak:

[Test]
public void When_try_to_create_directory_that_already_exists_return_false()
{
    var directoryInfoStub = MockRepository.GenerateStub<IDirectoryInfoWrap>();
    directoryInfoStub.Stub(x => x.Exists).Return(true);
    Assert.AreEqual(false, new DirectoryInfoSample().TryToCreateDirectory(directoryInfoStub));

    directoryInfoStub.AssertWasNotCalled(x => x.Create());
}
 30
Author: Vadim,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-11-07 16:17:19

Nie ma koniecznie jednej rzeczy do zrobienia podczas testowania systemu plików. Prawdę mówiąc, jest kilka rzeczy, które możesz zrobić, w zależności od okoliczności.

Pytanie, które musisz zadać, brzmi: Co testuję?

  • Że system plików działa? prawdopodobnie nie musisz testować tego, chyba że używasz systemu operacyjnego, którego nie znasz. Więc jeśli po prostu wydajesz polecenie zapisywania plików, na przykład, to strata czasu, aby napisać test, aby upewnić się, że naprawdę zapisać.

  • Że pliki zostaną zapisane we właściwym miejscu?Skąd wiesz, jakie jest właściwe miejsce? Prawdopodobnie masz kod, który łączy ścieżkę z nazwą pliku. Jest to kod, który możesz łatwo przetestować: twoje wejście to dwa ciągi znaków, a twoje wyjście powinno być ciągiem znaków, który jest poprawną lokalizacją pliku zbudowaną przy użyciu tych dwóch ciągów.

  • Że otrzymasz odpowiedni zestaw plików z katalog? prawdopodobnie będziesz musiał napisać test dla swojej klasy Getter, który naprawdę testuje system plików. Ale powinieneś użyć katalogu testowego z plikami w nim, które się nie zmienią. Powinieneś również umieścić ten test w projekcie integration test, ponieważ nie jest to prawdziwy test jednostkowy, ponieważ zależy on od systemu plików.

  • Ale muszę coś zrobić z aktami, które dostaję. dla tego testu, powinieneś użyć fake dla swojej klasy Getter. Twoja podróbka powinna zwrócić zakodowaną listę plików. Jeśli użyjesz gettera real i procesora real , nie będziesz wiedział, który z nich spowoduje niepowodzenie testu. Więc twoja klasa procesora plików, podczas testów, powinna używać fałszywej klasy getter. Twoja klasa procesora plików powinna przyjmować interfejs getter . W prawdziwym kodzie, przekażesz w prawdziwym pliku-getter. W kodzie testowym zdasz fałszywy plik-getter, który zwraca znaną, statyczną lista.

Podstawowe zasady to:

  • używaj fałszywego systemu plików, ukrytego za interfejsem, gdy nie testujesz samego systemu plików.
  • Jeśli chcesz przetestować rzeczywiste operacje na plikach, to
    • Oznacz test jako test całkowy, a nie test jednostkowy.
    • mają wyznaczony katalog testowy, zestaw plików itp. to zawsze będzie w niezmienionym stanie, więc testy integracyjne zorientowane na pliki mogą przejść pomyślnie konsekwentnie.
 36
Author: Ryan Lundy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-11-07 03:41:19

Q1:

Masz trzy opcje.

Opcja 1: Żyj z tym.

(Brak przykładu: P)

Opcja 2: w razie potrzeby utworzyć lekką abstrakcję.

Zamiast wykonywania pliku I / o (File.ReadAllBytes lub cokolwiek) w testowanej metodzie można ją zmienić tak,aby IO było wykonywane na zewnątrz i zamiast tego przekazywany jest strumień.

public class MyClassThatOpensFiles
{
    public bool IsDataValid(string filename)
    {
        var filebytes = File.ReadAllBytes(filename);
        DoSomethingWithFile(fileBytes);
    }
}

Stałoby się

// File IO is done outside prior to this call, so in the level 
// above the caller would open a file and pass in the stream
public class MyClassThatNoLongerOpensFiles
{
    public bool IsDataValid(Stream stream) // or byte[]
    {
        DoSomethingWithStreamInstead(stream); // can be a memorystream in tests
    }
}

Takie podejście to kompromis. Po pierwsze, tak, to jest bardziej testowalny. Jednak wymienia testowalność na niewielki dodatek do złożoności. Może to wpłynąć na łatwość konserwacji i ilość kodu, który musisz napisać, a także możesz po prostu przenieść problem testowy o jeden poziom.

Jednak z mojego doświadczenia wynika, że jest to ładne, zrównoważone podejście, ponieważ można uogólnić i przetestować ważną logikę bez angażowania się w pełni zawinięty system plików. Czyli można uogólnić elementy, na których naprawdę Ci zależy, pozostawiając resztę jako jest.

Opcja 3: zawiń cały system plików

Idąc o krok dalej, wyśmiewanie się z systemu plików może być dobrym podejściem; zależy to od tego, z jakim nadęciem chcesz żyć.

Przechodziłem już tą trasę; miałem zawiniętą implementację systemu plików, ale w końcu ją usunąłem. Były subtelne różnice w API, musiałem go wstrzykiwać wszędzie i ostatecznie był to dodatkowy ból dla małego zysku, ponieważ wiele klas korzystających z niego nie było ogromnie ważne dla mnie. Gdybym korzystał z kontenera IoC lub pisał coś, co było krytyczne, a testy musiały być szybkie, mógłbym z tym zostać. Podobnie jak w przypadku wszystkich tych opcji, przebieg może się różnić.

Co do twojego pytania:

Wstrzykiwać test dwukrotnie ręcznie. Jeśli musisz wykonać wiele powtarzalnych prac, po prostu użyj metod setup/factory w swoich testach. Używanie kontenera IoC do testowania byłoby przesadą! Może nie jestem rozumiem twoje drugie pytanie.

 9
Author: Mark Simpson,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-10-06 21:44:46

Obecnie używam obiektu IFileSystem poprzez iniekcję zależności. Dla kodu produkcyjnego, Klasa wrapper implementuje interfejs, owijając określone funkcje IO, których potrzebuję. Podczas testowania, mogę utworzyć implementację null lub stub i dostarczyć ją do klasy testowanej. Testowana klasa nie jest mądrzejsza.

 1
Author: Grant Palin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-10-06 23:12:21

Od 2012 roku możesz to zrobić używając Microsoft Fakes bez konieczności zmiany kodu, na przykład dlatego, że był już zamrożony.

Najpierw Wygeneruj fałszywy zestaw dla systemu.dll-lub jakikolwiek inny pakiet, a następnie mock oczekiwanych zwrotów jak w:

using Microsoft.QualityTools.Testing.Fakes;
...
using (ShimsContext.Create())
{
     System.IO.Fakes.ShimFile.ExistsString = (p) => true;
     System.IO.Fakes.ShimFile.ReadAllTextString = (p) => "your file content";

      //Your methods to test
}
 1
Author: Bahadır İsmail Aydın,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-01-25 02:45:30