Notas preliminares
No voy a entrar en la distinción de los diferentes tipos de pruebas que hay, hay ya un par de preguntas sobre estos sitios con respecto a eso.
Tomaré lo que hay allí y eso dice: prueba de unidad en el sentido de "probar la unidad aislable más pequeña de una aplicación" de la cual esta pregunta realmente deriva
El problema de aislamiento
¿Cuál es la unidad aislable más pequeña de un programa? Bueno, como yo lo veo, ¿depende de qué idioma estás codificando?
Micheal Feathers habla sobre el concepto de costura : [WEwLC, p31]
Una costura es un lugar donde puede alterar el comportamiento en su programa sin editar en ese lugar.
Y sin entrar en detalles, entiendo que una costura, en el contexto de las pruebas unitarias, es un lugar en un programa donde su "prueba" puede interactuar con su "unidad".
Ejemplos
La prueba de unidad, especialmente en C ++, requiere del código bajo prueba para agregar más costuras que se requerirían estrictamente para un problema determinado.
Ejemplo:
- Agregar una interfaz virtual donde la implementación no virtual hubiera sido suficiente
- División - generalización (?) - una clase (más pequeña) más "justa" para facilitar la adición de una prueba.
- Dividir un proyecto ejecutable único en bibliotecas aparentemente "independientes", "solo" para facilitar su compilación independiente para las pruebas.
La pregunta
Probaré algunas versiones que con suerte preguntarán sobre el mismo punto:
- Es la forma en que las Pruebas unitarias requieren que uno estructura el código de una aplicación "solo" beneficioso para las pruebas unitarias o es realmente beneficioso para la estructura de las aplicaciones.
- ¿La generalización del código que se necesita para que sea comprobable por unidad es útil para cualquier cosa que no sean las pruebas unitarias?
- ¿Agregar pruebas unitarias obliga a uno a generalizar innecesariamente?
- ¿La unidad de forma prueba la fuerza en el código "siempre" también es una buena forma para el código en general como se ve desde el dominio del problema?
Recuerdo una regla general que decía que no generalices hasta que necesites / hasta que haya un segundo lugar que use el código. Con las pruebas unitarias, siempre hay un segundo lugar que usa el código, es decir, la prueba unitaria. Entonces, ¿es esta razón suficiente para generalizar?
fuente
Respuestas:
Solo si no considera probar una parte integral de la resolución de problemas. Para cualquier problema no trivial, debería serlo, no solo en el mundo del software.
En el mundo del hardware, esto se aprendió hace mucho tiempo, por las malas. Los fabricantes de diversos equipos han aprendido a través de siglos de innumerables puentes que caen, coches en explosión, CPUs humeantes, etc., etc., lo que estamos aprendiendo ahora en el mundo del software. Todos ellos construyen "costuras adicionales" en sus productos para hacerlos verificables. La mayoría de los automóviles nuevos hoy en día cuentan con puertos de diagnóstico para que los reparadores obtengan datos sobre lo que está sucediendo dentro del motor. Una parte importante de los transistores en cada CPU tiene fines de diagnóstico. En el mundo del hardware, cada bit de cosas "adicionales" cuesta, y cuando un producto es fabricado por millones, estos costos seguramente suman grandes sumas de dinero. Aún así, los fabricantes están dispuestos a gastar todo este dinero para la comprobabilidad.
Volviendo al mundo del software, C ++ es más difícil de probar que los lenguajes posteriores con carga de clase dinámica, reflexión, etc. Sin embargo, la mayoría de los problemas pueden al menos mitigarse. En el único proyecto C ++ en el que utilicé pruebas unitarias hasta el momento, no ejecutamos las pruebas con tanta frecuencia como lo haríamos, por ejemplo, en un proyecto Java, pero aún así formaban parte de nuestra compilación de CI, y las encontramos útiles.
En mi experiencia, un diseño comprobable es beneficioso en general, no "solo" para las pruebas unitarias en sí. Estos beneficios vienen en diferentes niveles:
Si puede demostrar que su software hace exactamente lo que se supone que debe hacer, y demostrarlo de una manera suficientemente rápida, repetible, barata y determinista para satisfacer a sus clientes, sin la generalización "extra" o costuras forzadas por las pruebas unitarias, hágalo. (y háganos saber cómo lo hace, porque estoy seguro de que mucha gente en este foro estaría tan interesada como yo :-)
Por cierto, supongo que por "generalización" te refieres a cosas como la introducción de una interfaz (clase abstracta) y el polimorfismo en lugar de una sola clase concreta; de lo contrario, aclara.
fuente
Voy a lanzar The Way of Testivus , pero para resumir:
Si está gastando una gran cantidad de tiempo y energía haciendo que su código sea más complicado para probar una sola parte del sistema, puede ser que su estructura sea incorrecta o que su enfoque de prueba sea incorrecto.
La guía más simple es esta: lo que está probando es la interfaz pública de su código en la forma en que está destinado a ser utilizado por otras partes del sistema.
Si sus pruebas se vuelven largas y complicadas, es una indicación de que usar la interfaz pública será difícil.
Si tiene que usar la herencia para permitir que su clase sea utilizada por otra cosa que no sea la instancia única para la que se usará actualmente, entonces hay una buena posibilidad de que su clase esté demasiado vinculada a su entorno de uso. ¿Puedes dar un ejemplo de una situación en la que esto sea cierto?
Sin embargo, tenga cuidado con el dogma de las pruebas unitarias. Escriba la prueba que le permita detectar el problema que hará que el cliente le grite .
fuente
TDD y Unit Testing, es bueno para el programa en su conjunto, y no solo para las pruebas unitarias. La razón de esto es porque es bueno para el cerebro.
Esta es una presentación sobre un marco específico de ActionScript llamado RobotLegs. Sin embargo, si hojeas las primeras 10 diapositivas más o menos, comienza a llegar a las partes buenas del cerebro.
Las pruebas de TDD y Unidad lo obligan a comportarse de una manera que sea mejor para que el cerebro procese y recuerde la información. Entonces, si bien su tarea exacta frente a usted es simplemente hacer una mejor prueba de unidad, o hacer que el código sea más comprobable por unidad ... lo que realmente hace es hacer que su código sea más legible y, por lo tanto, hacer que su código sea más fácil de mantener. Esto le permite codificar los habbits más rápido y le permite comprender su código más rápido cuando necesita agregar / eliminar funciones, corregir errores o, en general, abrir el archivo fuente.
fuente
esto es cierto, pero si lo lleva demasiado lejos, no le da mucho, y cuesta mucho, y creo que es este aspecto el que promueve el uso del término BDD para que sea lo que TDD debería haber sido. a lo largo: la unidad aislable más pequeña es lo que quieres que sea.
Por ejemplo, una vez depuré una clase de red que tenía (entre otros bits) 2 métodos: 1 para configurar la dirección IP, otro para configurar el número de puerto. Naturalmente, estos eran métodos muy simples y pasarían la prueba más trivial fácilmente, pero si configura el número de puerto y luego la dirección IP, no funcionaría: el configurador de IP sobrescribía el número de puerto de forma predeterminada. Así que tuvo que probar la clase en su conjunto para garantizar un comportamiento correcto, algo que creo que falta el concepto de TDD, pero BDD le brinda. Realmente no necesita probar cada método pequeño, cuando puede probar el área más sensible y más pequeña de la aplicación general, en este caso la clase de red.
En última instancia, no hay una bala mágica para las pruebas, debe tomar decisiones sensatas sobre cuánto y a qué granularidad aplicar sus recursos de prueba limitados. El enfoque basado en herramientas que genera automáticamente trozos para usted no hace esto, es un enfoque de fuerza contundente.
Entonces, dado esto, no necesita estructurar su código de una determinada manera para lograr TDD, pero el nivel de prueba que logre dependerá de la estructura de su código, si tiene una GUI monolítica que tiene toda su lógica estrechamente vinculada a la estructura de la GUI, entonces le resultará más difícil aislar esas piezas, pero aún puede escribir una prueba de unidad donde 'unidad' se refiere a la GUI y todo el trabajo de base de datos de fondo se burla. Este es un ejemplo extremo, pero muestra que aún puede hacer pruebas automáticas en él.
Un efecto secundario de estructurar su código para que sea más fácil probar unidades más pequeñas lo ayuda a definir mejor la aplicación, y eso le permite reemplazar partes más fácilmente. También ayuda cuando se codifica, ya que será menos probable que 2 desarrolladores trabajen en el mismo componente en un momento dado, a diferencia de una aplicación monolítica que tiene dependencias entremezcladas que interrumpen el trabajo de todos los demás.
fuente
Has alcanzado una buena comprensión de las compensaciones en el diseño del lenguaje. Algunas de las decisiones centrales de diseño en C ++ (el mecanismo de función virtual mezclado con el mecanismo de llamada de función estática) hacen que TDD sea difícil. El lenguaje no es realmente compatible con lo que necesita para facilitarlo. Es fácil escribir C ++ que es casi imposible de realizar una prueba unitaria.
Hemos tenido mejor suerte al hacer nuestro código TDD C ++ a partir de funciones de escritura de mentalidad cuasifuncional, no de procedimientos (una función que no toma argumentos y devuelve nulo), y usar composición siempre que sea posible. Dado que es difícil sustituir estas clases miembro, nos enfocamos en probar esas clases para construir una base confiable, y luego sabemos que la unidad básica funciona cuando la agregamos a otra cosa.
La clave es el enfoque cuasifuncional. Piénselo, si todo su código C ++ fuera funciones libres que no tienen acceso a globales, eso sería una prueba rápida de unidad :)
fuente