¿Existe tal cosa como tener demasiadas pruebas unitarias?

139

Me encargaron escribir pruebas unitarias para una aplicación existente. Después de terminar mi primer archivo, tengo 717 líneas de código de prueba para 419 líneas de código original.

¿Esta relación se volverá inmanejable a medida que aumentemos la cobertura de nuestro código?

Mi comprensión de las pruebas unitarias era probar cada método en la clase para asegurar que cada método funcionara como se esperaba. Sin embargo, en la solicitud de extracción, mi líder técnico señaló que debería centrarme en las pruebas de nivel superior. Sugirió probar 4-5 casos de uso que se usan más comúnmente con la clase en cuestión, en lugar de probar exhaustivamente cada función.

Confío en el comentario de mi líder tecnológico. Tiene más experiencia que yo y tiene mejores instintos a la hora de diseñar software. Pero, ¿cómo escribe un equipo de varias personas pruebas para un estándar tan ambiguo? es decir, ¿cómo conozco a mis compañeros y comparto la misma idea para los "casos de uso más comunes"?

Para mí, el 100% de cobertura de prueba unitaria es un objetivo elevado, pero incluso si solo alcanzáramos el 50%, sabríamos que el 100% de ese 50% estaba cubierto. De lo contrario, escribir pruebas para parte de cada archivo deja mucho espacio para hacer trampa.

usuario2954463
fuente
145
Depende. ¿Estás escribiendo un juego de tic-tac-toe, o estás escribiendo un código para administrar un reactor nuclear?
Bryan Oakley
11
Con suficientes pruebas unitarias, puede detectar problemas de implementación de hardware exóticos como el error Pentium FDIV o correlaciones en primitivas criptográficas, por lo que parece que no hay un límite difícil más allá del cual no puedan ser útiles más pruebas unitarias. Solo un límite práctico sobre cuándo es demasiado costoso.
Nat
55
Las pruebas a niveles más altos le proporcionarán una mejor perspectiva de la cobertura real. Por cobertura real me refiero a que es más probable que ocurra durante el uso regular del sistema. Ese es el tipo de cobertura que desea lograr primero. En el 50% que el último en llegar podría tener YAGNI o código muerto que una vez eliminado también contribuirá a aumentar la cobertura general.
Laiv
55
Si obtiene demasiadas pruebas (que no parece tener en este momento), el problema más probable es que el código que está probando hace demasiado. Por lo tanto, no se cumple la responsabilidad individual. Cuando el código está bien dividido, las pruebas tampoco generarán mucha carga. Si las clases hacen mucho, tienen muchos efectos secundarios, etc., se convertirá en una pesadilla.
Luc Franken
12
El documento de prueba sqlite es una lectura divertida: sqlite.org/testing.html . Cita: "la biblioteca SQLite consta de aproximadamente 122.9 KSLOC de código C. En comparación, el proyecto tiene 745 veces más código de prueba y scripts de prueba: 91596.1 KSLOC".
user60561

Respuestas:

180

Sí, con una cobertura del 100%, redactará algunas pruebas que no necesita. Desafortunadamente, la única forma confiable de determinar qué pruebas no necesita es escribirlas todas, luego esperar 10 años más o menos para ver cuáles nunca fallaron.

Mantener muchas pruebas no suele ser problemático. Muchos equipos tienen integración automatizada y pruebas del sistema además del 100% de cobertura de prueba unitaria.

Sin embargo, no estás en una fase de mantenimiento de prueba, estás jugando a ponerte al día. Es mucho mejor tener el 100% de sus clases con una cobertura de prueba del 50% que el 50% de sus clases con una cobertura de prueba del 100%, y su líder parece estar tratando de asignarle su tiempo en consecuencia. Después de tener esa línea de base, el siguiente paso generalmente es presionar al 100% en los archivos que se cambian en el futuro.

