Como você zomba do sistema de arquivos em C # para teste de unidade?

149

Existem bibliotecas ou métodos para zombar do sistema de arquivos em C # para gravar testes de unidade? No meu caso atual, tenho métodos que verificam se determinado arquivo existe e leem a data de criação. Eu posso precisar de mais do que isso no futuro.

pupeno
fonte
1
Parece uma duplicata de várias outras, incluindo: stackoverflow.com/questions/664277/… .
John Saunders
Talvez tente olhar para pex ( research.microsoft.com/en-us/projects/pex/filesystem.pdf )
Tinus
2
@ Mitch: Na maioria das vezes, é suficiente colocar dados no sistema de arquivos e permitir que os testes de unidade continuem. No entanto, encontrei métodos que executam muitas operações de E / S e a configuração do ambiente de teste para esses métodos é bastante simplificada usando um sistema de arquivos simulado.
23411 Steve Guidi
Eu escrevi github.com/guillaume86/VirtualPath para esse fim (e mais), ainda é o WIP e a API certamente mudará, mas já funciona e alguns testes estão incluídos.
Guillaume86

Respostas:

154

Editar: Instale o pacote NuGet System.IO.Abstractions.

Este pacote não existia quando a resposta foi aceita originalmente. A resposta original é fornecida para o contexto histórico abaixo:

Você pode fazer isso criando uma interface:

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

e criar uma implementação 'real' que usa System.IO.File.Exists () etc. Você pode então zombar dessa interface usando uma estrutura de zombaria; Eu recomendo o Moq .

Edit: alguém fez isso e gentilmente postou online aqui .

Usei essa abordagem para zombar do DateTime.UtcNow em uma interface IClock (realmente muito útil para nossos testes poderem controlar o fluxo do tempo!) E, mais tradicionalmente, em uma interface ISqlDataAccess.

Outra abordagem pode ser o uso do TypeMock , que permite interceptar chamadas para classes e eliminá-las. No entanto, isso custa dinheiro e precisa ser instalado nos PCs de toda a equipe e no servidor de compilação para que seja executado, além disso, aparentemente ele não funcionará para o System.IO.File, pois não pode stub mscorlib .

Você também pode simplesmente aceitar que certos métodos não podem ser testados por unidade e testá-los em um conjunto separado de integração / testes de sistema de execução lenta.

Matt Howells
fonte
1
Na minha opinião, criar uma interface como Matt descreve aqui é o caminho a percorrer. Até escrevi uma ferramenta que gera essas interfaces para você, que é útil ao tentar zombar de classes estáticas e / ou seladas ou métodos não determinísticos (por exemplo, relógios e geradores de números aleatórios). Veja jolt.codeplex.com para mais informações.
Steve Guidi
Parece que o repositório no artigo mencionado foi excluído / movido sem aviso prévio. No entanto, parece haver um pacote nuget de seus esforços aqui: nuget.org/packages/mscorlib-mock #
Mike-E
O Typemock tem restrições sobre quais tipos são procuráveis, mas (pelo menos na versão atual a partir de outubro de 2017) você pode definitivamente fingir a classe estática File. Acabei de verificar isso sozinho.
Ryan Rodemoyer
Você pode resumir alguns conjuntos de testes de integração?
Ozkan
83

Install-Package System.IO.Abstractions

Esta biblioteca imaginária existe agora, há um pacote NuGet para System.IO.Abstractions , que abstrai o espaço para nome System.IO.

Há também um conjunto de auxiliares de teste, System.IO.Abstractions.TestingHelpers que - no momento da redação deste documento - são implementados apenas parcialmente, mas é um excelente ponto de partida.

Preocupação binária
fonte
3
Eu acho que padronizar em torno dessa abstração já construída é a melhor aposta. Nunca ouvi falar dessa biblioteca, então muito obrigado pelo aviso.
21814 julealgon
PM significa gerenciador de pacotes .. para abrir ... Ferramentas> NuGet Package Manager> Console do Gerenciador de Pacotes
thedanotto
11

Provavelmente, você precisará criar um contrato para definir o que precisa do sistema de arquivos e, em seguida, escrever um wrapper sobre essas funcionalidades. Nesse ponto, você poderá zombar ou descartar a implementação.

Exemplo:

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"); }
}
Joseph
fonte
5

Minha recomendação é usar http://systemwrapper.codeplex.com/ , pois fornece wrappers para os tipos mais usados ​​no namespace do sistema

adeel41
fonte
Atualmente, estou usando esta biblioteca e agora que descobri que suas abstrações para coisas como FileStream não incluem IDisposable, estou procurando uma substituição. Se a biblioteca não permitir que eu descarte corretamente os fluxos, não posso recomendá-la (ou usá-la) para lidar com esses tipos de operações.
James prego
1
O IFileStreamWrap do SystemWrapper implementa IDisposable agora.
tster 3/03/16
systemwrapper é apenas o framework .net, causará problemas estranhos se usado com o .netcore
Adil H. Raza
3

Encontrei as seguintes soluções para isso:

  • Escreva testes de integração, não testes de unidade. Para que isso funcione, você precisa de uma maneira simples de criar uma pasta na qual você pode despejar coisas sem se preocupar com outros testes que interfiram. Eu tenho uma classe TestFolder simples que pode criar uma pasta exclusiva por método de teste para usar.
  • Escreva um System.IO.File zombável. Isso é criar um IFile.cs . Acho que o uso disso geralmente termina com testes que simplesmente provam que você pode escrever declarações de zombaria, mas usam quando o uso de IO é pequeno.
  • Examine sua camada de abstração e extraia o arquivo IO da classe. O criar uma interface para isso. O restante usa testes de integração (mas isso será muito pequeno). Isso é diferente do descrito acima em vez de fazer file.Lead você escrever a intenção, digamos ioThingie.loadSettings ()
  • System.IO.Abstractions . Ainda não o usei, mas é com quem mais me empolgo em brincar.

