2008-09-06 10 views
14

Hace un tiempo leí el artículo Mocks Aren't Stubs por Martin Fowler y tengo que admitir que soy un poco de miedo de dependencias externas con respecto a la complejidad añadida así que me gustaría preguntar:¿Son los burdos mejores que los talones?

¿Cuál es el mejor método para utilizar cuando ¿examen de la unidad?

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

Respuesta

12

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

  1. Si las clases falsas pueden hacer el trabajo, acompáñelos.
  2. Si necesita una interfaz con varios métodos para burlarse, vaya con un marco simulado.

evitar el uso de burla siempre porque hacen pruebas frágil. Sus pruebas ahora tienen un conocimiento intrincado de los métodos invocados por la implementación, si la interfaz simulada o su implementación cambian ... sus pruebas se rompen. Esto es malo porque pasarás más tiempo haciendo que tus pruebas se ejecuten en lugar de solo ejecutar tu SUT. Las pruebas no deben ser inapropiadamente íntimas con la implementación.
Así que usa tu mejor juicio ... Prefiero burlas cuando me ayudará a ahorrarme la escritura, actualizando una clase falsa con n >> 3 métodos.

actualización Epílogo/Deliberación:
(Gracias a Toran Billups por ejemplo de una prueba con imitaciones Ver más abajo.)
Hola Doug, Bueno, creo que hemos trascendido en otra guerra santa - TDDers Classic vs TDDers con imitaciones . Creo que soy del primero.

  • Si estoy en la prueba # 101 Test_ExportProductList y encuentro que necesito agregar un nuevo parámetro a IProductService.GetProducts(). Yo hago eso para obtener esta prueba verde. Utilizo una herramienta de refactorización para actualizar todas las demás referencias. Ahora descubro que todas las pruebas de simulacros que llaman a este miembro ahora 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 está roto? Más bien, las pruebas están rotas. Estoy a favor de una falla de prueba = 1 lugar para reparar. Burlarse de las frecuencias va en contra de eso. ¿Los stubs serían mejores? Si tuviera un fake_class.GetProducts() .. sure Un lugar para cambiar en lugar de la cirugía de escopeta en múltiples llamadas a Espera. Al final, es una cuestión de estilo ... si tuvieras un método de utilidad común MockHelper.SetupExpectForGetProducts(), eso también sería suficiente ... pero verás que esto no es común.
  • Si coloca una franja blanca en el nombre de la prueba, la prueba es difícil de leer. Gran parte del código de plomería para el marco simulado oculta la prueba real que se está realizando.
  • se requiere para aprender este sabor particular de un marco de burla
1

Lea la discusión de Luke Kanies sobre exactamente esta pregunta en this blog post. Él hace referencia al a post from Jay Fields, que incluso sugiere que usar [un equivalente de ruby's/mocha's] stub_everything es preferible para que las pruebas sean más robustas. Para citar las últimas palabras de Fields: "Mocha hace que sea tan fácil definir un simulacro como definir un talón, pero eso no significa que siempre deba preferir los simulacros. De hecho, generalmente prefiero los talones y uso simulaciones cuando sea necesario. "

2

Sólo depende de qué tipo de pruebas que está haciendo. Si realiza pruebas basadas en el comportamiento, es posible que desee un simulacro dinámico para poder verificar que se produce alguna interacción con su dependencia. Pero si está haciendo una prueba basada en el estado, podría querer un stub para verificar los valores/etc.

Por ejemplo, en la prueba siguiente, observa que anulo la vista para poder verificar que el valor de la propiedad esté establecido (basado en estado) pruebas). Luego creo un simulacro dinámico de la clase de servicio para asegurarme de que se llama un método específico durante la prueba (prueba basada en la 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 
2

Lo mejor es usar una combinación, y tendrá que usar su propio juicio. Estas son las pautas que uso:

  • Si hacer una llamada al código externo es parte del comportamiento esperado (hacia afuera) de su código, esto debe ser probado. Usa un simulacro
  • Si la llamada es realmente un detalle de implementación que al mundo exterior no le importa, prefiera los stubs. Sin embargo:
  • Si te preocupa que las implementaciones posteriores del código probado puedan pasar accidentalmente por tus talones, y quieres darte cuenta si eso sucede, usa un simulacro. Estás uniendo tu prueba a tu código, pero es por el solo hecho de darte cuenta de que tu stub ya no es suficiente y que tu prueba debe volver a funcionar.

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

Y de nuevo, este es un olor a código y un último recurso. Si encuentra que necesita hacer esto a menudo, intente repensar la forma en que escribe sus pruebas.

7

Generalmente prefiero usar simulaciones debido a las expectativas. Cuando llama a un método en un stub que devuelve un valor, por lo general solo le devuelve un valor. Pero cuando llamas a un método en un simulacro, no solo devuelve un valor, sino que también impone la expectativa de que configuraste que incluso se llamó al método 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, básicamente está diciendo "Si este método no se llama, algo salió mal". Y lo opuesto es cierto, si llamas a un método en un simulacro y no estableces una expectativa, emitirá una excepción, básicamente diciendo "Oye, ¿qué haces llamando a este método cuando no lo esperabas?".

A veces no desea expectativas en cada método que está llamando, por lo que algunos frameworks burlones permitirán burlas "parciales" que son como híbridos simulados, ya que solo se cumplen las expectativas que establezca, y cualquier otro La llamada a método se trata más como un apéndice porque solo devuelve un valor.

Sin embargo, un lugar válido para usar talones que puedo pensar en la parte superior es cuando se introducen las pruebas en el código heredado. A veces es más fácil crear un apéndice subclasificando la clase que estás probando que refacturando todo para hacer que la burla sea fácil o incluso posible.

Y a esto ...

Evite usar simulaciones 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 cambia ... sus pruebas se rompen. Así que use su mejor juicio ... <

... Digo que si mi interfaz cambia, mis pruebas se rompen mejor. Porque el objetivo de las pruebas unitarias es que prueban con precisión mi código tal como existe en este momento.

2

No importa Statist vs. Interaction. Piensa en los Roles y las Relaciones. Si un objeto colabora con un vecino para hacer su trabajo, entonces esa relación (como se expresa en una interfaz) es un candidato para probar usando simulaciones. Si un objeto es un objeto de valor simple con un poco de comportamiento, entonces pruébelo directamente. No veo el sentido de escribir simulacros (o incluso talones) a mano. Así es como todos comenzamos y refactorizamos lejos de eso.

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

Cuestiones relacionadas