¿Qué debe hacer con las pruebas unitarias?

110

Estoy recién salido de la universidad y estoy comenzando la universidad en algún lugar la próxima semana. Hemos visto pruebas unitarias, pero no las usamos mucho; y todos hablan de ellos, así que pensé que tal vez debería hacer algo.

El problema es que no sé qué probar. ¿Debo probar el caso común? El caso de borde? ¿Cómo sé que una función está adecuadamente cubierta?

Siempre tengo la terrible sensación de que, si bien una prueba probará que una función funciona en un caso determinado, es completamente inútil demostrar que la función funciona, punto.

    
pregunta zneak 03.09.2010 - 18:28
fuente

7 respuestas

104

Mi filosofía personal ha sido hasta ahora:

  1. Prueba el caso común de todo lo que puedas. Esto le dirá cuándo se rompe ese código después de realizar algún cambio (que, en mi opinión, es el mayor beneficio de las pruebas unitarias automatizadas).
  2. Pruebe los casos límite de algunos códigos inusualmente complejos que cree que probablemente tendrán errores.
  3. Cada vez que encuentre un error, escriba un caso de prueba para cubrirlo antes de solucionarlo
  4. Agregue pruebas de caso extremo a código menos crítico cada vez que alguien tenga tiempo para matar.
respondido por el Fishtoaster 03.09.2010 - 18:47
fuente
58

Entre la gran cantidad de respuestas, por lo tanto, nadie ha tratado sobre partición de equivalencia y análisis de valor de límite , consideraciones vitales en la respuesta a la pregunta en cuestión. Todas las demás respuestas, aunque son útiles, son cualitativas, pero es posible, y preferible, ser cuantitativas aquí. @fishtoaster proporciona algunas pautas concretas, solo mirando bajo las coberturas de la cuantificación de prueba, pero la partición de equivalencia y el análisis del valor límite nos permiten hacerlo mejor.

En partición de equivalencia , divide el conjunto de todas las entradas posibles en grupos según los resultados esperados. Cualquier entrada de un grupo producirá resultados equivalentes, por lo que dichos grupos se denominan clases de equivalencia . (Tenga en cuenta que los resultados equivalentes no significa no resultados idénticos).

Como ejemplo simple, considere un programa que debería transformar los caracteres ASCII en minúsculas en caracteres en mayúsculas. Otros personajes deben sufrir una transformación de identidad, es decir, permanecer sin cambios. Aquí hay un posible desglose en clases de equivalencia:

| # |  Equivalence class    | Input        | Output       | # test cases |
+------------------------------------------------------------------------+
| 1 | Lowercase letter      | a - z        | A - Z        | 26           |
| 2 | Uppercase letter      | A - Z        | A - Z        | 26           |
| 3 | Non-alphabetic chars  | 0-9!@#,/"... | 0-9!@#,/"... | 42           |
| 4 | Non-printable chars   | ^C,^S,TAB... | ^C,^S,TAB... | 34           |

La última columna informa el número de casos de prueba si los enumera todos. Técnicamente, según la regla 1 de @ fishtoaster, incluiría 52 casos de prueba, todos los que corresponden a las dos primeras filas mencionadas anteriormente corresponden al "caso común". La regla 2 de @ fishtoaster agregaría también algunas o todas las filas 3 y 4 anteriores. Pero con la partición de equivalencia es suficiente probar cualquier caso de prueba en cada clase de equivalencia. Si selecciona "a" o "g" o "w", está probando la misma ruta de código. Por lo tanto, tiene un total de 4 casos de prueba en lugar de 52+.

El análisis del valor límite recomienda un ligero refinamiento: esencialmente sugiere que no todos los miembros de una clase de equivalencia son, bueno, equivalentes. Es decir, los valores en los límites también deben considerarse dignos de un caso de prueba por derecho propio. (Una justificación fácil para esto es el infame error off-by-one !) Por lo tanto, para cada equivalencia clase podría tener 3 entradas de prueba. Mirando el dominio de entrada anterior, y con un poco de conocimiento de los valores ASCII, puedo encontrar estas entradas de caso de prueba:

| # | Input                | # test cases |
| 1 | a, w, z              | 3            |
| 2 | A, E, Z              | 3            |
| 3 | 0, 5, 9, !, @, *, ~  | 7            |
| 4 | nul, esc, space, del | 4            |

(Tan pronto como obtenga más de 3 valores de límite, eso sugiere que tal vez quiera replantearse las delineaciones de su clase de equivalencia original, pero esto fue lo suficientemente simple como para que no volviera a revisarlas). Por lo tanto, el análisis del valor de límite nos lleva a hasta solo 17 casos de prueba, con una alta confianza en la cobertura completa, en comparación con 128 casos de prueba para realizar pruebas exhaustivas. (¡Sin mencionar que la combinatoria dicta que las pruebas exhaustivas son simplemente inviables para cualquier aplicación del mundo real!)

    
respondido por el Michael Sorens 21.11.2013 - 23:32
fuente
17

Probablemente mi opinión no es demasiado popular. Pero te sugiero que seas económico con las pruebas unitarias. Si tiene demasiadas pruebas unitarias, puede fácilmente pasar la mitad de su tiempo o más con el mantenimiento de las pruebas en lugar de la codificación real.

