Jak wyśmiewać system plików w C # do testów jednostkowych?


149

Czy są jakieś biblioteki lub metody do makiety systemu plików w C # do pisania testów jednostkowych? W moim obecnym przypadku mam metody, które sprawdzają, czy dany plik istnieje i odczytują datę utworzenia. W przyszłości mogę potrzebować czegoś więcej.


1
To wygląda na duplikat kilku innych, w tym: stackoverflow.com/questions/664277/… .
John Saunders,


2
@Mitch: W większości przypadków wystarczy umieścić dane w systemie plików i pozwolić, aby testy jednostkowe działały. Jednak napotkałem metody, które wykonują wiele operacji we / wy, a konfigurowanie środowiska testowego dla takich metod jest znacznie uproszczone dzięki użyciu pozorowanego systemu plików.
Steve Guidi,

Napisałem w tym celu github.com/guillaume86/VirtualPath (i nie tylko), to wciąż WIP i API na pewno się zmieni, ale już działa, a niektóre testy są dołączone.
Guillaume86

Odpowiedzi:


154

Edycja: zainstaluj pakiet NuGet System.IO.Abstractions.

Ten pakiet nie istniał, gdy pierwotnie zaakceptowano tę odpowiedź. Oryginalna odpowiedź jest podana poniżej w kontekście historycznym:

Możesz to zrobić, tworząc interfejs:

interface IFileSystem {
    bool FileExists(string fileName);
    DateTime GetCreationDate(string fileName);
}

i stworzenie „prawdziwej” implementacji, która używa System.IO.File.Exists () itd. Możesz następnie mockować ten interfejs przy użyciu frameworka mockującego; Polecam Moq .

Edycja: ktoś to zrobił i uprzejmie opublikował to w Internecie tutaj .

Użyłem tego podejścia do wyśmiewania DateTime.UtcNow w interfejsie IClock (naprawdę bardzo przydatne w naszych testach, aby móc kontrolować upływ czasu!), A bardziej tradycyjnie w interfejsie ISqlDataAccess.

Innym podejściem może być użycie TypeMock , co pozwala przechwytywać wywołania klas i odrzucać je. To jednak kosztuje i musiałoby być zainstalowane na komputerach całego zespołu i serwerze kompilacji, aby działać, a także najwyraźniej nie będzie działać dla System.IO.File, ponieważ nie może usunąć mscorlib .

Możesz także zaakceptować, że niektórych metod nie można testować jednostkowo i przetestować je w osobnym, wolno działającym zestawie testów integracyjnych / systemowych.


1
Moim zdaniem stworzenie interfejsu tak, jak opisuje to tutaj Matt, jest drogą do zrobienia. Napisałem nawet narzędzie, które generuje takie interfejsy dla Ciebie, co jest przydatne, gdy próbujesz mockować statyczne i / lub zapieczętowane klasy lub metody, które nie są deterministyczne (tj. Zegary i generatory liczb losowych). Zobacz jolt.codeplex.com aby uzyskać więcej informacji.
Steve Guidi

Wygląda na to, że repozytorium we wskazanym artykule zostało usunięte / przeniesione bez powiadomienia. Jednak wydaje się, że istnieje pakiet nuget
Mike-E

Typemock ma ograniczenia dotyczące tego, które typy są fałszywe, ale (przynajmniej w aktualnej wersji z października 2017 r.) Możesz zdecydowanie sfałszować statyczną klasę File. Właśnie to zweryfikowałem.
Ryan Rodemoyer

Czy możesz podsumować niektóre zestawy testów integracji?
Ozkan

83

Install-Package System.IO.Abstractions

Ta wyimaginowana biblioteka istnieje teraz, istnieje pakiet NuGet dla System.IO.Abstractions , który wyodrębnia przestrzeń nazw System.IO.

Istnieje również zestaw pomocników testowych, System.IO.Abstractions.TestingHelpers, który - w momencie pisania - jest tylko częściowo zaimplementowany, ale jest bardzo dobrym punktem wyjścia.


3
Myślę, że najlepszym rozwiązaniem jest standaryzacja wokół już zbudowanej abstrakcji. Nigdy nie słyszałem o tej bibliotece, więc wielkie dzięki za ostrzeżenie.
julealgon

PM oznacza menadżera pakietów .. otworzyć ... Narzędzia> Nuget Package Manager> Package Manager Console
thedanotto

