¿Cómo se burlan del sistema de archivos en C # para pruebas unitarias?

149

¿Hay alguna biblioteca o método para simular el sistema de archivos en C # para escribir pruebas unitarias? En mi caso actual, tengo métodos que verifican si existe cierto archivo y leen la fecha de creación. Puede que necesite más que eso en el futuro.

pupeno
fuente
1
Esto parece un duplicado de varios otros, incluidos: stackoverflow.com/questions/664277/… .
John Saunders el
Tal vez intente buscar en pex ( research.microsoft.com/en-us/projects/pex/filesystem.pdf )
Tinus
2
@Mitch: la mayoría de las veces, es suficiente colocar datos en el sistema de archivos y dejar que las pruebas unitarias sigan su curso. Sin embargo, he encontrado métodos que ejecutan muchas operaciones de E / S, y la configuración del entorno de prueba para dichos métodos se simplifica enormemente mediante el uso de un sistema de archivos simulado.
Steve Guidi el
Escribí github.com/guillaume86/VirtualPath para ese propósito (y más), sigue siendo WIP y la API ciertamente cambiará, pero ya funciona, y se incluyen algunas pruebas.
Guillaume86

Respuestas:

154

Editar: instale el paquete NuGet System.IO.Abstractions.

Este paquete no existía cuando esta respuesta fue aceptada originalmente. La respuesta original se proporciona para el contexto histórico a continuación:

Podrías hacerlo creando una interfaz:

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

y creando una implementación 'real' que usa System.IO.File.Exists () etc. Luego puede burlarse de esta interfaz usando un marco de burla; Recomiendo Moq .

Editar: alguien ha hecho esto y lo ha publicado amablemente en línea aquí .

He utilizado este enfoque para simular DateTime.UtcNow en una interfaz IClock (¡realmente muy útil para que nuestras pruebas puedan controlar el flujo del tiempo!), Y más tradicionalmente, una interfaz ISqlDataAccess.

Otro enfoque podría ser usar TypeMock , esto le permite interceptar llamadas a clases y eliminarlas. Sin embargo, esto cuesta dinero, y necesitaría instalarse en las PC de todo su equipo y en su servidor de compilación para poder ejecutarse, además, aparentemente no funcionará para el archivo System.IO., ya que no puede bloquear mscorlib .

También podría aceptar que ciertos métodos no son comprobables por unidad y probarlos en un conjunto separado de pruebas de integración / sistema de ejecución lenta.

Matt Howells
fuente
1
En mi opinión, crear una interfaz como Matt describe aquí es el camino a seguir. Incluso he escrito una herramienta que genera tales interfaces para usted, que es útil cuando se trata de burlarse de clases estáticas y / o selladas, o métodos que no son deterministas (es decir, relojes y generadores de números aleatorios). Consulte jolt.codeplex.com para obtener más información.
Steve Guidi el
Parece que el repositorio en el artículo referenciado se ha eliminado / movido sin previo aviso. Sin embargo, parece haber un paquete nuget de sus esfuerzos aquí: nuget.org/packages/mscorlib-mock
Mike-E
Typemock tiene restricciones sobre qué tipos son falsos, pero (al menos en la versión actual a partir de octubre de 2017) definitivamente puede falsificar la clase estática de archivos. Acabo de verificar esto yo mismo.
Ryan Rodemoyer
¿Puedes resumir algunas suites de prueba de integración?
Ozkan
83

Install-Package System.IO.Abstraction

Esta biblioteca imaginaria existe ahora, hay un paquete NuGet para System.IO.Abstraction , que abstrae el espacio de nombres System.IO.

También hay un conjunto de ayudantes de prueba, System.IO.Abstraction.TestingHelpers que, en el momento de la escritura, solo está implementado parcialmente, pero es un muy buen punto de partida.

Binario Worrier
fuente
3
Creo que estandarizar alrededor de esta abstracción ya construida es la mejor apuesta. Nunca he oído hablar de esa biblioteca, así que muchas gracias por el aviso.
julealgon
PM significa administrador de paquetes ... para abrir ... Herramientas> Administrador de paquetes NuGet> Consola de administrador de paquetes
thedanotto
11

Probablemente tendrá que crear un contrato para definir qué cosas necesita del sistema de archivos y luego escribir un contenedor alrededor de esas funcionalidades. En ese momento, podrás burlarte o rechazar la implementación.

Ejemplo:

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
fuente
5

Mi recomendación es utilizar http://systemwrapper.codeplex.com/, ya que proporciona contenedores para los tipos más utilizados en el espacio de nombres del sistema

adeel41
fuente
Actualmente estoy usando esta biblioteca, y ahora que descubrí que sus abstracciones para cosas como FileStream no incluyen IDisposable, estoy buscando un reemplazo. Si la biblioteca no me permite eliminar correctamente las transmisiones, entonces no puedo recomendarla (o usarla) para manejar ese tipo de operaciones.
James Nail
1
El IFileStreamWrap de SystemWrapper implementa IDisposable ahora.
tster
systemwrapper es solo .net framework, causará problemas extraños si se usa con .netcore
Adil H. Raza
3

He encontrado las siguientes soluciones para esto:

  • Escribir pruebas de integración, no pruebas unitarias. Para que esto funcione, necesita una forma simple de crear una carpeta donde pueda volcar cosas sin preocuparse de que otras pruebas interfieran. Tengo una clase TestFolder simple que puede crear una carpeta única por método de prueba para usar.
  • Escriba un archivo System.IO. simulable. Eso es crear un IFile.cs . Creo que usar esto a menudo termina con pruebas que simplemente prueban que puedes escribir declaraciones burlonas, pero lo usas cuando el uso de IO es pequeño.
  • Examine su capa de abstracción y extraiga el archivo IO de la clase. El crea una interfaz para esto. El resto usa pruebas de integración (pero esto será muy pequeño). Esto difiere de lo anterior en que en lugar de hacer el archivo. Lea, escriba la intención, diga ioThingie.loadSettings ()
  • System.IO.Abstraction . Todavía no he usado esto, pero es el que más me entusiasma jugar.

