2010-07-15 11 views
156

Soy bastante nuevo en el mundo de las pruebas unitarias, y esta semana decidí agregar cobertura de prueba para mi aplicación existente.¿Nuevo en pruebas unitarias, cómo escribir grandes pruebas?

Esta es una gran tarea, sobre todo por la cantidad de clases para evaluar, pero también porque escribir pruebas es nuevo para mí.

Ya he escrito pruebas para varias clases, pero ahora me pregunto si lo estoy haciendo bien.

Cuando estoy escribiendo pruebas para un método, tengo la sensación de volver a escribir por segunda vez lo que ya escribí en el método mismo.
Mis pruebas parecen tan estrictamente relacionadas con el método (probar todas las rutas de códigos, esperando que algunos métodos internos se llamen varias veces, con ciertos argumentos), que parece que si alguna vez refactorizo ​​el método, las pruebas fallarán incluso si el comportamiento final del método no cambió.

Esto es solo una sensación, y como dije antes, no tengo experiencia en las pruebas. Si algunos probadores más experimentados pudieran darme consejos sobre cómo escribir pruebas excelentes para una aplicación existente, eso sería muy apreciado.

Editar: Me gustaría agradecer a Stack Overflow, tuve excelentes entradas en menos de 15 minutos que respondieron más horas de lectura en línea que acabo de hacer.

Respuesta

112

Mis pruebas parecen tan estrictamente relacionadas con el método (probando todas las rutas de códigos, esperando llamar algunos métodos internos varias veces, con ciertos argumentos), que parece que si alguna vez refactorizo ​​el método, las pruebas fallarán aunque el comportamiento final del método no haya cambiado.

Creo que lo estás haciendo mal.

una prueba de unidad debe:

  • prueba un método
  • proporcionan algunos argumentos específicos para ese método
  • prueba de que el resultado es el esperado

No debe observar el interior del método para ver lo que está haciendo, por lo que cambiar las partes internas no debe hacer que la prueba falle. No debe probar directamente que se están llamando a métodos privados. Si está interesado en saber si su código privado está siendo probado, use una herramienta de cobertura de código. Pero no se obsesione con esto: el 100% de cobertura no es un requisito.

Si su método llama a métodos públicos en otras clases, y estas llamadas están garantizadas por su interfaz, entonces puede probar que estas llamadas se realizan mediante el uso de un marco de burla.

No debe usar el método en sí mismo (o cualquiera de los códigos internos que utiliza) para generar dinámicamente el resultado esperado. El resultado esperado debe estar codificado en su caso de prueba para que no cambie cuando la implementación cambie. He aquí un ejemplo simplificado de lo que es una prueba de la unidad debe hacer:

testAdd() 
{ 
    int x = 5; 
    int y = -2; 
    int expectedResult = 3; 

    Calculator calculator = new Calculator(); 
    int actualResult = calculator.Add(x, y); 
    Assert.AreEqual(expectedResult, actualResult); 
} 

Tenga en cuenta que el resultado de cómo se calcula no se comprueba - sólo que el resultado es correcto. Siga agregando más y más casos de prueba simples como el anterior hasta que haya cubierto tantos escenarios como sea posible. Usa tu herramienta de cobertura de código para ver si te has perdido alguna ruta interesante.

+7

Muchas gracias, su respuesta fue la más completa. Ahora entiendo mejor para qué son realmente los objetos falsos: no necesito afirmar cada llamada a otros métodos, solo los relevantes. Tampoco necesito saber CÓMO se hacen las cosas, pero sí lo hacen correctamente. – pixelastic

+1

Respetuosamente pienso que lo estás haciendo mal. Las pruebas unitarias son sobre el flujo de ejecución de código (prueba de caja blanca). La prueba de caja negra (lo que sugiere) suele ser la técnica utilizada en las pruebas funcionales (sistema y prueba de integración). – Wes

14

Vale la pena señalar que las pruebas de unidad de ajuste retroactivo en el código existente es ahora más difícil que conducir la creación de ese código con las pruebas en primer lugar. Esa es una de las grandes preguntas al tratar con aplicaciones heredadas ... ¿cómo probar la unidad? Esto se ha hecho muchas veces antes (por lo que puede se cierren con una pregunta víctima), y la gente por lo general terminan aquí:

Moving existing code to Test Driven Development

