it-swarm-es.com

¿Qué debe probar con las pruebas unitarias?

Recién salí de la universidad y comenzaré la universidad en algún lugar la próxima semana. Hemos visto pruebas unitarias, pero no las hemos usado mucho; y todos hablan de ellos, así que pensé que tal vez debería hacer algo.

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

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

128
zneak

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 que realice algún cambio (que, en mi opinión, es el mayor beneficio de las pruebas unitarias automatizadas).
  2. Pruebe los casos de Edge 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 arreglarlo
  4. Agregue pruebas Edge-case a un código menos crítico cuando alguien tenga tiempo de matar.
124
Fishtoaster

Entre la gran cantidad de respuestas hasta ahora, nadie ha tocado división de equivalencia y análisis de valor límite , consideraciones vitales en la respuesta a la pregunta en cuestión. Todas las otras respuestas, aunque útiles, son cualitativas, pero es posible, y preferible, ser cuantitativas aquí. @fishtoaster proporciona algunas pautas concretas, solo asomándose bajo las coberturas de la cuantificación de la prueba, pero la divisió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 arrojará resultados equivalentes, por lo que dichos grupos se denominan ¡clases de equivalencia. (Tenga en cuenta que los resultados equivalentes ¡no significan resultados idénticos.)

Como un ejemplo simple, considere un programa que debería transformar las letras minúsculas ASCII caracteres en mayúsculas. Otros caracteres deberían 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  | [email protected]#,/"... | [email protected]#,/"... | 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 de las dos primeras filas indicadas anteriormente se incluyen en el "caso común". La regla 2 de @ fishtoaster agregaría también algunas o todas las filas 3 y 4 anteriores. Pero con la prueba de partición de equivalencia, cualquier un caso de prueba en cada clase de equivalencia es suficiente. Si elige "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 clase de equivalencia podría tener 3 entradas de prueba. Mirando el dominio de entrada anterior, y con algún conocimiento de ASCII), podría 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 límite que sugieran que tal vez quiera repensar sus delimitaciones de clase de equivalencia originales, pero esto fue lo suficientemente simple como para que no volviera a revisarlos). Por lo tanto, el análisis del valor límite nos lleva a solo 17 casos de prueba, con una alta confianza de 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!)

68
Michael Sorens

Probablemente mi opinión no es muy popular. Pero le sugiero que sea económico con las pruebas unitarias. Si tiene demasiadas pruebas unitarias, puede terminar fácilmente pasando 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 cosas que tiene un mal presentimiento o cosas que son cruciales y/o elementales. Las pruebas unitarias de la OMI no son un reemplazo para una buena ingeniería y codificación defensiva. Actualmente trabajo en un proyecto que es más o menos inutilizable. 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é? Porque está abarrotado de pruebas unitarias, para ser precisos: pruebas unitarias y pruebas de integración automatizadas. (¿Alguna vez has oído hablar de pepino y cosas similares?) Y esta es la mejor parte: este (aún) inestimable software ha sido desarrollado por una compañía cuyos empleados son pioneros en la escena del desarrollo basado en pruebas. :RE

Entonces mi sugerencia es:

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

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

  • Deje estas cosas TDD a los expertos TDD.

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

20
Philip

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

Si está agregando pruebas después del hecho, entonces no puedo recomendar lo suficiente para que obtenga una copia de Trabajando efectivamente con el código heredado por Michael Feathers y eche un vistazo a algunos de los técnicas para agregar pruebas a su código y formas de refactorizar su código para hacerlo más comprobable.

8
Paddyslacker

Si comienza a seguir las prácticas Test Driven Development , lo ordenarán ¡guía a través del proceso y sabrá qué probar será algo natural. Algunos lugares para comenzar:

Las pruebas son lo primero

Nunca, nunca escriba código antes de escribir las pruebas. Consulte ¡Repetidor de repetición rojo-verde para obtener una explicación.

Escribir pruebas de regresión

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

Rojo-Verde-Refactor-Repetir

¡Rojo: Comience escribiendo una prueba más básica para el comportamiento que está tratando de implementar. Piense en este paso como escribir un código de ejemplo que use 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 ha escrito ningún código, por lo que debe fallar, ¿verdad? Lo importante que debe aprender aquí es que, a menos que vea que la prueba falla al menos una vez, nunca puede estar seguro de que si pasa, lo hace debido a algo que ha hecho por alguna razón falsa.

¡Verde: Escribe el código más simple y estúpido que realmente hace pasar la prueba. No trates de ser inteligente. Incluso si ve que hay un caso obvio de Edge pero la prueba toma en cuenta, ¡no escriba el código para manejarlo (pero no olvide el caso de Edge: lo necesitará más tarde). La idea es que cada pieza de código que escriba, cada if, cada try: ... except: ... debe estar justificado por 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 método correctos. Vea si la prueba todavía está pasando. Optimizar. Ejecute la prueba nuevamente.

¡Repita: Recuerda el caso de Edge que la prueba no cubrió, ¿verdad? Entonces, ahora es su gran momento. Escriba un caso de prueba que cubra esa situación, vea cómo falla, escriba un código, vea cómo pasa, refactorice.

Prueba tu código

Estás trabajando en un 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, trate de evitar probar el "mundo". Esto incluye: llamar a API web externas, algunas cosas intensivas en bases de datos, etc. Siempre que pueda intentar simularlo (cree un objeto que siga la misma interfaz, pero que devuelva datos estáticos predefinidos).

6
Ryszard Szopa

Para las pruebas unitarias, comience con la prueba de que hace lo que está diseñado para hacer. Ese debería ser el primer caso que escriba. Si parte del diseño es "debería arrojar una excepción si pasa basura", pruébelo también, ya que es parte del diseño.

Comience con eso. A medida que adquiera experiencia en hacer las pruebas más básicas, comenzará a aprender si eso es suficiente o no, y comenzará a ver otros aspectos de su código que necesitan pruebas.

3
Bryan Oakley

La respuesta estándar es "pruebe todo lo que pueda romperse" .

¿Qué es demasiado simple para romper? Campos de datos, accesores de propiedad con muerte cerebral y gastos generales similares. Cualquier otra cosa probablemente implementa alguna parte identificable de un requisito, y puede beneficiarse de la prueba.

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

0
Jeffrey Hantin