Karl Bielefeldt
fuente
11
Gracias por tu respuesta. Me ayudó a poner mi pregunta en perspectiva y resolvió el problema real: ¡mi actitud! +1
user2954463
43
@astra Tu actitud no es tan mala. Es bueno preguntarse por qué. Para responder a su otra pregunta, excelente pregunta: "¿Cómo conozco a mis compañeros y comparto la misma idea para los" casos de uso más comunes "? Usted hace que miren sus exámenes. Miren los suyos. Hablen sobre ellos. Aprenderán mucho y tal vez también lo harán. Las pruebas de revisión de códigos rara vez son una pérdida de tiempo. Aunque tiendo a hacer las mías en una terminal en lugar de en una sala de conferencias
candied_orange
18
Una prueba que nunca falla en 10 años ni siquiera garantiza que sea innecesaria, podría terminar fallando en el año 11.
Pharap
24
Pragmáticamente, podrías tomar el enfoque opuesto. Escriba las pruebas que cree que cubren los casos comunes. Pero luego, cada vez que encuentre una falla, escriba una prueba para cubrir esa área.
stannius
10
@Pharap Mi único problema con esta respuesta es que existe la suposición implícita de que una prueba solo puede agregar valor cuando falla. Una buena prueba de unidad también proporciona una excelente forma de documentación viva. También agregó valor cuando escribió la prueba, al obligarlo a pensar en la reutilización / composabilidad / encapsulación. El código no probado en mi experiencia tiende a ser bestias monolíticas inflexibles.
ArTs
66

Si ha trabajado en grandes bases de código creadas con Test Driven Development, ya sabrá que puede haber demasiadas pruebas unitarias. En algunos casos, la mayor parte del esfuerzo de desarrollo consiste en actualizar las pruebas de baja calidad que se implementarían mejor como comprobaciones invariantes, previas y posteriores a la condición en las clases relevantes, en tiempo de ejecución (es decir, la prueba como un efecto secundario de una prueba de nivel superior )

Otro problema es la creación de diseños de baja calidad, utilizando técnicas de diseño impulsadas por el culto de carga, que resultan en una proliferación de cosas para probar (más clases, interfaces, etc.). En este caso, la carga puede parecer actualizar el código de prueba, pero el verdadero problema es el diseño de baja calidad.

Frank Hileman
fuente
16
Votados a favor de señalar condiciones previas, condiciones posteriores e invariantes deben tratarse como pruebas unitarias. De esa manera, cada uso es una prueba unitaria cuando ese código de depuración está activo.
Persixty
Esta es una gran respuesta y se alinea perfectamente con mi experiencia.
Tony Ennis
Y un problema más: si ha cerrado los registros (¡realmente debería hacerlo!) Con grandes cantidades de baja calidad, posiblemente incluso las pruebas de ejecución prolongada ralentizarán todo sin proporcionar ningún beneficio real. Y luego, obviamente, el hecho divertido de cambiar una cosa en una clase y cientos de pruebas fallan.
Voo
3
¡Esta es una respuesta mucho mejor que la aceptada! "En algunos casos, la mayor parte del esfuerzo de desarrollo consiste en actualizar pruebas de baja calidad". Lo he experimentado y es una mierda. Más que no tener pruebas en absoluto, en algunos aspectos.
Benjamin Hodgson
36

Respuestas a tus preguntas

¿Existe tal cosa como tener demasiadas pruebas unitarias?

Claro ... Podría, por ejemplo, tener múltiples pruebas que parecen ser diferentes a primera vista pero que realmente prueban lo mismo (dependen lógicamente de las mismas líneas de código de aplicación "interesante" bajo prueba).

O podría probar las partes internas de su código que nunca salen a la superficie (es decir, que no forman parte de ningún tipo de contrato de interfaz), donde uno podría discutir si eso tiene sentido. Por ejemplo, la redacción exacta de los mensajes de registro internos o lo que sea.

Me encargaron escribir pruebas unitarias para una aplicación existente. Después de terminar mi primer archivo, tengo 717 líneas de código de prueba para 419 líneas de código original.