Me segunda recomendación el libro de la respuesta aceptada, pero más allá de que hay más información vinculada en las respuestas allí.

+3

Si escribe las pruebas primero o segundo, ambas son buenas, pero al escribir las pruebas se asegura que su código sea comprobable para que pueda escribir las pruebas. Terminas pensando "cómo puedo probar esto", a menudo eso en sí mismo hace que se escriba mejor código. Reequipar casos de prueba siempre es un gran no-no. Muy duro. No es un problema de tiempo, es un problema de cantidad y capacidad de prueba. No puedo decirle a mi jefe en este momento y decir que quiero escribir casos de prueba para nuestras más de mil mesas y usos, es demasiado ahora, me tomaría un año, y algunas de las decisiones/lógica se olvidan. Así que no lo posponga demasiado: P –

+1

Es de suponer que la respuesta aceptada ha cambiado. Hay una respuesta de Linx que recomienda El arte de la prueba unitaria de Roy Osherove, http://www.manning.com/osherove/ – thelem

5

Intente escribir una Prueba unitaria antes de escribir el método que va a probar.

Eso definitivamente te obligará a pensar un poco diferente sobre cómo se están haciendo las cosas. No tendrá idea de cómo funcionará el método, solo lo que se supone que debe hacer.

Siempre debe probar los resultados del método, no cómo obtiene el método esos resultados.

+0

Sí, me encantaría poder hacer eso, excepto que los métodos ya están escritos. Solo quiero probarlos. Escribiré las pruebas antes de los métodos en el futuro, aunque. – pixelastic

+1

@pixelastic pretende que los métodos no han sido escritos? – committedandroider

10

No escriba pruebas para obtener una cobertura completa de su código. Escriba pruebas que garanticen sus requisitos. Puede descubrir rutas de código que son innecesarias. Por el contrario, si son necesarios, están allí para cumplir algún tipo de requisito; encuéntrelo y compruebe el requisito (no el camino).

Mantenga sus pruebas pequeñas: una prueba por requerimiento.

Más tarde, cuando necesite realizar un cambio (o escribir un nuevo código), primero intente escribir una prueba. Solo uno. Entonces habrá dado el primer paso en el desarrollo impulsado por pruebas.

+0

Gracias, tiene sentido tener solo pequeñas pruebas para pequeñas necesidades, una a la vez. Lección aprendida. – pixelastic

10

La prueba de la unidad se trata de la salida que obtiene de una función/método/aplicación. No importa en absoluto cómo se produce el resultado, solo importa que sea correcto. Por lo tanto, su enfoque de contar llamadas a métodos internos es incorrecto. Lo que suelo hacer es sentarme y escribir lo que debería devolver un método dado ciertos valores de entrada o un entorno determinado, luego escribir una prueba que compare el valor real devuelto con el que se me ocurrió.

+0

Gracias! Tenía la sensación de que lo estaba haciendo mal, pero tener a alguien realmente diciéndome es mejor. – pixelastic

3

se supone que las pruebas mejoran el mantenimiento. Si cambia un método y se rompe una prueba que puede, sea una buena cosa. Por otro lado, si miras tu método como una caja negra, no debería importar lo que hay dentro del método. El hecho es que necesita burlarse de algunas pruebas y, en esos casos, no puede tratar el método como una caja negra. Lo único que puede hacer es crear una prueba de integración: carga una instancia completamente instanciada del servicio bajo prueba y hace que funcione como en su aplicación. Entonces puedes tratarlo como una caja negra.

When I'm writing tests for a method, I have the feeling of rewriting a second time what I   
already wrote in the method itself. 
My tests just seems so tightly bound to the method (testing all codepath, expecting some  
inner methods to be called a number of times, with certain arguments), that it seems that 
if I ever refactor the method, the tests will fail even if the final behavior of the 
method did not change. 

Esto se debe a que está escribiendo sus pruebas después de haber escrito el código. Si lo hiciste al revés (escribió las pruebas primero) no se sentiría de esta manera.

+0

Gracias por el ejemplo de la caja negra, no lo he pensado así. Desearía haber descubierto pruebas unitarias anteriormente, pero desafortunadamente, ese no es el caso y estoy atascado con una aplicación _legacy_ para agregar pruebas.¿No hay forma de agregar pruebas a un proyecto existente sin que se sientan rotas? – pixelastic

+1

