¿Es necesario mantener pruebas para funciones simples (autocontenidas)?

36

Considera esto:

public function polynominal($a, $b, $c, $d)
{
    return  $a * pow($x, 3) + $b * pow($x, 2) + $c * $x + $d;
}

Suponga que escribe varias pruebas para la función anterior y se prueba a sí mismo y a los demás que "funciona".

¿Por qué no eliminar esas pruebas y vivir felices para siempre? Mi punto es que algunas funciones no necesitan ser probadas continuamente después de que se ha demostrado que funcionan. Estoy buscando contrapuntos que indiquen, sí, estas funciones aún deben probarse, porque: ... O eso sí, no es necesario probarlas ...

Dennis
fuente
32
¿No es cierto para cada función que, a menos que cambie su código, si funcionó ayer, también funcionará mañana? El punto es que el software ha cambiado.
5gon12eder
3
Porque nadie escribirá override_function('pow', '$a,$b', 'return $a * $b;');en su sano juicio ... o tratará de reescribirlo para manejar números complejos.
8
Así que ... um ... que "demostrado ser de código libre de errores" ... que tiene un error .
Para funciones pequeñas como estas, puede considerar las pruebas basadas en propiedades. Las pruebas basadas en propiedades generan automáticamente casos de prueba y pruebas para invariantes predeterminados. Proporcionan documentación a través de los invariantes, lo que los hace útiles para mantenerse cerca. Para funciones simples como esta, son perfectas.
Martijn
66
Además, esa función es un gran candidato para un cambio en la forma de Horner (($a*$x + $b)*$x + $c)*$x + $dque es fácil de equivocar, pero a menudo es considerablemente más rápido. El hecho de que piense que no cambiará no significa que no vaya a cambiar.
Michael Anderson

Respuestas:

78

Pruebas de regresión

Se trata de pruebas de regresión .

Imagine que el próximo desarrollador observa su método y se da cuenta de que está usando números mágicos. Le dijeron que los números mágicos son malvados, por lo que crea dos constantes, una para el número dos y la otra para el número tres: no hay nada de malo en hacer este cambio; no es como si estuviera modificando su implementación ya correcta.

Distraído, invierte dos constantes.

Confirma el código, y todo parece funcionar bien, porque no hay pruebas de regresión ejecutándose después de cada confirmación.

Un día (podrían ser semanas después), algo se rompe en otro lugar. Y por otra parte, me refiero a la ubicación completamente opuesta de la base del código, que parece no tener nada que ver con la polynominalfunción. Horas de depuración dolorosa conducen al culpable. Durante este tiempo, la aplicación continúa fallando en la producción, causando muchos problemas a sus clientes.

Mantener las pruebas originales que escribió podría prevenir ese dolor. El desarrollador distraído cometería el código y casi de inmediato vería que rompió algo; dicho código ni siquiera llegará a la producción. Las pruebas unitarias serán adicionalmente muy precisas sobre la ubicación del error . Resolverlo no sería difícil.

Un efecto secundario ...

En realidad, la mayoría de las refactorizaciones se basan en gran medida en las pruebas de regresión. Haz un pequeño cambio. Prueba. Si pasa, todo está bien.

El efecto secundario es que si no tiene pruebas, prácticamente cualquier refactorización se convierte en un gran riesgo de romper el código. Dado que hay muchos casos, ya es difícil explicarle a la gerencia que se debe refactorizar , sería aún más difícil después de que sus intentos de refactorización anteriores introdujeran múltiples errores.

Al tener un conjunto completo de pruebas, está alentando la refactorización y, por lo tanto, un código mejor y más limpio. Sin riesgos, se vuelve muy tentador refactorizar más, de forma regular.

Cambios en los requisitos.

Otro aspecto esencial es que los requisitos cambian. Es posible que se le solicite que maneje números complejos y, de repente, debe buscar en su registro de control de versiones para encontrar las pruebas anteriores, restaurarlas y comenzar a agregar nuevas pruebas.

¿Por qué toda esta molestia? ¿Por qué eliminar las pruebas para agregarlas más tarde? Podrías haberlos mantenido en primer lugar.