Eso me parece bastante normal. Sus pruebas emplean muchas líneas de código en la configuración y el desmontaje además de las pruebas reales. La relación puede mejorar o no. Yo mismo tengo muchas pruebas y, a menudo, invierto más loc y tiempo en las pruebas que el código real.

¿Esta relación se volverá inmanejable a medida que aumentemos la cobertura de nuestro código?

La relación no tiene mucho en cuenta. Hay otras cualidades de las pruebas que tienden a hacerlas inmanejables. Si regularmente tiene que refactorizar una gran cantidad de pruebas cuando realiza cambios bastante simples en su código, debe echar un vistazo a las razones. Y esas no son cuántas líneas tienes, sino cómo te acercas a la codificación de las pruebas.

Mi comprensión de las pruebas unitarias era probar cada método en la clase para asegurar que cada método funcionara como se esperaba.

Eso es correcto para las pruebas de "unidad" en sentido estricto. Aquí, "unidad" es algo así como un método o una clase. El punto de la prueba de "unidad" es probar solo una unidad de código específica, no todo el sistema. Lo ideal sería eliminar todo el resto del sistema (usando dobles o cualquier otra cosa).

Sin embargo, en la solicitud de extracción, mi líder técnico señaló que debería centrarme en las pruebas de nivel superior.

Luego caíste en la trampa de asumir que las personas realmente significaban pruebas unitarias cuando decían pruebas unitarias. He conocido a muchos programadores que dicen "prueba unitaria" pero quieren decir algo muy diferente.

Sugirió probar 4-5 casos de uso que se usan más comúnmente con la clase en cuestión, en lugar de probar exhaustivamente cada función.

Claro, solo concentrarse en el 80% superior del código importante también reduce la carga ... Aprecio que pienses mucho en tu jefe, pero esto no me parece la opción óptima.

Para mí, el 100% de cobertura de prueba unitaria es un objetivo elevado, pero incluso si solo alcanzáramos el 50%, sabríamos que el 100% de ese 50% estaba cubierto.

No sé qué es la "cobertura de prueba unitaria". Supongo que quiere decir "cobertura de código", es decir, después de ejecutar el conjunto de pruebas, cada línea de código (= 100%) se ha ejecutado al menos una vez.

Esta es una buena métrica de estadio, pero de lejos no es el mejor estándar para el que uno podría disparar. Simplemente ejecutar líneas de código no es la imagen completa; Esto no tiene en cuenta las diferentes rutas a través de ramas complicadas y anidadas, por ejemplo. Es más una métrica que apunta con el dedo a fragmentos de código que se prueban muy poco (obviamente, si una clase tiene una cobertura de código del 10% o 5%, entonces algo está mal); Por otro lado, una cobertura del 100% no le dirá si ha realizado pruebas suficientes o si las ha realizado correctamente.

Pruebas de integración

Me molesta sustancialmente cuando la gente habla constantemente de pruebas unitarias hoy, por defecto. En mi opinión (y experiencia), las pruebas unitarias son excelentes para bibliotecas / API; en áreas más orientadas a los negocios (donde hablamos de casos de uso como en la pregunta en cuestión), no son necesariamente la mejor opción.

Para el código de aplicación general y en el negocio promedio (donde ganar dinero, cumplir con los plazos y cumplir con la satisfacción del cliente es importante, y principalmente desea evitar errores que están directamente en la cara del usuario o que podrían conducir a desastres reales , no estamos hablando de los lanzamientos de cohetes de la NASA aquí), las pruebas de integración o características son mucho más útiles.

Esos van de la mano con el desarrollo dirigido por el comportamiento o el desarrollo basado en características; aquellos que no funcionan con pruebas unitarias (estrictas), por definición.