Escribir pruebas después es diferente a las pruebas de escritura anteriores, por lo que está atrapado con él. sin embargo, lo que puede hacer es configurar las pruebas para que fallen primero, y luego hacerlas pasar poniendo a prueba a su clase en .... haga algo así, poniendo su instancia bajo prueba después de que la prueba falla inicialmente . Lo mismo con los simulacros: al principio, el simulacro no tiene expectativas, y fallará porque el método bajo prueba hará algo con el simulacro, luego hará que la prueba pase. No me sorprendería si encuentra muchos errores de esta manera. – hvgotcodes

+0

también, sea realmente específico con sus expectativas. No afirme solo que la prueba devuelve un objeto, pruebe que el objeto tenga varios valores. Pruebe eso cuando se supone que un valor es nulo, eso es. También puede dividirlo un poco haciendo algunas refactorizaciones que tenía intención de hacer, después de agregar algunas pruebas. – hvgotcodes

3

Este es el mejor libro para las pruebas unitarias: http://www.manning.com/osherove/

Explica todas las mejores prácticas, hacer de, y para las pruebas de unidad no haga.

+0

Gracias, echaré un vistazo a eso. Compré el libro de Kent Beck sobre TDD, pero creo que primero necesito probar primero la cubierta de mi aplicación existente. – pixelastic

22

Para la prueba unitaria, encontré tanto el Test Driven (prueba primero, código segundo) y el código primero, prueba el segundo para ser extremadamente útil.

En lugar de escribir el código, escriba la prueba. Escriba el código y luego observe lo que PIENSA que el código debería estar haciendo. Piense en todos los usos previstos de la misma y luego escriba una prueba para cada uno. Las pruebas de escritura me parecen más rápidas pero más complicadas que la codificación misma. Las pruebas deben probar la intención. También pensando en las intenciones terminas encontrando casos de esquina en la fase de escritura de prueba. Y, por supuesto, mientras escribo las pruebas, puede encontrar que uno de los pocos usos causa un error (algo que a menudo encuentro, y estoy muy contento de que este error no haya corrompido los datos y no se hayan revisado).

Sin embargo, las pruebas son casi como dos veces la codificación. De hecho, tenía aplicaciones donde había más código de prueba (cantidad) que código de la aplicación. Un ejemplo fue una máquina de estado muy compleja. Tenía que asegurarme de que después de agregarle más lógica, todo el asunto siempre funcionó en todos los casos de uso anteriores. Y dado que esos casos fueron bastante difíciles de seguir mirando el código, terminé teniendo un conjunto de pruebas tan bueno para esta máquina que estaba seguro de que no se rompería incluso después de hacer cambios, y las pruebas salvan mi culo algunas veces . Y a medida que los usuarios o probadores encontraban errores con los casos de flujo o esquina no contabilizados, adivinen qué, se agregaron a las pruebas y nunca volvieron a ocurrir. Esto realmente les dio a los usuarios confianza en mi trabajo además de hacer que todo fuera súper estable. Y cuando tuvo que volver a escribirse por motivos de rendimiento, adivinen qué, funcionó como se esperaba en todas las entradas gracias a las pruebas.

Todos los ejemplos simples como function square(number) son geniales y todos, y probablemente sean malos candidatos para pasar muchas pruebas de tiempo. Los que hacen una lógica comercial importante, ahí es donde las pruebas son importantes. Pruebe los requisitos. No solo pruebe la tubería. Si los requisitos cambian, adivina qué, las pruebas también deben hacerlo.

Las pruebas no deberían probar literalmente esa función foo invocó la barra de funciones 3 veces. Eso está mal. Compruebe si el resultado y los efectos secundarios son correctos, no la mecánica interna.

+1

Una respuesta agradable, me dio la confianza de que escribir pruebas después del código aún puede ser útil y posible. – pixelastic

+1

Un perfecto ejemplo reciente. Tuve una función muy simple. Piénselo verdad, hace una cosa, falso hace otra. MUY SIMPLE. Tuve como 4 pruebas comprobando para asegurarme de que la función hace lo que pretende hacer. Cambio el comportamiento un poco. Ejecutar pruebas, POW un problema. Lo curioso es que al usar la aplicación el problema no se manifiesta, solo en un caso complejo que lo hace. El caso de prueba lo encontró y me ahorré horas de dolor de cabeza. –