Termino usando todos los métodos anteriores, dependiendo de lo que estoy escribiendo. Pero la mayoría de las veces termino pensando que la abstracción está mal cuando escribo pruebas unitarias que llegan al IO.

Michael Lloyd Lee mlk
fuente
44
El enlace a IFile.cs está roto.
Mike-E
3

Al usar System.IO.Abstraction y System.IO.Abstraction.TestingHelpers así:

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

En su clase de prueba, utiliza MockFileSystem () para simular el archivo e instanciar ManageFile como:

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

Puede hacerlo utilizando Microsoft Fakes sin la necesidad de cambiar su base de código, por ejemplo, porque ya estaba congelada.

Primero genere un ensamblaje falso para System.dll - o cualquier otro paquete y luego simule los retornos esperados como en:

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
fuente
1

Sería difícil burlarse del sistema de archivos en una prueba, ya que las API de archivos .NET no se basan realmente en interfaces o clases extensibles que se puedan burlar.

Sin embargo, si tiene su propia capa funcional para acceder al sistema de archivos, podría burlarse de eso en una prueba unitaria.

Como alternativa a la burla, considere crear las carpetas y archivos que necesita como parte de su configuración de prueba y eliminarlos en su método de desmontaje.

LBushkin
fuente
1

No estoy seguro de cómo se burlaría del sistema de archivos. Lo que podría hacer es escribir una configuración de dispositivo de prueba que cree una carpeta, etc. con la estructura necesaria para las pruebas. Un método de desmontaje lo limpiaría después de ejecutar las pruebas.

Editado para agregar: Al pensar en esto un poco más, no creo que quiera burlarse del sistema de archivos para probar este tipo de métodos. Si se burla del sistema de archivos para que devuelva verdadero si existe un determinado archivo y lo usa en su prueba de un método que verifica si ese archivo existe, entonces no está probando mucho de nada. Donde burlarse del sistema de archivos sería útil si quisiera probar un método que dependiera del sistema de archivos pero la actividad del sistema de archivos no fuera parte integral del método bajo prueba.

Jamie Ide
fuente
1

Para responder a su pregunta específica: No, no hay bibliotecas que le permitan simular llamadas de E / S de archivos (que yo sepa). Esto significa que la unidad "correctamente" que prueba sus tipos requerirá que tenga en cuenta esta restricción cuando defina sus tipos.

Nota rápida sobre cómo defino una prueba de unidad "adecuada". Creo que las pruebas unitarias deberían confirmar que obtienes el resultado esperado (ya sea una excepción, llamar a un método, etc.) proporcionó entradas conocidas. Esto le permite configurar las condiciones de prueba de su unidad como un conjunto de entradas y / o estados de entrada. La mejor manera que he encontrado para hacer esto es usar servicios basados ​​en interfaz e inyección de dependencia para que cada responsabilidad externa a un tipo se proporcione a través de una interfaz que se pasa a través de un constructor o propiedad.

Entonces, con esto en mente, regresemos a su pregunta. Me he burlado de las llamadas al sistema de archivos creando una IFileSystemServiceinterfaz junto con una FileSystemServiceimplementación que es simplemente una fachada sobre los métodos del sistema de archivos mscorlib. Mi código luego usa los IFileSystemServicetipos mscorlib en lugar de los tipos. Esto me permite conectar mi estándar FileSystemServicecuando la aplicación se está ejecutando o simular IFileSystemServiceen las pruebas de mi unidad. El código de la aplicación es el mismo independientemente de cómo se ejecute, pero la infraestructura subyacente permite que ese código se pruebe fácilmente.

Reconoceré que es difícil usar el contenedor alrededor de los objetos del sistema de archivos mscorlib pero, en estos escenarios específicos, vale la pena el trabajo extra ya que las pruebas se vuelven mucho más fáciles y confiables.

akmad
fuente
1

Crear una interfaz y burlarse de ella para realizar pruebas es el camino más limpio. Sin embargo, como alternativa, podría echar un vistazo al marco Microsoft Moles .

Konamiman
fuente
0

La solución común es usar una API de sistema de archivos abstracto (como Apache Commons VFS para Java): toda la lógica de aplicación usa API y la prueba de unidad puede burlarse del sistema de archivos real con implementación de código auxiliar (emulación en memoria o algo así).

Para C # existe una API similar: NI.Vfs, que es muy similar a Apache VFS V1. Contiene implementaciones predeterminadas tanto para el sistema de archivos local como para el sistema de archivos en memoria (el último puede usarse en pruebas unitarias desde la caja).

Vitaliy Fedorchenko
fuente
-1

Actualmente utilizamos un motor de datos patentado y su API no está expuesta como interfaces, por lo que apenas podemos probar nuestro código de acceso a datos. Luego fui con el enfoque de Matt y Joseph también.

Tien Do
fuente
-2

Iría con la respuesta de Jamie Ide. No trates de burlarte de cosas que no escribiste. Habrá todo tipo de dependencias que no conocía: clases selladas, métodos no virtuales, etc.

Otro enfoque sería envolver los métodos apropiados con algo que se pueda burlar. por ejemplo, cree una clase llamada FileWrapper que permita el acceso a los métodos File pero es algo que puede burlarse.

gbanfill
fuente