Para mantenerlo breve (ish), una prueba de integración / función ejercita toda la pila de aplicaciones. En una aplicación basada en la web, actuaría como un navegador que hace clic en la aplicación (y no, obviamente, no tiene que ser tan simplista, existen marcos muy potentes para hacer eso; consulte http: // pepino. io por ejemplo).

Ah, para responder a sus últimas preguntas: hace que todo su equipo tenga una cobertura de prueba alta asegurándose de que una nueva función solo se programe después de que su prueba de función se haya implementado y haya fallado. Y sí, eso significa cada característica. Esto le garantiza una cobertura de características del 100% (positiva). Por definición, garantiza que una característica de su aplicación nunca "desaparecerá". No garantiza una cobertura de código del 100% (por ejemplo, a menos que programe activamente funciones negativas, no ejercerá su manejo de errores / manejo de excepciones).

No le garantiza una aplicación libre de errores; por supuesto, querrá escribir pruebas de características para situaciones de errores obvias o muy peligrosas, entradas incorrectas del usuario, piratería (por ejemplo, administración de sesiones, seguridad y demás), etc .; pero incluso solo programar las pruebas positivas tiene un beneficio enorme y es bastante factible con marcos modernos y potentes.

Las pruebas de características / integración obviamente tienen su propia lata de gusanos (p. Ej., Rendimiento; pruebas redundantes de marcos de terceros; como generalmente no usas dobles, también tienden a ser más difíciles de escribir, según mi experiencia ...), pero yo ' d tome una aplicación 100% de prueba de características positivas sobre una aplicación 100% de prueba de unidad de cobertura de código (¡no biblioteca!) cualquier día.

AnoE
fuente
1
Las pruebas de integración son excelentes, pero no reemplazan las pruebas unitarias, ni tampoco para las aplicaciones comerciales. Hay múltiples problemas con ellos: a) por definición tardan mucho tiempo en ejecutarse (también significa que las pruebas incrementales son prácticamente inútiles), b) hacen que sea increíblemente difícil determinar el problema real (oh 50 pruebas de integración simplemente fallaron, ¿Qué cambio causó eso?) yc) cubren las mismas rutas de código repetidamente.
Voo
1
a) es un problema porque hace que la ejecución de las pruebas en registros cerrados sea engorrosa y hace que sea menos probable que los programadores realicen las pruebas repetidamente durante el desarrollo, lo que junto con b) disminuye la eficiencia y la capacidad de diagnosticar rápidamente los errores. c) significa que cambiar una pequeña cosa fácilmente puede hacer que decenas o cientos (estado allí) de sus pruebas de integración fallen, lo que significa que pasará mucho tiempo arreglándolas. Esto también significa que las pruebas de integración solo prueban caminos felices, porque escribir estas pruebas específicas es engorroso o imposible.
Voo
1
@Voo, todo lo que escribiste es cierto, y por lo que puedo decir, ya mencioné todos los problemas que
anotaste
Si está de acuerdo con ese resumen, realmente no veo cómo puede llegar a la conclusión de que preferiría las pruebas de integración a las pruebas unitarias. Los paquetes completos de pruebas de integración de programas grandes tardan horas o incluso días en ejecutarse, son excelentes, pero son casi inútiles durante el desarrollo real. Y sus pruebas de aceptación (que todo el mundo está haciendo, ¿verdad?) Detectarán muchos de los mismos problemas que las pruebas de integración encontrarían que las pruebas unitarias pasarían por alto; sin embargo, lo contrario no es cierto.
Voo
24

Sí, es posible tener demasiadas pruebas unitarias. Si tiene una cobertura del 100% con pruebas unitarias y sin pruebas de integración, por ejemplo, tiene un problema claro.

Algunos escenarios:

  1. Sobre-diseñas tus pruebas para una implementación específica. Luego, debe descartar las pruebas unitarias cuando refactoriza, por no decir cuándo cambia la implementación (un punto muy frecuente cuando se realizan optimizaciones de rendimiento).

    Un buen equilibrio entre las pruebas unitarias y las pruebas de integración reducen este problema sin perder una cobertura significativa.

  2. Podrías tener una cobertura razonable para cada confirmación con el 20% de las pruebas que tienes, dejando el 80% restante para la integración o al menos pasar las pruebas por separado; Los principales efectos negativos que ve en este escenario son cambios lentos, ya que debe esperar mucho tiempo para que se ejecuten las pruebas.

  3. Modifica demasiado el código para permitirle probarlo; Por ejemplo, he visto mucho abuso de IoC en componentes que nunca requerirán ser modificados o al menos es costoso y de baja prioridad generalizarlos, pero la gente invierte mucho tiempo en generalizarlos y refactorizarlos para permitir que la unidad los pruebe. .