Acabo usando todos os métodos acima, dependendo do que estou escrevendo. Mas, na maioria das vezes, acabo pensando que a abstração está errada quando escrevo testes de unidade que atingem o IO.

Michael Lloyd Lee mlk
fonte
4
O link para IFile.cs está quebrado.
Mike-E
3

Usando System.IO.Abstractions e System.IO.Abstractions.TestingHelpers assim:

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;
   }
}

Na sua classe de teste, você usa o MockFileSystem () para simular um arquivo e instancia o ManageFile como:

var mockFileSysteme = new MockFileSystem();
var mockFileData = new MockFileData("File content");
mockFileSysteme.AddFile(mockFilePath, mockFileData );
var manageFile = new ManageFile(mockFileSysteme);
Olivier Martial Soro
fonte
2

Você pode fazer isso usando o Microsoft Fakes sem a necessidade de alterar sua base de código, por exemplo, porque ela já estava congelada.

Primeiro, gere um assembly falso para o System.dll - ou qualquer outro pacote e depois simule os retornos esperados, como em:

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
}
Bahadır İsmail Aydın
fonte
1

Seria difícil zombar do sistema de arquivos em um teste, pois as APIs de arquivos .NET não são realmente baseadas em interfaces ou classes extensíveis que poderiam ser zombadas.

No entanto, se você tiver sua própria camada funcional para acessar o sistema de arquivos, poderá zombar disso em um teste de unidade.

Como alternativa à zombaria, considere apenas criar as pastas e arquivos necessários como parte da configuração do teste e excluí-los no seu método de desmontagem.

LBushkin
fonte
1

Não sei como você zombaria do sistema de arquivos. O que você pode fazer é escrever uma configuração de equipamento de teste que crie uma pasta etc. com a estrutura necessária para os testes. Um método de desmontagem o limparia após a execução dos testes.

Editado para adicionar: Ao pensar um pouco mais sobre isso, não acho que você queira zombar do sistema de arquivos para testar esse tipo de método. Se você zomba do sistema de arquivos para retornar true se um determinado arquivo existir e o usa no teste de um método que verifica se esse arquivo existe, não está testando muita coisa. Onde a zombaria do sistema de arquivos seria útil é se você quisesse testar um método que dependesse do sistema de arquivos, mas a atividade do sistema de arquivos não fosse parte integrante do método em teste.

Jamie Ide
fonte
1

Para responder sua pergunta específica: Não, não há bibliotecas que permitam que você imite chamadas de E / S de arquivos (que eu saiba). Isso significa que o teste "unitário" adequado de seus tipos exigirá que você leve essa restrição em consideração ao definir seus tipos.

Nota lateral rápida sobre como eu defino um teste de unidade "adequado". Acredito que os testes de unidade devem confirmar que você obtém a saída esperada (seja uma exceção, invoque um método etc.), desde que as entradas conhecidas sejam fornecidas. Isso permite que você configure suas condições de teste de unidade como um conjunto de entradas e / ou estados de entrada. A melhor maneira que encontrei para fazer isso é usar serviços baseados em interface e injeção de dependência, para que cada responsabilidade externa a um tipo seja fornecida por meio de uma interface transmitida por um construtor ou propriedade.

Então, com isso em mente, volte à sua pergunta. Eu zombei das chamadas do sistema de arquivos criando uma IFileSystemServiceinterface junto com uma FileSystemServiceimplementação que é simplesmente uma fachada dos métodos do sistema de arquivos mscorlib. Meu código usa os IFileSystemServicetipos em vez dos mscorlib. Isso me permite conectar meu padrão FileSystemServicequando o aplicativo está sendo executado ou zombar dos IFileSystemServicetestes de unidade. O código do aplicativo é o mesmo, independentemente de como é executado, mas a infraestrutura subjacente permite que esse código seja facilmente testado.

Reconheço que é uma tarefa difícil usar o wrapper em torno dos objetos do sistema de arquivos mscorlib, mas, nesses cenários específicos, vale a pena o trabalho extra, pois o teste se torna muito mais fácil e confiável.

akmad
fonte
1

Criar uma interface e zombar dela para teste é o caminho mais limpo a seguir. No entanto, como alternativa, você pode dar uma olhada na estrutura do Microsoft Moles .

Konamiman
fonte
0

A solução comum é usar alguma API abstrata do sistema de arquivos (como o Apache Commons VFS para Java): toda a lógica do aplicativo usa API e o teste de unidade é capaz de zombar do sistema de arquivos real com a implementação do stub (emulação na memória ou algo parecido).

Para C #, existe uma API semelhante: NI.Vfs, que é muito semelhante ao Apache VFS V1. Ele contém implementações padrão para o sistema de arquivos local e o sistema de arquivos em memória (o último pode ser usado em testes de unidade da caixa).

Vitaliy Fedorchenko
fonte
-1

Atualmente, usamos um mecanismo de dados proprietário e sua API não é exposta como interfaces, portanto, dificilmente podemos testar nosso código de acesso a dados. Então eu também fui com a abordagem de Matt e Joseph.

Tien Do
fonte
-2

Eu iria com a resposta de Jamie Ide. Não tente zombar de coisas que você não escreveu. Haverá todos os tipos de dependências que você não conhecia - classes seladas, métodos não virtuais etc.

Outra abordagem seria envolver os métodos de aplicação com algo que pode ser ridicularizado. por exemplo, crie uma classe chamada FileWrapper que permita o acesso aos métodos File, mas é algo que você pode zombar.

gbanfill
fonte