2009-12-30 19 views
22

Al usar Moq, tengo un problema muy extraño en el que la configuración en un simulacro solo parece funcionar si el método que estoy configurando es público. No sé si esto es un error Moq o si simplemente tengo esto mal (novato para Moq). Aquí es el caso de prueba:Verificación de un método se llamó

public class TestClass 
{ 
    public string Say() 
    { 
     return Hello(); 
    } 

    internal virtual string Hello() 
    { 
     return ""; 
    } 
} 

[TestMethod] 
public void Say_WhenPublic_CallsHello() 
{ 
    Mock<TestClass> mock = new Mock<TestClass>(); 
    mock.Setup(x => x.Hello()).Returns("Hello World"); 

    string result = mock.Object.Say(); 
    mock.Verify(x => x.Hello(), Times.Exactly(1)); 
    Assert.AreEqual("Hello World", result);  
} 

Qué falla con este mensaje:

Say_WhenPublic_CallsHello fallidos: Moq.MockException: Invocación no se realizó en los simulacros de 1 Tiempos: x => x.Hello () en Moq.Mock.ThrowVerifyException (IProxyCall se esperaba, la expresión expresión, Times veces) ...

Si hago el método público Hola como esta, se pasa la prueba. Cuál es el problema aquí?

public virtual string Hello() 
{ 
    return ""; 
} 

¡Gracias de antemano!

+1

Votando esto porque el comportamiento ya no está presente en las versiones actuales de Moq. Ahora es capaz de manejar propiedades y métodos no virtuales que son internos. – krillgar

+2

En lugar de rechazarlo (y cualquier respuesta asociada), ¿qué le parece proporcionar el número de versión de Moq que introdujo esta función y haremos las modificaciones correspondientes? Votar hacia abajo realmente no logra nada. La Q y la A siguen siendo válidas para las versiones de Moq que no tienen esta característica. –

Respuesta

18

La prueba falla cuando Hello() es interno porque Moq no puede proporcionar una anulación del método en este caso. Esto significa que se ejecutará la implementación interna de Hello(), en lugar de la versión de prueba, lo que hará que falle el Verify().

Por cierto, lo que está haciendo aquí no tiene sentido en el contexto de una prueba de unidad. A una prueba unitaria no debería importarle que Say() llame a un método interno Hello(). Esta es la implementación interna de su ensamblado y no una preocupación de consumir código.

+1

+1 Además, las pruebas unitarias deben escribirse en una biblioteca diferente a la del sistema bajo prueba. Si se hubiera hecho esto, el código ni siquiera se habría compilado. –

+3

@Mark - verdadero, aunque las partes internas se pueden hacer visibles a otros ensamblados utilizando el atributo InternalVisibleTo –

+0

@Mark - Esa es una afirmación obvia - lo que significa que, por supuesto, debe escribir sus pruebas en una biblioteca diferente. Este código fue diseñado para proporcionar un ejemplo simple. ¿Por qué debería pasar por el esfuerzo adicional de separar estos dos en clases separadas para hacer un punto simple? – jle

10

No sé lo suficiente acerca de cómo funciona esto debajo de las sábanas para darle una respuesta técnica sobre por qué ese es el comportamiento, pero creo que puedo ayudarlo con su confusión.

En su ejemplo, está llamando a un método Say(), y está devolviendo el texto esperado. Su expectativa debe no imponer una implementación particular de Say(), es decir, que llama a un método interno llamado Hello() para devolver esa cadena. Es por eso que no pasa la verificación, y también por qué la cadena devuelta es "", es decir, se ha llamado a la implementación real de Hello().

Al hacer que el método Hello sea público, parece que esto ha permitido a Moq interceptar la llamada y utilizar su implementación. Por lo tanto, en este caso la prueba parece pasar. Sin embargo, en este escenario no ha logrado realmente nada útil, porque su prueba dice que cuando llama a Say() el resultado es "Hello World" cuando en realidad el resultado es "".