Arseni Mourzenko
fuente
Nota pedante: nada es sin riesgo. ;)
jpmc26
2
La regresión es la razón número uno para las pruebas para mí: incluso si su código es claro sobre las responsabilidades y solo usa lo que se le pasa (por ejemplo, no globales), es demasiado fácil romper algo por accidente. Las pruebas no son perfectas, pero siguen siendo excelentes (y casi siempre evitan que vuelva a aparecer el mismo error, algo por lo que la mayoría de los clientes no están contentos). Si sus pruebas funcionan, no hay costos de mantenimiento. Si dejan de funcionar, es probable que haya encontrado un error. Estoy trabajando en una aplicación heredada con miles de globales; sin pruebas, no me atrevería a hacer muchos de los cambios necesarios.
Luaan
Otra nota pedante: algunos números "mágicos" están realmente bien, y reemplazarlos con constantes es una mala idea.
Deduplicador
3
@Deduplicator lo sabemos, pero muchos programadores junior especialmente formados recientemente en la universidad son extremadamente entusiastas por seguir ciegamente los mantras. Y "los números mágicos son malvados" es uno de esos. Otra es "todo lo que se use más de una vez debe refactorizarse en un método" que conduce en casos extremos a una gran cantidad de métodos que contienen solo una declaración (y luego se preguntan por qué el rendimiento cayó repentinamente, nunca aprendieron sobre las pilas de llamadas).
Jwenting
@jwenting: Acabo de agregar esa nota porque MainMa escribió "no hay nada de malo en hacer este cambio".
Deduplicador
45

Porque nada es tan simple que no puede haber errores.

Su código, mientras que parece estar libre de errores. De hecho, es una representación programática simple de una función polinómica.

Excepto que tiene un error ...

public function polynominal($a, $b, $c, $d)
{
    return  $a * pow($x, 3) + $b * pow($x, 2) + $c * $x + $d;
}

$xno se define como una entrada a su código, y dependiendo del idioma o el tiempo de ejecución, o el alcance, su función puede no funcionar, puede causar resultados no válidos o puede estrellar una nave espacial .


Apéndice:

Si bien puede considerar que su código no tiene errores por ahora, es difícil decir cuánto tiempo sigue siendo el caso. Si bien se podría argumentar que escribir una prueba para una pieza de código tan trivial no vale la pena, después de haber escrito la prueba, el trabajo está hecho y eliminarlo es eliminar una protección segura tangible.

De nota adicional son los servicios de cobertura de código (como coveralls.io) que dan una buena indicación de la cobertura que proporciona un conjunto de pruebas. Al cubrir cada línea de código, proporciona una métrica decente de la cantidad (si no la calidad) de las pruebas que realiza. En combinación con muchas pruebas pequeñas, estos servicios al menos le dicen dónde no buscar un error cuando ocurre.

En última instancia, si ya tiene una prueba escrita, consérvela . Debido a que el ahorro de espacio o tiempo de su eliminación probablemente será mucho menor que la deuda técnica más adelante si surge un error.


fuente
Hay una solución alternativa: cambiar a lenguajes no dinámicos no débiles. Las pruebas unitarias suelen ser una solución alternativa para la verificación en tiempo de compilación insuficientemente sólida y algunos idiomas son bastante buenos en este es.wikipedia.org/wiki/Agda_(programming_language)
Den
21

Sí. Si pudiéramos decir con 100% de confianza, con certeza: esta función nunca se editará y nunca se ejecutará en un contexto que podría causar que falle; si pudiéramos decir eso, podríamos abandonar las pruebas y ahorrar unos pocos milisegundos en cada CI construcción.

Pero no podemos. O no podemos con muchas funciones. Y es más simple tener una regla de ejecutar todas las pruebas todo el tiempo que hacer un esfuerzo para determinar exactamente con qué umbral de confianza estamos satisfechos, y exactamente cuánta confianza tenemos en la inmutabilidad e infalibilidad de cualquier función dada.

Y el tiempo de procesamiento es barato. Esos milisegundos guardados, incluso multiplicados muchas veces, no suman casi lo suficiente como para justificar tomarse el tiempo con cada función para preguntar: ¿tenemos suficiente confianza en que no necesitamos volver a probarlo?

Carl Manaster
fuente
Creo que es un muy buen punto que traes aquí. Ya puedo ver a dos muchachos que pasan horas discutiendo sobre cómo mantener las pruebas para alguna pequeña funcionalidad.
Kapol
@Kapol: no es un argumento, una reunión para determinar qué puede irse y qué puede quedarse, y qué criterios deben usarse para decidir, así como dónde documentar los criterios y quién firma la decisión final ...
jmoreno
12

Todo lo dicho en las otras respuestas es correcto, pero agregaré uno más.

Documentación

Las pruebas unitarias, si están bien escritas, pueden explicar al desarrollador exactamente qué hace una función, cuáles son sus expectativas de entrada / salida y, lo que es más importante, qué comportamiento se puede esperar de ella.

Puede facilitar la detección de un error y reducir la confusión.

No todos recuerdan los polinomios, la geometría o incluso el álgebra :) Pero una buena prueba unitaria registrada en el control de versiones me recordará.

Para ver un ejemplo de lo útil que puede ser como documentación, consulte la introducción de Jasmine: http://jasmine.github.io/edge/introduction.html Espere unos segundos para cargar, luego desplácese hasta la parte inferior. Verá toda la API de Jasmine documentada como salida de prueba unitaria.