Estoy particularmente de acuerdo con la sugerencia de obtener una cobertura del 50% en el 100% de los archivos, en lugar de una cobertura del 100% en el 50% de los archivos; centre sus esfuerzos iniciales en los casos positivos más comunes y en los casos negativos más peligrosos, no invierta demasiado en el manejo de errores y caminos inusuales, no porque no sean importantes sino porque tiene un tiempo limitado y un universo de pruebas infinito, así que debes priorizar en cualquier caso.

Bruno Guardia
fuente
2
Lo cual no es un problema con las pruebas unitarias, sino con la organización por haber equivocado sus prioridades al exigir un número específico para la cobertura de las pruebas unitarias sin gastar los recursos para crear y ejecutar pruebas adecuadas en otros niveles.
partir
2
Acuerda firmemente el n. ° 3 y también lo extendería para pasar manualmente las instancias de clases de nivel inferior a clases de nivel superior. Si una cosa de alto nivel se basa en algo de bajo nivel para hacer algo, está bien. Si oculta los detalles de eso de las personas que llaman, llamaría a eso un buen diseño. Pero si luego haces que la cosa de bajo nivel sea parte de la interfaz de la cosa de alto nivel y haces que las personas que llaman la pasen porque hace que tus pruebas sean bonitas, ahora la cola está moviendo al perro. (Si la cosa de bajo nivel se reutiliza en muchos lugares, y cambia mucho, eso cambia las cosas. En mi experiencia, eso no ha sido típico.)
johncip
Me encanta tu descripción @johncip, definitivamente ese es un ejemplo frecuente de cómo una buena clase se vuelve horrible al agregar un montón de parámetros necesarios innecesarios al constructor ...
Bruno Guardia
19

Tenga en cuenta que cada prueba tiene un costo y un beneficio. Los inconvenientes incluyen:

  • una prueba tiene que ser escrita;
  • una prueba lleva (generalmente una cantidad muy pequeña) de tiempo para ejecutarse;
  • se debe mantener una prueba con el código; las pruebas deben cambiar cuando las API que están probando cambian;
  • Puede que tenga que cambiar su diseño para escribir una prueba (aunque estos cambios son generalmente para mejor).

Si los costos son mayores que los beneficios, es mejor no realizar una prueba. Por ejemplo, si la funcionalidad es difícil de probar, la API cambia a menudo, la corrección es relativamente poco importante y la posibilidad de que la prueba encuentre un defecto es baja, probablemente sea mejor no escribirla.

En cuanto a su proporción particular de pruebas a código, si el código es lo suficientemente denso lógico, entonces esa proporción puede justificarse. Sin embargo, probablemente no valga la pena mantener una relación tan alta en una aplicación típica.

El secreto de Solomonoff
fuente
12

Sí, existen demasiadas pruebas unitarias.

Si bien las pruebas son buenas, cada prueba unitaria es:

  • Una carga de mantenimiento potencial que está estrechamente vinculada a la API

  • Tiempo que podría gastarse en otra cosa.

  • Un poco de tiempo en la suite de pruebas unitarias
  • Es posible que no agregue ningún valor real porque es en efecto un duplicado de alguna otra prueba que tiene una posibilidad minúscula de que alguna otra prueba pase y esta prueba falle.

Es aconsejable apuntar a una cobertura de código del 100%, pero eso dista mucho de significar un conjunto de pruebas, cada una de las cuales proporciona de forma independiente una cobertura de código del 100% en algún punto de entrada específico (función / método / llamada, etc.).

Aunque dado lo difícil que puede ser lograr una buena cobertura y eliminar errores, la verdad es que probablemente existan "pruebas unitarias incorrectas" y "demasiadas pruebas unitarias".