he reescrito el ejemplo para mostrar cómo yo esperaría Moq a utilizar (no necesariamente definitivo, pero es de esperar clara.

public interface IHelloProvider 
{ 
    string Hello(); 
} 

public class TestClass 
{ 
    private readonly IHelloProvider _provider; 

    public TestClass(IHelloProvider provider) 
    { 
     _provider = provider; 
    } 

    public string Say() 
    { 
     return _provider.Hello(); 
    } 
} 

[TestMethod] 
public void WhenSayCallsHelloProviderAndReturnsResult() 
{ 
    //Given 
    Mock<IHelloProvider> mock = new Mock<IHelloProvider>(); 
    TestClass concrete = new TestClass(mock.Object); 
    //Expect 
    mock.Setup(x => x.Hello()).Returns("Hello World"); 
    //When 
    string result = concrete.Say(); 
    //Then 
    mock.Verify(x => x.Hello(), Times.Exactly(1)); 
    Assert.AreEqual("Hello World", result); 
} 

En mi ejemplo, he introducido una interfaz a un IHelloProvider. Se le note que no hay implementación de IHelloProvider. Esto es el corazón de lo que estamos tratando de lograr usando una solución de burla.

Estamos tratando de probar una clase (TestClass), que depende de algo externo (IHelloProvider). Si está utilizando Test Driven Development, probablemente no haya escrito un IHelloProvider ye t, pero usted sabe que necesitará uno en algún momento. Sin embargo, en primer lugar, desea que TestClass funcione, en lugar de distraerse. O tal vez IHelloProvider usa una base de datos, o un archivo plano, o es difícil de configurar.

Incluso si tiene un IHelloProvider totalmente funcional, aún está tratando de probar el comportamiento de TestClass, por lo que usar un HelloProvider concreto puede hacer que su prueba sea más propensa a fallar, por ejemplo, si hay un cambio en el comportamiento de HelloProvider, no desea tener que cambiar las pruebas de cada clase que lo usa, solo desea cambiar las pruebas de HelloProvider.

Volviendo al código, ahora tenemos una clase TestClass, que depende de una interfaz IHelloProvider, cuya implementación se proporciona en tiempo de construcción (esto es inyección de dependencia).

El comportamiento de Say() es que llama al método Hello() en IHelloProvider.

Si mira hacia atrás en la prueba, hemos creado un objeto TestClass real, ya que en realidad queremos probar el código que hemos escrito. Creamos un Mock IHelloProvider, y dijimos que esperamos que tenga su método llamado Hello(), y cuando lo haga, devuelva la cadena "Hello World".

Luego llamamos a Say() y verificamos los resultados como antes.

Lo importante es darse cuenta de que no nos interesa el comportamiento de IHelloProvider, por lo que podemos simularlo para facilitar las pruebas. Estamos interesados ​​en el comportamiento de TestClass, por lo que hemos creado un TestClass real, no un simulacro, para que podamos probar su comportamiento real.

Espero que esto haya ayudado a aclarar lo que está pasando.

+1

+1 - Un concepto importante para recordar (como usted señaló) es que no se burla de la clase bajo prueba, se burla de sus dependencias. –

+0

@Modan - Gracias por pasar tanto tiempo en su respuesta. Sin embargo, debería haber sido más claro en mi post en cuanto a mi objetivo final. Estaba explorando el concepto de simulacros parciales usando Moq y estaba desconcertado sobre por qué no funciona una instalación en un método interno de una clase. Verá, estoy en el medio de hacer algunos cambios a algún código heredado y mi capacidad para cambiar este código en su totalidad es limitada. Lo que pensé que podía hacer es burlarme solo de los métodos de mi sujeto bajo prueba que no pueden ser ejecutados en una prueba unitaria (como golpear una base de datos). Gracias de nuevo. – jle

+0

"Lo que pensé que podía hacer es burlarme solo de los métodos de mi sujeto bajo prueba que no se pueden ejecutar en una prueba unitaria (como tocar una base de datos" En este escenario, aconsejaría dividir ese código en una clase separada como Lo hice en mi ejemplo. Es útil (quizás incluso esencial) mantener el código que no se puede probar fácilmente separado del código comprobable. Si no lo hace, es probable que termine con alguna lógica comercial eso es difícil/imposible de probar debido a su estructura de código. – Modan

6

Moq no realiza burlas parciales y solo puede simular métodos o interfaces virtuales públicos. Cuando creas un Mock estás creando un T completamente nuevo y eliminando toda implementación de cualquier método virtual público.

Mi sugerencia sería concentrarse en la prueba de la superficie pública de sus objetos y no en sus partes internas. Sus internos recibirán cobertura. Solo asegúrese de que tenga una idea clara de cuál es su clase objetivo y no simule que (en la mayoría de los casos).

La burla parcial solo es útil cuando se desea probar la funcionalidad de una clase abstracta con implementaciones simuladas de métodos abstractos. Si no está haciendo esto, es probable que no obtenga muchos beneficios al hacer un simulacro parcial.

Si esto es no una clase abstracta, querrá centrarse más en el enfoque que sugiere Modan, lo que quiere decir que debe burlarse de las dependencias en lugar de su propia clase de destino. Todo esto se reduce a la regla no se burle de lo que está probando.

+0

Esto ya no es cierto. Moq puede hacer métodos internos que ahora no son virtuales. – krillgar

Cuestiones relacionadas