[Actualización basada en los comentarios de @Warbo] Se garantiza que las pruebas también estarán actualizadas, ya que si no lo están, fallarán, lo que generalmente causará una falla de compilación si se usa CI. La documentación externa cambia independientemente del código y, por lo tanto, no está necesariamente actualizada.

Brandon
fuente
1
Existe una gran ventaja al usar pruebas como (una parte de) la documentación que dejó implícita: se verifican automáticamente. Otras formas de documentación, por ejemplo. Los comentarios explicativos o fragmentos de código en una página web pueden quedar desactualizados a medida que el código evoluciona. Las pruebas unitarias desencadenarán una falla si ya no son precisas. Esa página de jazmín es un ejemplo extremo de esto.
Warbo
Me gustaría agregar que algunos lenguajes, como D y Rust, integran su generación de documentación con sus pruebas unitarias, para que pueda tener el mismo código compilado en una prueba unitaria e insertado en la documentación HTML
Idan Arye
2

Control de realidad

He estado en entornos desafiantes donde las pruebas son "una pérdida de tiempo" durante el presupuesto y el cronograma, y ​​luego "una parte fundamental de la garantía de calidad" una vez que el cliente está lidiando con errores, por lo que mi opinión es más fluida de lo que otros podrían ser.

Tienes un presupuesto. Su trabajo es obtener el mejor producto que pueda con ese presupuesto, para cualquier definición de "mejor" que pueda reunir (no es una palabra fácil de definir). Fin de la historia.

La prueba es una herramienta en su inventario. Debería usarlo, porque es una buena herramienta , con una larga historia de ahorro de millones, o quizás miles de millones de dólares. Si tiene la oportunidad, debe agregar pruebas a estas funciones simples. Puede salvar su piel algún día.

Pero en el mundo real, con limitaciones de presupuesto y calendario, puede que no suceda. No te hagas esclavo del procedimiento. Las funciones de prueba son agradables, pero en algún momento, sus horas de trabajo pueden gastarse mejor escribiendo la documentación del desarrollador en palabras, en lugar de código, por lo que el próximo desarrollador no necesita pruebas tanto. O puede ser mejor gastar refactorizando la base del código para que no tenga que mantener una bestia tan difícil. O tal vez es mejor dedicar ese tiempo a hablar con su jefe sobre el presupuesto y el cronograma para que él o ella comprenda mejor a qué se postula cuando llegue la próxima ronda de financiación.

El desarrollo de software es un equilibrio. Siempre cuente el costo de oportunidad de todo lo que esté haciendo para asegurarse de que no haya una mejor manera de pasar su tiempo.

Cort Ammon
fuente
44
En este caso, ya había una prueba, por lo que la pregunta no es si escribirlos, sino si confirmarlos y ejecutarlos con cada compilación o versión o cualquier programa que haya para ejecutar todas las pruebas.
Paŭlo Ebermann
0

Sí, mantenga las pruebas, manténgalas funcionando y haga que pasen.

Las pruebas unitarias están ahí para protegerlo a usted (y a otros) de usted mismo (y de sí mismos).

¿Por qué mantener las pruebas es una buena idea?

  • Validar la funcionalidad de los requisitos anteriores frente a los nuevos requisitos y la funcionalidad adicional.
  • Verifique que los ejercicios de refactorización sean correctos
  • Documentación interna: así es como se espera que se use el código
  • Pruebas de regresión, las cosas cambian
    • ¿Los cambios rompen el viejo código?
    • ¿Los cambios requieren más solicitudes de cambio o actualizaciones a las funciones o códigos actuales?
  • Dado que las pruebas ya están escritas, consérvelas; es el tiempo y el dinero ya gastados lo que reducirá los costos de mantenimiento más adelante
Niall
fuente
2
Esto no parece ofrecer nada sustancial sobre las otras respuestas que se han proporcionado.
1
Hubo un problema al publicarlo antes, pero en retrospectiva, es un buen resumen. Lo quitaré
Niall
-1

Documentación del desarrollador

  • ¿Cómo sé (como otro desarrollador) que esto ha sido probado?
  • Si quiero arreglar un error en el autocontenido función , ¿cómo sé que no estoy introduciendo un error que ya había considerado?
  • Indicador de complejidad: el número de pruebas puede ser una buena medida de cuán complejo es algo. Esto puede indicar que no debe tocarlo porque es maduro y estable o que está hinchado y acoplado.

Documentación del usuario

  • A la espera de un documento pobre, puedo ver y ver si se aceptan {cero, valores negativos, conjuntos vacíos, etc.} y cuál es el valor de retorno esperado.
  • También da un buen ejemplo de cómo debería usar el objeto / función
Sixtyfootersdude
fuente
1
Esto no parece ofrecer nada sustancial sobre los puntos hechos y explicados en respuestas anteriores. Incluso "buen resumen" ya ha sido publicado (a mi gusto no es tan agradable, pero bueno)
mosquito