11

Prawdopodobnie będziesz musiał zbudować kontrakt, aby określić, czego potrzebujesz z systemu plików, a następnie napisać opakowanie wokół tych funkcji. W tym momencie będziesz w stanie kpić z implementacji lub ją zablokować.

Przykład:

interface IFileWrapper { bool Exists(String filePath); }

class FileWrapper: IFileWrapper
{
    bool Exists(String filePath) { return File.Exists(filePath); }        
}

class FileWrapperStub: IFileWrapper
{
    bool Exists(String filePath) 
    { return (filePath == @"C:\myfilerocks.txt"); }
}

5

Zalecam użycie http://systemwrapper.codeplex.com/, ponieważ zapewnia on otoki dla najczęściej używanych typów w przestrzeni nazw System


Obecnie używam tej biblioteki i teraz, gdy odkryłem, że jej abstrakcje dla rzeczy takich jak FileStream nie zawierają IDisposable, szukam zamiennika. Jeśli biblioteka nie pozwala mi prawidłowo pozbyć się strumieni, to nie mogę jej polecić (ani użyć) do obsługi tego rodzaju operacji.
James Nail,

1
IFileStreamWrap SystemWrapper implementuje teraz IDisposable.
tster

systemwrapper jest tylko frameworkiem .net, spowoduje dziwne problemy, jeśli zostanie użyty z .netcore
Adil H. Raza

3

Natknąłem się na następujące rozwiązania tego problemu:

  • Napisz testy integracji, a nie testy jednostkowe. Aby to zadziałało, potrzebujesz prostego sposobu na utworzenie folderu, w którym możesz zrzucić rzeczy bez martwienia się o inne testy zakłócające. Mam prostą klasę TestFolder, która może utworzyć unikalny folder dla metody testowej do użycia.
  • Napisz mockable System.IO.File. Oznacza to utworzenie pliku IFile.cs . Uważam, że używanie tego często kończy się testami, które po prostu dowodzą, że możesz pisać fałszywe instrukcje, ale używaj ich, gdy użycie we / wy jest niewielkie.
  • Zbadaj warstwę abstrakcji i wyodrębnij plik IO z klasy. Utwórz w tym celu interfejs. Pozostali używają testów integracyjnych (ale to będzie bardzo małe). Różni się to od powyższego tym, że zamiast robić plik. Przeczytaj, napisz intencję, powiedzmy ioThingie.loadSettings ()
  • System.IO.Abstractions . Jeszcze tego nie używałem, ale to jest ten, z którym najbardziej podekscytowany jestem grą.

W końcu używam wszystkich powyższych metod, w zależności od tego, co piszę. Ale przez większość czasu myślę, że abstrakcja jest zła, kiedy piszę testy jednostkowe, które trafiają w IO.


4
Link do IFile.cs jest uszkodzony.
Mike-E

3

Używając System.IO.Abstractions i System.IO.Abstractions.TestingHelpers w ten sposób:

public class ManageFile {
   private readonly IFileSystem _fileSystem;
   public ManageFile(IFileSystem fileSystem){

      _fileSystem = fileSystem;
   }

   public bool FileExists(string filePath){}
       if(_fileSystem.File.Exists(filePath){
          return true;
       }
       return false;
   }
}

W swojej klasie testowej używasz MockFileSystem () do mockowania pliku i instalujesz ManageFile jak:

var mockFileSysteme = new MockFileSystem();
var mockFileData = new MockFileData("File content");
mockFileSysteme.AddFile(mockFilePath, mockFileData );
var manageFile = new ManageFile(mockFileSysteme);

2

Możesz to zrobić za pomocą Microsoft Fakes bez konieczności zmiany bazy kodu, na przykład ponieważ została już zamrożona.

Najpierw wygeneruj fałszywy zestaw dla System.dll - lub dowolnego innego pakietu, a następnie udawaj oczekiwane zwroty, 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

Trudno byłoby sfałszować system plików w teście, ponieważ interfejsy API plików .NET nie są tak naprawdę oparte na interfejsach lub rozszerzalnych klasach, które można by pozorować.

Jeśli jednak masz własną warstwę funkcjonalną, aby uzyskać dostęp do systemu plików, możesz kpić z tego w teście jednostkowym.

Jako alternatywę dla mockowania rozważ po prostu utworzenie folderów i plików, których potrzebujesz w ramach konfiguracji testowej, i usunięcie ich metodą dezaktywacji.