La pragmática para la mayoría del código indica:

  1. Asegúrese de tener una cobertura del 100% de los puntos de entrada (todo se prueba de alguna manera) y procure estar cerca del 100% de cobertura del código de las rutas 'sin errores'.

  2. Pruebe cualquier valor o tamaño mínimo / máximo relevante

  3. Pruebe cualquier cosa que piense que es un caso especial divertido, particularmente valores "extraños".

  4. Cuando encuentre un error, agregue una prueba de unidad que hubiera revelado ese error y piense si se deben agregar casos similares.

Para algoritmos más complejos, considere también:

  1. Haciendo algunas pruebas masivas de más casos.
  2. Comparación de resultados con una implementación de 'fuerza bruta' y verificación de los invariantes.
  3. Usando algún método para producir casos de prueba aleatorios y verificar contra la fuerza bruta y las condiciones posteriores, incluidas las invariantes.

Por ejemplo, verifique un algoritmo de clasificación con alguna entrada aleatoria y la validación de los datos se clasifica al final escaneándola.

Yo diría que su líder tecnológico está proponiendo pruebas de 'mínimo culo desnudo'. Estoy ofreciendo 'pruebas de calidad de mayor valor' y hay un espectro intermedio.

Tal vez su superior sabe que el componente que está construyendo se incrustará en una pieza más grande y la unidad se probará más a fondo cuando se integre.

La lección clave es agregar pruebas cuando se encuentran errores. Lo que me lleva a mi mejor lección sobre el desarrollo de pruebas unitarias:

Concéntrese en unidades, no en subunidades. Si está construyendo una unidad a partir de subunidades, escriba pruebas muy básicas para las subunidades hasta que sean plausibles y logre una mejor cobertura probando las subunidades a través de sus unidades de control.

Entonces, si está escribiendo un compilador y necesita escribir una tabla de símbolos (por ejemplo). Obtenga la tabla de símbolos en funcionamiento con una prueba básica y luego trabaje (digamos) el analizador de declaraciones que llena la tabla. Solo agregue más pruebas a la unidad 'independiente' de la tabla de símbolos si encuentra errores en ella. De lo contrario, aumente la cobertura mediante pruebas unitarias en el analizador de declaraciones y luego en todo el compilador.

Eso obtiene el mejor rendimiento (una prueba del conjunto es probar múltiples componentes) y deja más capacidad para rediseñar y refinar porque solo se usa la interfaz 'externa' en las pruebas que tienden a ser más estables.

Junto con las condiciones previas y posteriores de las pruebas de código de depuración, incluidas las invariantes en todos los niveles, obtiene la máxima cobertura de prueba desde una implementación de prueba mínima.

Persixty
fuente
44
No diría que la cobertura del 100% es pragmática. El 100% de cobertura es un estándar extremadamente alto.
Bryan Oakley
Desafortunadamente, incluso el método aleatorio puede perder errores. No hay sustituto para las pruebas, incluso si son informales.
Frank Hileman
@BryanOakley Point tomado. Eso es una exageración. Pero es más importante acercarse a eso que las personas dan crédito. "Probé el camino fácil, todo está bien" siempre causará problemas más adelante.
Persixty
@FrankHileman La pregunta no era "¿La unidad está probando un buen sustituto para diseñar el software con cuidado, la lógica de comprobación estática y los algoritmos de prueba", entonces la respuesta es 'no'. Ninguno de los métodos producirá software de alta calidad por su cuenta.
Persixty
3

En primer lugar, no es necesariamente un problema tener más líneas de prueba que de código de producción. El código de prueba es (o debería ser) lineal y fácil de comprender: su complejidad necesaria es muy, muy baja, independientemente de si el código de producción es o no. Si la complejidad de las pruebas comienza a acercarse a la del código de producción, entonces es probable que tenga un problema.

