2009-10-06 6 views
50

Leyendo a través de los subprocesos de pruebas de unidades existentes aquí en Desbordamiento de pila, no pude encontrar uno con una respuesta clara sobre cómo probar las operaciones de E/S de archivos de prueba. Recientemente comencé a investigar las pruebas unitarias, al haber tenido conocimiento de las ventajas pero tener dificultades para acostumbrarme a escribir pruebas primero. He configurado mi proyecto para usar NUnit y Rhino Mocks, y aunque entiendo el concepto detrás de ellos, tengo problemas para entender cómo usar Mock Objects.E/S de archivos de prueba de unidades

Específicamente, tengo dos preguntas que me gustaría responder. Primero, ¿cuál es la forma correcta de analizar las operaciones de E/S del archivo de prueba? En segundo lugar, en mis intentos por aprender acerca de las pruebas unitarias, me encontré con la inyección de dependencia. Después de configurar y poner en funcionamiento el Ninject, me preguntaba si debería usar DI en mis pruebas unitarias, o simplemente crear instancias de objetos directamente.

+5

si es o no el uso de DI para las pruebas unitarias deben ser una cuestión separada IHMO. –

+0

Recomendaría usar el período DI ** ** (ver [este artículo] (http://blog.ploeh.dk/2011/05/24/PokayokeDesignFromSmellToFragrance.aspx), y las que enlaza) –

Respuesta

28

Echa un vistazo Tutorial to TDD usando Rhino Mocks y SystemWrapper.

SystemWrapper ajusta muchas de las clases de System.IO incluyendo File, FileInfo, Directory, DirectoryInfo, .... Usted puede ver the complete list.

En este tutorial estoy mostrando cómo hacer pruebas con MbUnit, pero es exactamente lo mismo para NUnit.

Su prueba va a ser algo como esto:

[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()); 
} 
+3

+1 - ¡SystemWrapper parece útil! Hay una buena discusión de esta técnica en el podcast en http://www.slickthought.net/post/2009/04/03/New-Spaghetti-Code-Podcast-ndash3b-Donn-Felker-Talks-Mocking.aspx – TrueWill

+0

This disponible en NuGet todavía? No lo vi Dispuesto a aceptar que me lo perdí :) –

+0

Está disponible en NuGet. No es la última versión, pero está disponible. Busque SystemWrapper. https://nuget.org/packages/SystemWrapper/0.4 – Vadim

9

P1:

Usted tiene tres opciones aquí.

Opción 1: vivir con ella.

(sin ejemplo: P)

Opción 2: Crear un ligero abstracción cuando sea necesario.

En lugar de hacer las E/S de archivo (File.ReadAllBytes o lo que sea) en el método bajo prueba, puede cambiarlo para que IO se haga afuera y pase una transmisión.

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

se convertiría en

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

Este enfoque es una solución de compromiso. En primer lugar, sí, es más comprobable. Sin embargo, intercambia la capacidad de prueba por una pequeña adición a la complejidad. Esto puede afectar la capacidad de mantenimiento y la cantidad de código que tiene que escribir, además de que puede mover su problema de prueba a un nivel.

Sin embargo, en mi experiencia, este es un enfoque agradable y equilibrado, ya que puede generalizar y hacer comprobable la importante lógica sin comprometerse con un sistema de archivos completamente envuelto. Es decir. puedes generalizar las partes que realmente te interesan, dejando el resto tal como está.

Opción 3: Envolver todo el sistema de archivos

Tomando un paso más allá, el imitar el sistema de archivos puede ser un enfoque válido; depende de la cantidad de hinchazón con la que estés dispuesto a vivir.

He hecho esta ruta antes; Tenía una implementación de sistema de archivos envuelto, pero al final simplemente lo eliminé.Hubo diferencias sutiles en la API, tuve que inyectarlo en todas partes y, en última instancia, fue un dolor extra con pocas ganancias ya que muchas de las clases que lo usaban no eran muy importantes para mí. Si hubiera estado usando un contenedor IoC o escribiendo algo que fuera crítico y las pruebas necesitaran ser rápidas, podría haberme quedado con él. Al igual que con todas estas opciones, su millaje puede variar.

cuanto a su pregunta contenedor IoC:

inyectarse la prueba de dobles manualmente. Si tiene que hacer un montón de trabajo repetitivo, solo use los métodos de instalación/fábrica en sus pruebas. ¡Utilizar un contenedor de IoC para realizar pruebas sería excesivo en extremo! Sin embargo, tal vez no entiendo tu segunda pregunta.

1

Actualmente, consumen un objeto IFileSystem a través de la inyección de dependencia. Para el código de producción, una clase contenedora implementa la interfaz, envolviendo las funciones específicas de IO que necesito. Al probar, puedo crear una implementación nula o de stub y proporcionar eso a la clase bajo prueba. La clase probada no es la más sabia.

32

no hay necesariamente una que hay que hacer cuando se prueba el sistema de archivos. En verdad, hay varias cosas que podrías hacer, dependiendo de las circunstancias.

La pregunta que hay que preguntarse es: ¿Qué voy probando?

  • Que el sistema de archivos funciona? Es probable que no necesita probar que menos que esté utilizando un sistema operativo que estás muy familiarizado. Entonces, si simplemente está dando un comando para guardar archivos, por ejemplo, es una pérdida de tiempo escribir una prueba para asegurarse de que realmente se guarde.

  • que los archivos se guardan en el lugar correcto? Bueno, ¿cómo sabes cuál es el lugar correcto? Es de suponer que tiene un código que combina una ruta con un nombre de archivo. Este es un código que puede probar fácilmente: su entrada es de dos cadenas y su salida debe ser una cadena que es una ubicación de archivo válida construida con esas dos cadenas.

  • ¿Obtienes el conjunto correcto de archivos de un directorio? Probablemente tengas que escribir una prueba para tu clase get-file que realmente prueba el sistema de archivos. Pero debe usar un directorio de prueba con archivos que no cambien. También debe colocar esta prueba en un proyecto de prueba de integración, porque esta no es una verdadera prueba de unidad, porque depende del sistema de archivos.

  • Pero, tengo que hacer algo con los archivos que recibo. Para prueba, debe usar falso para su clase get-file. Su falso debería devolver una lista de archivos codificados. Si utiliza un realizador de archivos y un real procesador de archivos, no sabrá cuál causa una falla en la prueba. Por lo tanto, su clase de procesador de archivos, en pruebas, debe hacer uso de una clase falsa de búsqueda de archivos. Su clase-procesador de archivos debe tomar la interfaz de archivo-comprador . En el código real, pasarás el verdadero captador de archivos. En el código de prueba, pasará un compilador de archivos falso que devuelve una lista estática conocida.

Los principios fundamentales son:

  • utilizar un sistema de archivos falsos, escondido detrás de una interfaz, cuando no se está probando el sistema de archivos.
  • Si usted necesita para poner a prueba las operaciones de archivo real, entonces
    • marcar la prueba como una prueba de integración, no es una prueba de la unidad.
    • tienen un directorio de prueba designado, un conjunto de archivos, etc. que siempre estarán allí en un estado sin cambios, por lo que sus pruebas de integración orientadas a archivos pueden pasar constantemente.
1

Desde 2012, se puede hacer eso usando Microsoft Fakes sin la necesidad de cambiar su código base, por ejemplo, debido a que ya se había congelado.

Primera generate a fake assembly para System.dll - o cualquier otro paquete y luego vuelve burlarse 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 
} 
Cuestiones relacionadas