2012-04-25 12 views
26

tengo una clase Transfer, simplificado que tiene este aspecto:¿Se burla de un método para lanzar una excepción (moq), pero actúa como el objeto burlado?

public class Transfer 
{ 
    public virtual IFileConnection source { get; set; } 
    public virtual IFileConnection destination { get; set; } 

    public virtual void GetFile(IFileConnection connection, 
     string remoteFilename, string localFilename) 
    { 
     connection.Get(remoteFilename, localFilename); 
    } 

    public virtual void PutFile(IFileConnection connection, 
     string localFilename, string remoteFilename) 
    { 
     connection.Get(remoteFilename, localFilename); 
    } 

    public virtual void TransferFiles(string sourceName, string destName) 
    { 
     source = internalConfig.GetFileConnection("source"); 
     destination = internalConfig.GetFileConnection("destination"); 
     var tempName = Path.GetTempFileName(); 
     GetFile(source, sourceName, tempName); 
     PutFile(destination, tempName, destName); 
    } 
} 

La versión simplificada de la interfaz IFileConnection se parece a esto:

public interface IFileConnection 
{ 
    void Get(string remoteFileName, string localFileName); 
    void Put(string localFileName, string remoteFileName); 
} 

Se supone que la verdadera clase para manejar una System.IO.IOException que es lanzado cuando las clases concretas IFileConnection pierden conectividad con el control remoto, enviando correos electrónicos y lo que no.

me gustaría utilizar Moq para crear una clase Transfer, y utilizarlo como mi hormigón clase Transfer en todas las propiedades y métodos, excepto cuando se invoca el método GetFile - entonces quiero que tire System.IO.IOException y asegúrese de que el Transfer clase maneja correctamente.

¿Estoy utilizando la herramienta adecuada para el trabajo? ¿Voy por esto de la manera correcta? ¿Y cómo escribiría la configuración para esa prueba de unidad para NUnit?

Respuesta

7

Así es como me las arreglé para hacer lo que estaba tratando de hacer:

[Test] 
public void TransferHandlesDisconnect() 
{ 
    // ... set up config here 
    var methodTester = new Mock<Transfer>(configInfo); 
    methodTester.CallBase = true; 
    methodTester 
     .Setup(m => 
      m.GetFile(
       It.IsAny<IFileConnection>(), 
       It.IsAny<string>(), 
       It.IsAny<string>() 
      )) 
     .Throws<System.IO.IOException>(); 

    methodTester.Object.TransferFiles("foo1", "foo2"); 
    Assert.IsTrue(methodTester.Object.Status == TransferStatus.TransferInterrupted); 
} 

Si hay un problema con este método, me gustaría saber; las otras respuestas sugieren que estoy haciendo esto mal, pero esto era exactamente lo que estaba tratando de hacer.

49

Así es como se puede burlarse de su FileConnection

Mock<IFileConnection> fileConnection = new Mock<IFileConnection>(
                  MockBehavior.Strict); 
fileConnection.Setup(item => item.Get(It.IsAny<string>,It.IsAny<string>)) 
       .Throws(new IOException()); 

Entonces instancia su clase de transferencia y el uso de la maqueta en su llamada al método

Transfer transfer = new Transfer(); 
transfer.GetFile(fileConnection.Object, someRemoteFilename, someLocalFileName); 

Actualización:

En primer lugar usted tiene que burlarse de sus dependencias solamente, no de la clase que está probando (Clase de transferencia en este caso). Al indicar esas dependencias en su constructor, es más fácil ver qué servicios necesita su clase para funcionar. También hace posible reemplazarlos con falsificaciones cuando está escribiendo las pruebas de su unidad. Por el momento, es imposible reemplazar esas propiedades con falsificaciones.

Puesto que usted está configurando las propiedades utilizando otra dependencia, lo escribiría así:

public class Transfer 
{ 
    public Transfer(IInternalConfig internalConfig) 
    { 
     source = internalConfig.GetFileConnection("source"); 
     destination = internalConfig.GetFileConnection("destination"); 
    } 

    //you should consider making these private or protected fields 
    public virtual IFileConnection source { get; set; } 
    public virtual IFileConnection destination { get; set; } 

    public virtual void GetFile(IFileConnection connection, 
     string remoteFilename, string localFilename) 
    { 
     connection.Get(remoteFilename, localFilename); 
    } 

    public virtual void PutFile(IFileConnection connection, 
     string localFilename, string remoteFilename) 
    { 
     connection.Get(remoteFilename, localFilename); 
    } 

    public virtual void TransferFiles(string sourceName, string destName) 
    { 
     var tempName = Path.GetTempFileName(); 
     GetFile(source, sourceName, tempName); 
     PutFile(destination, tempName, destName); 
    } 
} 

De esta manera puede burlarse internalConfig y hacerlo volver burla IFileConnection que hace lo que quiere.

+0

Hmm. No es lo que esperaba, y ahora veo que simplifiqué demasiado mi clase 'Transfer'. ¿Puedes ver los cambios que hice en la clase 'Transfer'? Esto se parece a mi código un poco más de cerca. Las propiedades están expuestas en la clase, pero lo hice principalmente para poder probar la condición de excepción. ¿Es lo que estaba tratando de hacer de la manera incorrecta? –

