Pregunta

Hace un tiempo leí el Los simulacros no son talones artículo de Martin Fowler y debo admitir que tengo un poco de miedo a las dependencias externas con respecto a la complejidad adicional, así que me gustaría preguntar:

¿Cuál es el mejor método a utilizar cuando se realizan pruebas unitarias?

¿Es mejor usar siempre un marco simulado para simular automáticamente las dependencias del método que se está probando, o preferiría usar mecanismos más simples como, por ejemplo, resguardos de prueba?

¿Fue útil?

Solución

Como dice el mantra: "Elija lo más simple que pueda funcionar".

  1. Si las clases falsas pueden hacer el trabajo, ve con ellas.
  2. Si necesita una interfaz con múltiples métodos para burlarse, opte por un marco simulado.

Evite el uso de simulacros siempre porque hacen que las pruebas sean frágiles.Sus pruebas ahora tienen un conocimiento complejo de los métodos llamados por la implementación, si la interfaz simulada o su implementación cambia...tus pruebas se rompen.Esto es malo porque dedicará más tiempo a ejecutar las pruebas en lugar de simplemente ejecutar el SUT. Las pruebas no deben tener una intimidad inapropiada con la implementación.
Así que usa tu mejor criterio...Prefiero las burlas cuando me ayudarán a ahorrarme escribir y actualizar una clase falsa con n>>3 métodos.

Actualizar Epílogo/Deliberación:
(Gracias a Toran Billups por ejemplo por una prueba simulada.Vea abajo)
Hola Doug, Bueno, creo que hemos trascendido a otra guerra santa: los TDDers clásicos contra los TDDers simulados.Creo que pertenezco a los primeros.

  • Si estoy en la prueba n.° 101 Test_ExportProductList y encuentro que necesito agregar un nuevo parámetro a IProductService.GetProducts().Hago que esta prueba salga verde.Utilizo una herramienta de refactorización para actualizar todas las demás referencias.Ahora encuentro que todas las pruebas simuladas que llaman a este miembro explotan.Luego tengo que volver atrás y actualizar todas estas pruebas: una pérdida de tiempo.¿Por qué falló ShouldPopulateProductsListOnViewLoadWhenPostBackIsFalse?¿Fue porque el código no funciona?Más bien las pruebas están rotas.favorezco el una prueba fallida = 1 lugar para arreglar.La frecuencia burlona va en contra de eso.¿Serían mejores los talones?Si tuviera un fake_class.GetProducts()..seguro Un lugar para cambiarse en lugar de una cirugía de escopeta en múltiples llamadas Expect.Al final es una cuestión de estilo..si tuviera un método de utilidad común MockHelper.SetupExpectForGetProducts(), eso también sería suficiente.pero verás que esto es poco común.
  • Si coloca una franja blanca sobre el nombre de la prueba, la prueba será difícil de leer.Gran cantidad de código de plomería para el marco simulado oculta la prueba real que se está realizando.
  • requiere que aprendas este sabor particular de un marco burlón

Otros consejos

Generalmente prefiero usar simulacros debido a las expectativas.Cuando llamas a un método en un código auxiliar que devuelve un valor, normalmente solo te devuelve un valor.Pero cuando llamas a un método en un simulacro, no solo devuelve un valor, sino que también refuerza la expectativa que configuraste de que el método fue llamado en primer lugar.En otras palabras, si configura una expectativa y luego no llama a ese método, se genera una excepción.Cuando establece una expectativa, esencialmente está diciendo "Si este método no se llama, algo salió mal". Y lo contrario es cierto, si llamas un método en un simulacro y no estableció una expectativa, lanzará una excepción, esencialmente diciendo "Oye, ¿qué estás haciendo llamando a este método cuando no lo esperabas"?

A veces no desea tener expectativas sobre cada método al que llama, por lo que algunos marcos simulados permitirán simulacros "parciales" que son como híbridos simulados/stub, en el sentido de que solo se aplican las expectativas que usted establece y se tratan todas las demás llamadas a métodos. más como un talón en el sentido de que simplemente devuelve un valor.

Sin embargo, un lugar válido para usar resguardos que se me ocurre desde el principio es cuando se introducen pruebas en el código heredado.A veces es más fácil crear un fragmento subclasificando la clase que estás probando que refactorizar todo para que la burla sea fácil o incluso posible.

Y a esto...

