¿Pueden las pruebas unitarias automatizadas reemplazar la verificación de tipo estático?

StackOverflow https://stackoverflow.com/questions/415434

  •  03-07-2019
  •  | 
  •  

Pregunta

He comenzado a analizar la idea de desarrollo de pruebas unitarias completas / prueba de desarrollo, y cuanto más lo pienso, más parece tener un papel similar a la verificación de tipo estático. Ambas técnicas proporcionan una comprobación de respuesta rápida en tiempo de compilación para ciertos tipos de errores en su programa. Sin embargo, corríjame si me equivoco, pero parece que un conjunto de pruebas unitarias con cobertura total probaría todo lo que la comprobación de tipo estático probaría, y algo más. O dicho de otra manera, las comprobaciones de tipo estático solo van en parte a & Quot; probar & Quot; que su programa es correcto, mientras que las pruebas unitarias le permitirán " probar " tanto como quieras (hasta cierto punto).

Entonces, ¿hay alguna razón para usar un lenguaje con verificación de tipo estático si también usa pruebas unitarias? Se hizo una pregunta algo similar aquí , pero me gustaría entrar en más detalles . ¿Qué ventajas específicas, si hay alguna, tiene la verificación de tipo estático sobre las pruebas unitarias? Me vienen a la mente algunos problemas como las optimizaciones del compilador y el intellisense, pero ¿hay otras soluciones para esos problemas? ¿Hay otras ventajas / desventajas en las que no he pensado?

¿Fue útil?

Solución

Creo que las pruebas unitarias automatizadas serán importantes para los idiomas de tipo dinámico, pero eso no significa que reemplazará la verificación de tipo estático en el contexto que aplique. De hecho, algunos de los que usan la escritura dinámica en realidad podrían estar usándola porque no quieren las molestias de las constantes comprobaciones de seguridad de los tipos.

Las ventajas que ofrecen los lenguajes de escritura dinámica sobre los lenguajes de escritura estática van mucho más allá de las pruebas, y la seguridad de escritura es solo un aspecto. Los estilos de programación y las diferencias de diseño sobre los lenguajes de tipo dinámico y estático también varían mucho.

Además, las pruebas unitarias que se escriben con demasiada fuerza imponen la seguridad de los tipos significaría que el software no debe escribirse dinámicamente después de todo, o que el diseño que se aplica debe escribirse en un lenguaje estático, no dinámico.

Otros consejos

Hay un hecho inmutable sobre la calidad del software.

  

Si no puede compilar, no puede enviarse

En esta regla, los idiomas con escritura estática ganarán a los idiomas con escritura dinámica.

Ok, sí, esta regla no es inmutable. Las aplicaciones web pueden enviarse sin compilar (he implementado muchas aplicaciones web de prueba que no se compilaron). Pero lo que es fundamentalmente cierto es

  

Cuanto antes detecte un error, más barato será repararlo

Un lenguaje de tipo estático evitará que ocurran errores reales en uno de los primeros momentos posibles del ciclo de desarrollo de software. Un lenguaje dinámico no lo hará. Las pruebas unitarias, si eres minucioso a un nivel súper humano, pueden tomar el lugar de un lenguaje estáticamente tipado.

Sin embargo, ¿por qué molestarse? Hay muchas personas increíblemente inteligentes que escriben un sistema completo de verificación de errores en forma de compilador. Si le preocupa obtener errores antes, use un lenguaje de tipo estático.

Por favor, no tome esta publicación como un ataque de lenguajes dinámicos. Yo uso lenguajes dinámicos a diario y los amo. Son increíblemente expresivos y flexibles y permiten programas increíblemente fascinantes. Sin embargo, en el caso de informes de errores tempranos, pierden a los lenguajes estáticamente escritos.

Para cualquier proyecto de tamaño razonable, simplemente no puede dar cuenta de todas las situaciones solo con pruebas unitarias.

Entonces, mi respuesta es & "; no &"; e incluso si logras dar cuenta de todas las situaciones, has derrotado el propósito de usar un lenguaje dinámico en primer lugar.

Si desea programar un tipo seguro, mejor use un lenguaje de tipo seguro.

Tener una cobertura de código del 100% no significa que haya probado completamente su aplicación. Considere el siguiente código:

if (qty > 3)
{
    applyShippingDiscount();
}
else
{
    chargeFullAmountForShipping();
}

Puedo obtener una cobertura de código del 100% si agrego valores de qty = 1 y qty = 4.

Ahora imagine que mi condición comercial era " ... para pedidos de 3 o más artículos, debo aplicar un descuento a los costos de envío ... " ;. Entonces tendría que escribir pruebas que funcionaran en los límites. Por lo tanto, diseñaría pruebas donde la cantidad fuera 2,3 y 4. Todavía tengo una cobertura del 100%, pero lo más importante es que encontré un error en mi lógica.

Y ese es el problema que tengo al enfocarme solo en la cobertura del código. Creo que, en el mejor de los casos, terminas en una situación en la que el desarrollador crea algunas pruebas iniciales basadas en las reglas comerciales. Luego, para aumentar el número de cobertura, hacen referencia a su código cuando diseñan nuevos casos de prueba.

El tipeo manifiesto (que supongo que quiere decir) es una forma de especificación, la prueba unitaria es mucho más débil ya que solo proporciona ejemplos. La diferencia importante es que una especificación declara lo que debe contener en cualquier caso, mientras que una prueba solo cubre ejemplos. Nunca puede estar seguro de que sus pruebas cubren todas las condiciones de contorno.