Le sugiero que escriba pruebas para las cosas que tiene un mal presentimiento en su estómago o cosas que son muy importantes y / o elementales. Las pruebas de unidad IMHO no son un reemplazo para una buena ingeniería y codificación defensiva. Actualmente trabajo en un proyecto que es más o menos inusitado. Es realmente estable pero un dolor para refactorizar. De hecho, nadie ha tocado este código en un año y la pila de software en la que se basa tiene 4 años. ¿Por qué? Debido a que está lleno de pruebas unitarias, para ser precisos: Pruebas unitarias y pruebas de integración automatizadas. (¿Alguna vez escuchó hablar de pepino y similares?) Y aquí está la mejor parte: esta (aún) pieza inusual de software ha sido desarrollada por una compañía cuyos empleados son pioneros en la escena de desarrollo impulsado por pruebas. : D

Así que mi sugerencia es:

  • Comienza a escribir pruebas después de que hayas desarrollado el esqueleto básico, de lo contrario, la refactorización puede ser dolorosa. Como desarrollador que desarrolla para otros, nunca obtiene los requisitos desde el principio.

  • Asegúrese de que sus pruebas de unidad puedan realizarse rápidamente. Si tiene pruebas de integración (como el pepino) está bien si demoran un poco más. Pero las pruebas de larga duración no son divertidas, créeme. (La gente olvida todas las razones por las que C ++ se ha vuelto menos popular ...)

  • Deje este material TDD a los expertos en TDD.

  • Y sí, a veces te concentras en los casos de borde, a veces en los casos comunes, dependiendo de dónde esperes lo inesperado. Aunque si siempre esperas lo inesperado, deberías repensar tu flujo de trabajo y disciplina. ;-)

respondido por el Philip 21.08.2011 - 15:49
fuente
8

Si está probando primero con Test Driven Development, entonces su cobertura aumentará en el rango del 90% o más, porque no agregará funcionalidad sin antes escribir una prueba de unidad que falla.

Si está agregando pruebas después del hecho, no puedo recomendar lo suficiente que obtenga una copia de Trabajando de manera efectiva con el código heredado en Michael Feathers y observe algunas de las técnicas para agregar pruebas a su código y las formas de refactorizar su código. para que sea más comprobable.

    
respondido por el Paddyslacker 03.09.2010 - 20:00
fuente
5

Si comienza a seguir las prácticas de Test Driven Development , le ordenarán guía . A través del proceso y sabiendo qué probar vendrá naturalmente. Algunos lugares para comenzar:

Las pruebas son lo primero

Nunca, nunca escriba código antes de escribir las pruebas. Consulte Red-Green-Refactor-Repeat para obtener una explicación.

Escriba pruebas de regresión

Siempre que encuentre un error, escriba un testcase y asegúrese de que falle . A menos que pueda reproducir un error a través de un testcase fallido, realmente no lo ha encontrado.

Red-Green-Refactor-Repeat

Red : Comience escribiendo la prueba más básica para el comportamiento que está intentando implementar. Piense en este paso al escribir algún código de ejemplo que utilice la clase o función en la que está trabajando. Asegúrese de que compila / no tiene errores de sintaxis y que falla . Esto debería ser obvio: no has escrito ningún código, por lo que debe fallar, ¿verdad? Lo importante que debes aprender aquí es que, a menos que veas que la prueba falla al menos una vez, nunca puedes estar seguro de que si pasa, lo hace debido a algo que has hecho debido a una razón falsa.

Verde : escribe el código más simple y estúpido que realmente hace que la prueba pase. No trates de ser inteligente. Incluso si ve que hay un caso de borde obvio pero la prueba toma en cuenta, no escriba el código para manejarlo (pero no se olvide del caso de borde: lo necesitará más adelante) . La idea es que cada pieza de código que escriba, cada if , cada try: ... except: ... se justifique con un caso de prueba. El código no tiene que ser elegante, rápido u optimizado. Solo quieres que la prueba pase.

Refactor : limpie su código, obtenga los nombres de los métodos correctos. A ver si la prueba sigue pasando. Optimizar. Ejecutar la prueba de nuevo.

Repetir : Recuerda el caso de borde que la prueba no cubrió, ¿verdad? Entonces, ahora es su gran momento. Escriba un caso de prueba que cubra esa situación, mírelo fallar, escriba un código, vea cómo pasa, refactorice.

Pruebe su código

Estás trabajando en un fragmento de código específico, y esto es exactamente lo que quieres probar. Esto significa que no debe probar las funciones de la biblioteca, la biblioteca estándar o su compilador. Además, trata de evitar probar el "mundo". Esto incluye: llamar a API web externas, algunas cosas de uso intensivo de la base de datos, etc. Siempre que pueda intentar simularlo (cree un objeto que siga la misma interfaz, pero que devuelva datos estáticos y predefinidos).

    
respondido por el Ryszard Szopa 13.10.2010 - 02:08
fuente
3

Para las pruebas unitarias, comience con las pruebas de que hace lo que está diseñado para hacer. Ese debería ser el primer caso que escribas. Si parte del diseño es "debería lanzar una excepción si pasas en la chatarra", prueba que también es parte del diseño.

Comienza con eso. A medida que adquiera experiencia para realizar la prueba más básica, empezará a saber si eso es suficiente o no, y comenzará a ver otros aspectos de su código que necesitan pruebas.

    
respondido por el Bryan Oakley 22.08.2011 - 00:25
fuente
0

La respuesta del stock es "probar todo lo que pueda romperse" .

¿Qué es demasiado simple para romper? Campos de datos, accesores de propiedades con muerte cerebral y gastos indirectos similares. Cualquier otra cosa probablemente implemente una parte identificable de un requisito, y puede beneficiarse de ser probado.

Por supuesto, su kilometraje y las prácticas de su entorno de trabajo pueden variar.

    
respondido por el Jeffrey Hantin 25.09.2010 - 00:01
fuente

Lea otras preguntas en las etiquetas