¿Qué debe probar con las pruebas unitarias?

122

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é probar. ¿Debo probar el caso común? El caso del borde? ¿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.

zneak
fuente
Echa un vistazo al blog de Roy Osherove . Hay mucha información sobre las pruebas unitarias allí, incluidos videos. También ha escrito un libro, "The art of Unit Testing", que es muy bueno.
Piers Myers
99
Me pregunto qué piensas después de casi 5 años. Porque cada vez más siento que la gente debería saber mejor "qué no hacer pruebas unitarias" hoy en día. El desarrollo impulsado por el comportamiento ha evolucionado a partir de las preguntas que usted ha formulado.
Remigijus Pankevičius

Respuestas:

121

Mi filosofía personal ha sido hasta ahora:

  1. Prueba el caso común de todo lo que puedas. Esto le indicará 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 extremos de un código inusualmente complejo que cree que probablemente tendrá errores.
  3. Cada vez que encuentre un error, escriba un caso de prueba para cubrirlo antes de arreglarlo
  4. Agregue pruebas de casos extremos a un código menos crítico siempre que alguien tenga tiempo de matar.
Fishtoaster
fuente
1
Gracias por esto, estaba dando vueltas por aquí con las mismas preguntas que el OP.
Stephen
55
+1, aunque también probaría los casos extremos de cualquier función de biblioteca / tipo de utilidad para garantizar que tenga una API lógica. Por ejemplo, ¿qué sucede cuando se pasa un valor nulo? ¿Qué pasa con la entrada vacía? Esto ayudará a garantizar que su diseño sea lógico y documentará el comportamiento del caso de esquina.
mikera
77
# 3 parece una respuesta muy sólida, ya que es un ejemplo de la vida real de cómo una prueba unitaria podría haber ayudado. Si se rompió una vez, puede romperse nuevamente.
Ryan Griffith
Acabo de empezar, me parece que no soy muy creativo para diseñar pruebas. Así que los uso como el n. ° 3 anterior, lo que garantiza la tranquilidad de que esos errores nunca más pasarán desapercibidos.
ankush981
Su respuesta apareció en este popular artículo medio: hackernoon.com/…
BugHunterUK
67

Entre la gran cantidad de respuestas hasta ahora, nadie ha tocado el reparto de equivalencia y el análisis del 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 la 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 tanto, 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 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 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. Sin embargo, con la partición de equivalencia someter a prueba toda una 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 cierto conocimiento de los valores ASCII, podría encontrar estas entradas de casos 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!)

Michael Sorens
fuente
3
+1 Así es exactamente como escribo intuitivamente mis pruebas. Ahora puedo ponerle un nombre :) Gracias por compartir eso.
guillaume31
+1 para "las respuestas cualitativas son útiles, pero es posible, y preferible, ser cuantitativo"
Jimmy Breck-McKye
Creo que esta es una buena respuesta si la directiva es "cómo puedo obtener una buena cobertura con mis exámenes". Creo que sería útil encontrar un enfoque pragmático además de esto: ¿es el objetivo que cada rama de cada pieza de lógica en cada capa se pruebe a fondo de esta manera?
Kieren Johnstone
18

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 de desarrollar 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 extremos, 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. ;-)

Philip
fuente
2
¿Puede dar más detalles sobre por qué las pruebas hacen que este software sea un problema para refactorizar?
Mike Partridge
66
Gran +1. Tener muros de pruebas unitarias que prueban la implementación en lugar de las reglas hace que cualquier cambio requiera
2-3
99
Al igual que el código de producción mal escrito, las pruebas unitarias mal escritas son difíciles de mantener. "Demasiadas pruebas unitarias" suena como una falla para mantenerse SECO; cada prueba debe abordar / probar una parte específica del sistema.
Allan
1
Cada prueba unitaria debe verificar una cosa, por lo que no hay demasiadas pruebas unitarias pero faltan pruebas. Si sus pruebas unitarias son complejas, ese es otro problema.
graffic
1
-1: Creo que esta publicación está mal escrita. Se mencionan varias cosas, y no sé cómo se relacionan todas. Si el punto de la respuesta es "ser económico", ¿cómo se relaciona su ejemplo? Parece que su situación de ejemplo (aunque real) tiene malas pruebas unitarias. Por favor explique qué lecciones se supone que debo aprender de eso y cómo me ayuda a ser económico. Además, sinceramente, simplemente no sé a qué te refieres cuando dices Leave this TDD stuff to the TDD-experts.
Alexander Bird
8

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 como para que obtenga una copia de Working Effectively With Legacy Code de Michael Feathers y eche un vistazo a algunas de las técnicas para agregar pruebas a su código y formas de refactorizar su código para hacerlo más comprobable.

Paddyslacker
fuente
¿Cómo se calcula ese porcentaje de cobertura? ¿Qué significa cubrir el 90% de su código de todos modos?
zneak
2
@zneak: existen herramientas de cobertura de código que las calcularán por usted. Un google rápido para "cobertura de código" debería mostrar algunos de ellos. La herramienta rastrea las líneas de código que se ejecutan mientras se ejecutan las pruebas, y las bases que se ajustan al total de líneas de código en los ensamblados para obtener el porcentaje de cobertura.
Steven Evers
-1. No responde la pregunta:The problem is, I don't know _what_ to test
Alexander Bird
6

Si comienza a seguir las prácticas de desarrollo guiado por pruebas , lo guiarán 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. Ver Red-Green-Refactor-Repeat para 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 : escriba 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 límite obvio pero la prueba tiene en cuenta, no escriba código para manejarlo (pero no olvide el caso límite: lo necesitará más adelante). La idea es que cada pieza de código que escriba, cada if, cada try: ... except: ...debe estar justificada 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 límite 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).

Ryszard Szopa
fuente
1
Suponiendo que ya tengo una base de código existente y (por lo que puedo ver) en funcionamiento, ¿qué debo hacer?
zneak
Eso puede ser un poco más difícil (dependiendo de cómo se escriba el código). Comience con pruebas de regresión (siempre tienen sentido), luego puede intentar escribir pruebas unitarias para demostrar a usted mismo que comprende lo que está haciendo el código. Es fácil sentirse abrumado por la cantidad de trabajo que (aparentemente) se debe hacer, pero: algunas pruebas siempre son mejores que ninguna.
Ryszard Szopa
3
-1 No creo que sea una muy buena respuesta para esta pregunta . La pregunta no es acerca de TDD, se trata de qué probar al escribir pruebas unitarias. Creo que una buena respuesta a la pregunta real debería aplicarse a una metodología que no sea TDD.
Bryan Oakley
1
Si lo tocas, pruébalo. Y Clean Code (Robert C Martin) sugiere que escriba "pruebas de aprendizaje" para código de terceros. De esa manera, aprende a usarlo y tiene pruebas en caso de que una nueva versión cambie el comportamiento que está usando.
Roger Willcocks
3

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 con la realización de 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.

Bryan Oakley
fuente
0

La respuesta común es "probar todo lo que pueda romperse" .

¿Qué es demasiado simple para romper? Campos de datos, accesos 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.

Jeffrey Hantin
fuente
Bueno. Entonces, ¿qué casos debo probar? El caso "normal"? El caso del borde?
zneak
3
¿Regla de oro? Uno o dos justo en el medio del camino dorado, y justo dentro y fuera de cualquier borde.
Jeffrey Hantin
@JeffreyHantin Ese es el "análisis del valor límite" en una respuesta diferente.
Roger Willcocks