Las personas también tienden a olvidar el valor de los tipos declarados como documentación. Por ejemplo, si un método Java devuelve un List<String>, instantáneamente sé lo que obtengo, sin necesidad de leer documentación, casos de prueba o incluso el código del método en sí. Del mismo modo para los parámetros: si se declara el tipo, sé lo que espera el método.

El valor de declarar el tipo de variables locales es mucho más bajo ya que en un código bien escrito el alcance de la existencia de la variable debe ser pequeño. Sin embargo, todavía puede usar la escritura estática: en lugar de declarar el tipo, deja que el compilador lo infiera. Idiomas como Scala o incluso C # le permite hacer exactamente esto.

Algunos estilos de prueba se acercan a una especificación, p. QuickCheck o es la variante Scala ScalaCheck genera pruebas basadas en especificaciones, tratando de adivinar los límites importantes.

Lo diría de una manera diferente: si no tiene un lenguaje estáticamente tipado, tendría mejor pruebas de unidad muy exhaustivas si planea hacer algo " real " con ese código.

Dicho esto, la escritura estática (o más bien, la escritura explícita) tiene algunos beneficios significativos sobre las pruebas unitarias que me hacen preferirla en general. Crea API mucho más comprensibles y permite una visualización rápida de & Quot; skeleton & Quot; de una aplicación (es decir, los puntos de entrada a cada módulo o sección de código) de una manera que es mucho más difícil con un lenguaje de tipo dinámico.

En resumen: en mi opinión, dadas pruebas unitarias sólidas y exhaustivas, la elección entre un lenguaje de tipo dinámico y un lenguaje de tipo estático es principalmente de gusto. Algunas personas prefieren uno; otros prefieren al otro. Use la herramienta adecuada para el trabajo. Pero esto no significa que sean idénticos: los idiomas de tipo estático siempre tendrán una ventaja de ciertas maneras, y los idiomas de tipo dinámico siempre tendrán una ventaja de ciertas maneras diferentes. Las pruebas unitarias contribuyen en gran medida a minimizar las desventajas de los lenguajes de tipo dinámico, pero no los eliminan por completo.

No.

Pero esa no es la pregunta más importante, la pregunta más importante es: ¿importa que no pueda?

Considere el propósito de la verificación de tipo estático: evitar una clase de defectos de código (errores). Sin embargo, esto debe sopesarse en el contexto del dominio más grande de todos los defectos del código. Lo que más importa no es una comparación a lo largo de una franja estrecha, sino una comparación a lo largo y ancho de la calidad del código, la facilidad de escribir el código correcto, etc. Si puede llegar a un estilo / proceso de desarrollo que permita a su equipo producir una mayor calidad codifique de manera más eficiente sin verificación de tipo estático, entonces vale la pena. Esto es cierto incluso en el caso de que tenga agujeros en sus pruebas que atraparían la verificación de tipo estático.

Supongo que podría hacerlo si eres muy minucioso. ¿Pero por qué molestarse? Si el idioma ya está comprobando para asegurarse de que los tipos estáticos son correctos, no tendría sentido probarlos (ya que lo obtiene de forma gratuita).

Además, si está utilizando lenguajes de tipo estático con un IDE, el IDE puede proporcionarle errores y advertencias, incluso antes de compilar para probar. No estoy seguro de que haya aplicaciones de prueba unitarias automatizadas que puedan hacer lo mismo.

Dadas todas las ventajas de los lenguajes dinámicos y de enlace tardío, supongo que ese es uno de los valores ofrecidos por Unit Tests. Aún necesitará codificar con cuidado e intencionalmente, pero ese es el requisito # 1 para cualquier tipo de codificación en mi humilde opinión. Ser capaz de escribir pruebas claras y simples ayuda a demostrar la claridad y simplicidad de su diseño y su implementación. También proporciona pistas útiles para aquellos que ven su código más adelante. Pero no creo que cuente con él para detectar tipos no coincidentes. Pero en la práctica no encuentro que la verificación de tipos realmente capture muchos errores reales de todos modos. Simplemente no es un tipo de error que encuentro que ocurre en el código real, si tiene un estilo de codificación claro y simple en primer lugar.

Para javascript, esperaría que jsLint encuentre casi todos los problemas de verificación de tipo. principalmente sugiriendo estilos de codificación alternativos para disminuir su exposición.

La verificación de tipo ayuda a hacer cumplir los contratos entre componentes en un sistema. Las pruebas unitarias (como su nombre lo indica) verifican la lógica interna de los componentes.

Para una sola unidad de código, creo que las pruebas unitarias realmente pueden hacer innecesaria la verificación de tipo estático. Pero en un sistema complejo, las pruebas automatizadas no pueden verificar todas las formas en que los diferentes componentes del sistema pueden interactuar. Para esto, el uso de interfaces (que son, en cierto sentido, una especie de & Quot; contract & Quot; entre componentes) se convierte en una herramienta útil para reducir posibles errores. Y las interfaces requieren verificación de tipo en tiempo de compilación.

Realmente disfruto programando en lenguajes dinámicos, así que ciertamente no estoy criticando la escritura dinámica. Esto es solo un problema que se me ocurrió recientemente. Lamentablemente, no tengo ninguna experiencia en el uso de un lenguaje dinámico para un sistema grande y complejo, por lo que me interesaría saber de otras personas si este problema es real o meramente teórico.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top