+0

@JeremyHolovacs He actualizado mi respuesta. –

+0

Hmm. Lo que pensé que sería simple parece ser menos. Déjame preguntarte: ¿tendría más sentido crear una clase de prueba que herede de 'Transferir' y anular el método para fines de prueba? Tal vez burlarse no es la tecnología adecuada para usar en este escenario, y estoy tratando de meter una clavija cuadrada en un agujero redondo. –

2

creo que esto es lo que quiere, ya probado este código y funciona

Las herramientas utilizadas son: (todas estas herramientas se pueden descargar como paquetes Nuget)

http://fluentassertions.codeplex.com/

http://autofixture.codeplex.com/

http://code.google.com/p/moq/

https://nuget.org/packages/AutoFixture.AutoMoq

var fixture = new Fixture().Customize(new AutoMoqCustomization()); 
var myInterface = fixture.Freeze<Mock<IFileConnection>>(); 

var sut = fixture.CreateAnonymous<Transfer>(); 

myInterface.Setup(x => x.Get(It.IsAny<string>(), It.IsAny<string>())) 
     .Throws<System.IO.IOException>(); 

sut.Invoking(x => 
     x.TransferFiles(
      myInterface.Object, 
      It.IsAny<string>(), 
      It.IsAny<string>() 
     )) 
     .ShouldThrow<System.IO.IOException>(); 

Editado:

Me explico:

Cuando se escribe una prueba, debe saber exactamente lo que desea probar, esto se llama: "sujeto sometido a prueba (SUT) ", si mi entendimiento es correcto, en este caso su SUT es: Transfer

Por lo tanto, con esto en mente, no debe burlarse de su SUT, si sustituye su SUT, entonces no estaría realmente probando el código real

Cuando su SUT tiene dependencias externas (muy comunes), entonces necesita sustituirlas para probar en aislamiento su SUT. Cuando digo sustituto me refiero a utilizar un simulacro, simulado, simulacro, etc. según sus necesidades

En este caso, su dependencia externa es IFileConnection, por lo que necesita crear simulacro para esta dependencia y configurarla para lanzar la excepción , a continuación, sólo llame a su verdadero método SUT y afirman que su método maneja la excepción como se esperaba

  • var fixture = new Fixture().Customize(new AutoMoqCustomization());: este Linie inicializa un nuevo objeto Fixture (biblioteca Autofixture), este objeto se utiliza para crear SUT de sin necesidad de tener explícitamente preocuparse por los parámetros del constructor, ya que se crean automáticamente o se burlan, en este caso usando Moq

  • var myInterface = fixture.Freeze<Mock<IFileConnection>>();: Esto congela la dependencia IFileConnection. Congelar significa que Autofixture usará siempre esta dependencia cuando se le pida, como un singleton por simplicidad.Pero la parte interesante es que estamos creando una maqueta de esta dependencia, puede utilizar todos los métodos Moq, ya que este es un simple objeto Moq

  • var sut = fixture.CreateAnonymous<Transfer>();: Aquí AutoFixture está creando el SUT para nosotros

  • myInterface.Setup(x => x.Get(It.IsAny<string>(), It.IsAny<string>())).Throws<System.IO.IOException>(); Aquí está configurando la dependencia a lanzar una excepción cuando se llama al método Get, el resto de los métodos de esta interfaz no están siendo configurado, por lo tanto, si se intenta acceder a ellos obtendrá una excepción inesperada

  • sut.Invoking(x => x.TransferFiles(myInterface.Object, It.IsAny<string>(), It.IsAny<string>())).ShouldThrow<System.IO.IOException>(); : Y finalmente, el tiempo para t est el SUT, esta línea utiliza la biblioteca FluenAssertions, y que sólo se llama al método verdadero TransferFilesdel IVU y como parámetros que recibe el IFileConnection burlado por lo que cada vez que se llama a la IFileConnection.Get en el flujo normal de su método SUT TransferFiles, al ser burlado objeto invocará lanzando la excepción configurada y este es el momento de afirmar que su SUT maneja correctamente la excepción, en este caso, solo estoy asegurando que la excepción fue lanzada usando el ShouldThrow<System.IO.IOException>() (de la biblioteca FluentAsertions)

Referencias recomendadas:

http://martinfowler.com/articles/mocksArentStubs.html

http://misko.hevery.com/code-reviewers-guide/

http://misko.hevery.com/presentations/

http://www.youtube.com/watch?v=wEhu57pih5w&feature=player_embedded

http://www.youtube.com/watch?v=RlfLCWKxHJ0&feature=player_embedded

+0

No creo que esto haga lo que necesito que haga. Necesito arrojar la excepción en el medio del método 'Transfer.TransferFiles()' permitiendo que la llamada al método 'Transfer.TransferFiles()' invoque la excepción en el método 'GetFile()'. Creo (si estoy leyendo esto bien) esto inyecta el 'IFileConnection' en el tiempo de ejecución, que se escribiría. ¿Me equivoco? –

Cuestiones relacionadas