language agnostic - español - Diseño por contrato y desarrollo basado en pruebas
test driven development español (8)
Estoy trabajando para mejorar el proceso de desarrollo de nuestro grupo, y estoy considerando la mejor forma de implementar Design By Contract con Test-Driven Development. Parece que las dos técnicas tienen mucha superposición, y me preguntaba si alguien tenía alguna idea sobre las siguientes preguntas (relacionadas):
- ¿No está en contra del principio DRY tener TDD y DbC a menos que esté usando algún tipo de generador de código para generar pruebas unitarias basadas en contratos? De lo contrario, debe mantener el contrato en dos lugares (la prueba y el contrato en sí), ¿o me falta algo?
- ¿En qué medida TDD hace que DbC sea redundante? Si escribo las pruebas lo suficientemente bien, ¿no son equivalentes a escribir un contrato? ¿Solo obtengo un beneficio adicional si hago cumplir el contrato en el tiempo de ejecución así como a través de las pruebas?
- ¿Es significativamente más fácil / más flexible usar solo TDD en lugar de TDD con DbC?
El punto principal de estas preguntas es esta pregunta más general: si ya estamos haciendo TDD correctamente, ¿obtendremos un beneficio significativo por los gastos generales si también usamos DbC?
Un par de detalles, aunque creo que la pregunta es en gran parte agnóstica del lenguaje:
- Nuestro equipo es muy pequeño, <10 programadores.
- Usamos principalmente Perl.
Creo que hay una superposición entre DbC y TDD, sin embargo, no creo que haya un trabajo duplicado: la introducción de DbC probablemente dará como resultado una reducción de los casos de prueba.
Dejame explicar.
En TDD, las pruebas no son realmente pruebas. Son especificaciones de comportamiento. Sin embargo, también son herramientas de diseño: al escribir la prueba primero, utiliza la API externa de su objeto bajo prueba, que aún no ha escrito, de la misma manera que lo haría un usuario. De esta manera, diseñas la API de una manera que tenga sentido para un usuario, y no de la forma en que lo hace más fácil de implementar. Algo como queue.full?
en lugar de queue.num_entries == queue.size
.
Esta segunda parte no puede ser reemplazada por Contratos.
La primera parte puede ser parcialmente reemplazada por contratos, al menos para pruebas unitarias. Las pruebas TDD sirven como especificaciones de comportamiento, tanto para otros desarrolladores (pruebas unitarias) como para expertos de dominio (pruebas de aceptación). Los contratos también especifican el comportamiento, para otros desarrolladores, para expertos de dominio, pero también para el compilador y la biblioteca de tiempo de ejecución.
Pero los contratos tienen una granularidad fija: tiene pre y poscondiciones de métodos, invariantes de objetos, contratos de módulos, etc. Tal vez variantes de bucle e invariantes. Las pruebas unitarias, sin embargo, prueban las unidades de comportamiento. Esos podrían ser más pequeños que un método o consistir en múltiples métodos. Eso no es algo que puedas hacer con los contratos. Y para el "panorama general", aún necesita pruebas de integración, pruebas funcionales y pruebas de aceptación.
Y hay otra parte importante de TDD que DbC no cubre: la D. intermedia En TDD, las pruebas controlan su proceso de desarrollo: nunca escribe una sola línea de código de implementación a menos que tenga una prueba fallida, nunca escriba una sola línea del código de prueba a menos que todas sus pruebas pasen, solo escribe la cantidad mínima de código de implementación para que las pruebas pasen, solo escribe la cantidad mínima de código de prueba para producir una prueba fallida.
En conclusión: use pruebas para diseñar el "flujo", la "sensación" de la API. Use contratos para diseñar el, bueno, contrato de la API. Use pruebas para proporcionar el "ritmo" para el proceso de desarrollo.
Algo como esto:
- Escribir una prueba de aceptación para una función
- Escriba una prueba unitaria para una unidad que implemente una parte de esa característica
- Utilizando la firma de método que diseñó en el paso 2, escriba el prototipo del método
- Agregar la poscondición
- Añadir la precondición
- Implementar el cuerpo del método
- Si se aprueba la prueba de aceptación, vaya a 1, de lo contrario, pase a 2
Si quiere saber qué piensa Bertrand Meyer, el inventor de Design by Contract, sobre la combinación de TDD y DbC, hay un buen documento de su grupo, llamado Contract-Driven Design = Test-Driven Development - Writing Test Cases . La premisa básica es que los contratos proporcionan una representación abstracta de todos los casos posibles, mientras que los casos de prueba solo evalúan casos específicos. Por lo tanto, se puede generar automáticamente un arnés de prueba adecuado a partir de los contratos.
Tenga en cuenta las diferencias.
Diseño impulsado por contrato. Diseño impulsado por contrato.
Desarrollar conducido por prueba. Desarrollo impulsado por prueba.
Están relacionados en que uno precede al otro. Describen el software en diferentes niveles de abstracción.
¿Desechas el diseño cuando vas a la implementación? ¿Considera que un documento de diseño es una violación de DRY? ¿Mantiene el contrato y el código por separado?
El software es una implementación del contrato. Las pruebas son otra. El manual del usuario es un tercero. La guía de operaciones es una cuarta. Los procedimientos de copia de seguridad / restauración de la base de datos son parte de una implementación del contrato.
No puedo ver ninguna sobrecarga de Design by Contract.
Si ya está diseñando, cambie el formato de demasiadas palabras a las palabras correctas para delinear la relación contractual.
Si no está diseñando, redactar un contrato eliminará los problemas, reduciendo los costos y la complejidad.
No puedo ver ninguna pérdida de flexibilidad.
comenzar con un contrato,
entonces
a. escribir pruebas y
segundo. escribir código
Vea cómo las dos actividades de desarrollo están esencialmente entrelazadas y ambas provienen del contrato.
Yo podria agregar:
la API es el contrato para los programadores, la definición de la interfaz de usuario es el contrato con los clientes, el protocolo es el contrato para las interacciones cliente-servidor. Primero, primero puedes aprovechar las pistas de desarrollo paralelo y no perderte en las malas hierbas. Sí, revise periódicamente para asegurarse de que se cumplan los requisitos, pero nunca comience una nueva ruta sin el contrato. Y ''contrato'' es una palabra fuerte: una vez desplegado, nunca debe cambiar. Debe incluir administración de versiones e introspección desde el principio, los cambios en el contrato solo se implementan mediante conjuntos de extensiones, los números de versión cambian con estos, y luego puede hacer cosas como una degradación elegante cuando se trata de instalaciones mixtas o antiguas.
Aprendí esta lección de la manera difícil, con un gran proyecto que se desvió hacia la tierra de nunca jamás, y luego lo apliqué de la manera correcta más adelante cuando estuve seriamente bajo el arma, la supervivencia de la compañía, la corta línea de tiempo del fusible. Definimos el protocolo, definimos y escribimos un conjunto de emulaciones de protocolo para cada lado de las transacciones (básicamente generadores de mensajes enlatados y comprobador de mensajes recibidos, una noche de codificación de dos cerebros), luego dividimos para escribir por separado el servidor y el cliente finaliza de la aplicación. Nos recombinamos la noche del espectáculo, y simplemente funcionó. Requisitos, diseño, contrato, prueba, código, integrar. En ese orden. Repita hasta que esté cocido.
Estoy un poco receloso del diseño de TLA. Al igual que con Patterns, las recetas que cumplen con las palabras de moda son una buena guía, pero mi experiencia es que no existe un procedimiento de administración de proyectos o diseño único para todos. Si está haciendo las cosas precisamente por The Book (tm), entonces, a menos que sea un contrato de DOD con los requisitos de procedimiento del DOD, probablemente se meta en problemas en algún momento del camino. Lea el (los) Libro (s), sí, pero asegúrese de entenderlos, y luego tenga en cuenta también el lado de las personas de su equipo. Las reglas que solo son aplicadas por el Libro no se aplicarán de manera uniforme, incluso cuando se aplican herramientas puede haber abandonos (por ejemplo, los comentarios svn quedan vacíos o crípticamente breves). Los procedimientos solo tienden a seguirse cuando la cadena de herramientas no solo los impone sino que hace que el seguimiento sea más fácil que cualquier atajo posible. Créanme, cuando las cosas se ponen difíciles, se encuentran los atajos, y es posible que no sepan de los que se usaron a las 3 de la mañana hasta que sea demasiado tarde.
Microsoft ha trabajado en la generación automática de pruebas unitarias, basada en contratos de código y pruebas de unidades parametrizadas. Por ejemplo, el contrato dice que el recuento debe aumentarse en uno cuando se agrega un elemento a una colección, y la prueba de unidad parametrizada indica cómo agregar "n" elementos a una colección. Pex intentará crear una prueba unitaria que pruebe que el contrato está roto. Vea este video para una descripción general.
Si esto funciona, su unidad de prueba solo tendrá que escribirse para un ejemplo de cada cosa que está tratando de probar, y PEX podrá entonces determinar qué elementos de datos romperán la prueba.
También puede usar pruebas de aceptación ejecutables que están escritas en el lenguaje de dominio del contrato. Puede que no sea el "contrato" real, pero a mitad de camino entre las pruebas unitarias y el contrato.
Recomendaría usar Ruby Cucumber http://github.com/aslakhellesoy/cucumber
Pero como eres una tienda de Perl, entonces tal vez puedas usar mi pequeño intento de p5-pepino. http://github.com/kesor/p5-cucumber
Tuve algunas reflexiones sobre ese tema hace algún tiempo.
Es posible que desee echar un vistazo a
Cuando usa TDD para implementar un nuevo método, necesita alguna información: necesita conocer las afirmaciones para verificar sus pruebas. El diseño por contrato le proporciona esas afirmaciones: son las condiciones posteriores e invariantes del método.
He encontrado que DbC es muy útil para reiniciar el ciclo de refactorización rojo-verde porque ayuda a identificar las pruebas unitarias para empezar. Con DbC empiezo a pensar en las condiciones previas que debe manejar el objeto TDD-ed y cada condición previa puede representar una prueba de falla de la unidad para comenzar un ciclo de refactorización rojo-verde. En algún punto, cambio para comenzar el ciclo con una prueba de unidad fallida para una condición posterior , y luego simplemente mantengo el flujo de TDD en funcionamiento. He intentado este enfoque con los recién llegados a TDD y realmente funciona para poner en marcha la mentalidad de TDD.
En resumen, piense en DbC como una forma efectiva de identificar las pruebas clave de la unidad de comportamiento. DbC ayuda a analizar las entradas (condiciones previas) y las salidas (condiciones posteriores), que son las dos cosas que necesitamos controlar (entradas) y observar (salidas) para escribir software comprobable (un objetivo similar de TDD).