Sí, es posible tener demasiadas pruebas unitarias; un simple experimento mental muestra que puede continuar agregando pruebas que no proporcionan un valor adicional, y que todas esas pruebas agregadas pueden inhibir al menos algunas refactorizaciones.

El consejo de probar solo los casos más comunes es defectuoso, en mi opinión. Estos pueden actuar como pruebas de humo para ahorrar tiempo en las pruebas del sistema, pero las pruebas realmente valiosas detectan casos que son difíciles de ejercer en todo el sistema. Por ejemplo, la inyección de error controlada de fallas de asignación de memoria se puede utilizar para ejercer rutas de recuperación que de otro modo podrían ser de calidad completamente desconocida. O pase cero como un valor que sabe que se usará como divisor (o un número negativo que tendrá raíz cuadrada), y asegúrese de no obtener una excepción no controlada.

Las siguientes pruebas más valiosas son aquellas que ejercen los límites extremos o puntos límite. Por ejemplo, una función que acepta meses (basados ​​en 1) del año debe probarse con 0, 1, 12 y 13, para que sepa que las transiciones válidas no válidas están en el lugar correcto. Es una prueba excesiva usar también 2..11 para estas pruebas.

Estás en una posición difícil, ya que tienes que escribir pruebas para el código existente. Es más fácil identificar los casos extremos mientras escribe (o está a punto de escribir) el código.

Toby Speight
fuente
3

Mi comprensión de las pruebas unitarias era probar cada método en la clase para asegurar que cada método funcionara como se esperaba.

Esta comprensión está mal.

Las pruebas unitarias verifican el comportamiento de la unidad bajo prueba .

En ese sentido, una unidad no es necesariamente "un método en una clase". Me gusta la definición de una unidad de Roy Osherove en The Art of Unit Testing :

Una unidad es todo el código de producción que tiene la misma razón para cambiar.

En base a esto, una prueba unitaria debería verificar cada comportamiento deseado de su código. Donde el "deseo" se toma más o menos de los requisitos.


Sin embargo, en la solicitud de extracción, mi líder técnico señaló que debería centrarme en las pruebas de nivel superior.

Tiene razón, pero de una manera diferente de lo que piensa.

Por su pregunta, entiendo que usted es el "probador dedicado" en ese proyecto.

El gran malentendido es que él espera que escribas pruebas unitarias (en contraste con "prueba usando un marco de pruebas unitarias"). Escribir y probar pruebas es responsabilidad de los desarrolladores , no de los evaluadores (en un mundo ideal, lo sé ...). Por otro lado, etiquetó esta pregunta con TDD, lo que implica exactamente esto.

Su trabajo como probador es escribir (o ejecutar manualmente) pruebas de módulo y / o aplicación. Y este tipo de pruebas debería verificar principalmente que todas las unidades funcionen juntas sin problemas. Eso significa que debe seleccionar sus casos de prueba para que cada unidad se ejecute al menos una vez . Y esa verificación es que se ejecuta. El resultado real es menos importante ya que está sujeto a cambios con los requisitos futuros.

Para enfatizar la analogía del volcado de automóviles una vez más: ¿Cuántas pruebas se realizan con un automóvil al final de la línea de montaje? Exactamente uno: debe conducir al estacionamiento por sí mismo ...

El punto aquí es:

Debemos ser conscientes de esa diferencia entre "pruebas unitarias" y "prueba automatizada utilizando un marco de pruebas unitarias".


Para mí, el 100% de cobertura de prueba unitaria es un objetivo elevado, pero incluso si solo alcanzáramos el 50%, sabríamos que el 100% de ese 50% estaba cubierto.

Las pruebas unitarias son una red de seguridad. Le dan confianza para refactorizar su código para reducir la deuda técnica o agregar un nuevo comportamiento sin temor a romper el comportamiento ya implementado.

No necesita una cobertura del código del 100%.

Pero necesita una cobertura de comportamiento del 100%. (Sí, la cobertura de código y la cobertura de comportamiento se correlacionan de alguna manera, pero no son idénticas por el simple hecho de hacerlo).