Evite siempre el uso de simulacros porque hacen que las pruebas sean frágiles.Sus pruebas ahora tienen un conocimiento complejo de los métodos llamados por la implementación, si la interfaz simulada cambia...tus pruebas se rompen.Así que usa tu mejor criterio...<

... Yo digo que si mi interfaz cambia, será mejor que mis pruebas se rompan.Porque el objetivo de las pruebas unitarias es que prueben con precisión mi código tal como existe en este momento.

Es mejor usar una combinación y tendrás que usar tu propio criterio.Estas son las pautas que utilizo:

  • Si realizar una llamada a un código externo es parte del comportamiento esperado (hacia afuera) de su código, esto debe probarse.Utilice una burla.
  • Si la llamada es realmente un detalle de implementación que al mundo exterior no le importa, prefiera los resguardos.Sin embargo:
  • Si le preocupa que implementaciones posteriores del código probado puedan accidentalmente pasar por alto sus códigos auxiliares y desea darse cuenta si eso sucede, use un simulacro.Está acoplando su prueba a su código, pero es para notar que su código auxiliar ya no es suficiente y su prueba necesita volver a funcionar.

El segundo tipo de burla es una especie de mal necesario.Realmente lo que está sucediendo aquí es que ya sea que uses un código auxiliar o un simulacro, en algunos casos tienes que acoplar a tu código más de lo que te gustaría.Cuando eso sucede, es mejor usar un simulacro que un código auxiliar solo porque sabrá cuándo se rompe ese acoplamiento y su código ya no está escrito de la manera que su prueba pensó que estaría.Probablemente sea mejor dejar un comentario en la prueba cuando hagas esto para que quien lo rompa sepa que su código no es incorrecto, sino la prueba.

Y nuevamente, esto es un olor a código y un último recurso.Si descubre que necesita hacer esto con frecuencia, intente reconsiderar la forma en que escribe sus pruebas.

Simplemente depende del tipo de prueba que estés realizando.Si está realizando pruebas basadas en el comportamiento, es posible que desee una simulación dinámica para poder verificar que se produce alguna interacción con su dependencia.Pero si está realizando pruebas basadas en el estado, es posible que desee un código auxiliar para verificar los valores, etc.

Por ejemplo, en la prueba siguiente, observará que elimino la vista para poder verificar que se haya establecido un valor de propiedad (prueba basada en estado).Luego creo una simulación dinámica de la clase de servicio para poder asegurarme de que se llame a un método específico durante la prueba (prueba basada en interacción/comportamiento).

<TestMethod()> _
Public Sub Should_Populate_Products_List_OnViewLoad_When_PostBack_Is_False()
    mMockery = New MockRepository()
    mView = DirectCast(mMockery.Stub(Of IProductView)(), IProductView)
    mProductService = DirectCast(mMockery.DynamicMock(Of IProductService)(), IProductService)
    mPresenter = New ProductPresenter(mView, mProductService)
    Dim ProductList As New List(Of Product)()
    ProductList.Add(New Product())
    Using mMockery.Record()
        SetupResult.For(mView.PageIsPostBack).Return(False)
        Expect.Call(mProductService.GetProducts()).Return(ProductList).Repeat.Once()
    End Using
    Using mMockery.Playback()
        mPresenter.OnViewLoad()
    End Using
    'Verify that we hit the service dependency during the method when postback is false
    Assert.AreEqual(1, mView.Products.Count)
    mMockery.VerifyAll()
End Sub

No importa Estatista vs.Interacción.Piense en los roles y las relaciones.Si un objeto colabora con un vecino para realizar su trabajo, entonces esa relación (tal como se expresa en una interfaz) es candidata para realizar pruebas mediante simulacros.Si un objeto es un objeto de valor simple con un poco de comportamiento, pruébelo directamente.No veo el sentido de escribir simulacros (o incluso resguardos) a mano.Así es como todos comenzamos y refactorizamos a partir de eso.

Para una discusión más larga, considere echar un vistazo a http://www.mockobjects.com/book

Lea la discusión de Luke Kanies sobre exactamente esta pregunta en esta publicación de blog.Él hace referencia una publicación de Jay campos lo que incluso sugiere que usar stub_everything [un equivalente a Ruby's/mocha's] es preferible para hacer las pruebas más sólidas.Para citar las últimas palabras de Fields:"Mocha hace que sea tan fácil definir un simulacro como definir un trozo, pero eso no significa que siempre debas preferir los simulacros.De hecho, generalmente prefiero los resguardos y uso simulacros cuando es necesario".

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top