1

Nie jestem pewien, jak utworzyłbyś makietę systemu plików. To, co możesz zrobić, to napisać konfigurację urządzenia testowego, która tworzy folder itp. Z niezbędną strukturą do testów. Metoda zniszczenia wyczyściłaby go po zakończeniu testów.

Zmieniono, by dodać: myśląc o tym trochę więcej, nie sądzę, abyś chciał kpić z systemu plików, aby przetestować tego typu metody. Jeśli kpisz z systemu plików, aby zwrócić prawdę, jeśli określony plik istnieje, i użyjesz tego w swoim teście metody, która sprawdza, czy ten plik istnieje, to nie testujesz zbyt wiele. Mockowanie systemu plików byłoby przydatne, gdybyś chciał przetestować metodę, która była zależna od systemu plików, ale aktywność systemu plików nie była integralna z testowaną metodą.


1

Odpowiadając na twoje konkretne pytanie: Nie, nie ma bibliotek, które pozwolą ci na mockowanie wywołań we / wy plików (o których wiem). Oznacza to, że „prawidłowe” testowanie jednostek typów będzie wymagało uwzględnienia tego ograniczenia podczas definiowania typów.

Krótka uwaga na temat tego, jak definiuję „właściwy” test jednostkowy. Uważam, że testy jednostkowe powinny potwierdzić, że otrzymujesz oczekiwane dane wyjściowe (np. Wyjątek, wywołanie metody itp.) Pod warunkiem, że znane dane wejściowe. Umożliwia to skonfigurowanie warunków testu jednostki jako zestawu wejść i / lub stanów wejściowych. Najlepszym sposobem, jaki znalazłem, jest użycie usług opartych na interfejsach i iniekcji zależności, tak aby każda odpowiedzialność zewnętrzna w stosunku do typu była dostarczana za pośrednictwem interfejsu przekazanego przez konstruktor lub właściwość.

Mając to na uwadze, wracając do pytania. Kpiłem z wywołań systemu plików, tworząc IFileSystemServiceinterfejs wraz z FileSystemServiceimplementacją, która jest po prostu fasadą metod systemu plików mscorlib. Mój kod używa następnie IFileSystemServicetypów zamiast mscorlib. Dzięki temu mogę podłączyć mój standard, FileSystemServicegdy aplikacja jest uruchomiona lub udawać IFileSystemServicew moich testach jednostkowych. Kod aplikacji jest taki sam, niezależnie od tego, jak działa, ale podstawowa infrastruktura umożliwia łatwe testowanie tego kodu.

Przyznaję, że używanie opakowania wokół obiektów systemu plików mscorlib jest uciążliwe, ale w tych konkretnych scenariuszach warto wykonać dodatkową pracę, ponieważ testowanie staje się znacznie łatwiejsze i bardziej niezawodne.


1

Tworzenie interfejsu i mockowanie go do testowania to najczystszy sposób. Jednak jako alternatywę możesz przyjrzeć się frameworkowi Microsoft Moles .


0

Typowym rozwiązaniem jest użycie abstrakcyjnego API systemu plików (takiego jak Apache Commons VFS dla Java): cała logika aplikacji używa API, a testy jednostkowe są w stanie symulować prawdziwy system plików z implementacją stub (emulacja w pamięci lub coś w tym rodzaju).

Dla C # istnieje podobny interfejs API: NI.Vfs, który jest bardzo podobny do Apache VFS V1. Zawiera domyślne implementacje zarówno dla lokalnego systemu plików, jak i systemu plików w pamięci (ostatnia z nich może być używana w testach jednostkowych z pudełka).


-1

Obecnie używamy zastrzeżonego silnika danych, a jego API nie jest ujawniane jako interfejsy, więc prawie nie możemy testować jednostkowo naszego kodu dostępu do danych. Potem poszedłem z podejściem Matta i Josepha.


-2

Poszedłbym z odpowiedzią Jamiego Ide. Nie próbuj kpić z rzeczy, których nie napisałeś. Będą różne zależności, o których nie wiedziałeś - zamknięte klasy, niewirtualne metody itp.

Innym podejściem byłoby owinięcie odpowiednich metod czymś, co jest do podrobienia. np. utwórz klasę o nazwie FileWrapper, która umożliwia dostęp do metod File, ale jest czymś, co można wyszydzić.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.