Si tiene menos del 100% de cobertura de comportamiento, una ejecución exitosa de su conjunto de pruebas no significa nada, ya que podría haber cambiado parte del comportamiento no probado. Y su cliente lo notará el día después de que su lanzamiento se haya conectado ...


Conclusión

Pocas pruebas son mejores que ninguna prueba. ¡Sin duda!

Pero no existe tal cosa como tener demasiadas pruebas unitarias.

Esto se debe a que cada prueba unitaria verifica una sola expectativa sobre el comportamiento de los códigos . Y no puede escribir más pruebas unitarias de las que espera de su código. Y un agujero en su arnés de seguridad es una oportunidad para que un cambio no deseado dañe el sistema de producción.

Timothy Truckle
fuente
2

Absolutamente sí. Solía ​​ser un SDET para una gran empresa de software. Nuestro pequeño equipo tuvo que mantener el código de prueba que solía manejar un equipo mucho más grande. Además de eso, nuestro producto tenía algunas dependencias que constantemente introducían cambios importantes, lo que significaba un mantenimiento de prueba constante para nosotros. No teníamos la opción de aumentar el tamaño del equipo, por lo que tuvimos que tirar miles de las pruebas menos valiosas cuando fallaron. De lo contrario, nunca podríamos estar al día con los defectos.

Antes de descartar esto como un mero problema de gestión, tenga en cuenta que muchos proyectos en el mundo real sufren una reducción de personal a medida que se acercan al estado heredado. A veces, incluso comienza a suceder justo después del primer lanzamiento.

mrog
fuente
44
"Además de eso, nuestro producto tenía algunas dependencias que constantemente introducían cambios importantes, lo que significaba un mantenimiento de prueba constante para nosotros". - Esas pruebas que usted dice requieren sonido de mantenimiento como las valiosas si sus dependencias se rompen constantemente.
CodeMonkey
2
Eso no es un problema con las pruebas, sino con la organización.
partir
2
@CodeMonkey Las dependencias no se rompieron. Se estaban actualizando de formas que requerían cambios en nuestro producto. Sí, las pruebas fueron valiosas, pero no tan valiosas como otras. Las pruebas automatizadas son más valiosas cuando la prueba manual equivalente es difícil.
mrog
2
@jwenting Sí, es un problema de organización, no un problema de código. Pero eso no cambia el hecho de que hubo demasiadas pruebas. Una prueba fallida que no puede investigarse es inútil, independientemente de la causa.
mrog
¿Qué es un "SDET"?
Peter Mortensen
1

Tener más líneas de código de prueba que código de producto no es necesariamente un problema, suponiendo que esté refactorizando su código de prueba para eliminar copiar y pegar.

Lo que es un problema es tener pruebas que sean espejos de su implementación, sin ningún significado comercial, por ejemplo, pruebas cargadas con simulacros y apéndices y solo afirmando que un método llama a otro método.

Una gran cita en el documento "por qué la mayoría de las pruebas unitarias son desperdicios" es que las pruebas unitarias deben tener un "oráculo de corrección amplio, formal e independiente, y ... valor comercial atribuible"

wrschneider
fuente
0

Una cosa que no vi mencionado es que sus pruebas deben ser rápidas y fáciles para que cualquier desarrollador las ejecute en cualquier momento.

No desea tener que registrarse en el control de origen y esperar una hora o más (dependiendo del tamaño de su base de código) antes de que se completen las pruebas para ver si su cambio rompió algo; desea poder hacerlo en su propia máquina antes de registrarse en el control de origen (o al menos, antes de impulsar sus cambios). Idealmente, debería poder ejecutar sus pruebas con un solo script o presionando un botón.

Y cuando ejecuta esas pruebas localmente, desea que se ejecuten rápidamente, en el orden de segundos. Un poco más lento, y tendrás la tentación de no ejecutarlos lo suficiente o en absoluto.

Por lo tanto, tener tantas pruebas que ejecutarlas toma minutos, o tener algunas pruebas demasiado complejas, podría ser un problema.

mmathis
fuente