design - open - TDD: ¿se interpone en el camino del buen diseño de API?
swagger (14)
Con el diseño de API tradicional, es fácil incorporarse a una esquina: puede terminar con una API que tiene muchas dependencias ocultas (como la clase A necesita B necesita C necesita D y si cambia el orden en que se inicializan las clases, las cosas comienzan a romperse).
TDD se asegura de que las piezas separadas permanezcan separadas. También le permite ver su API desde una perspectiva muy inusual: como usuario / consumidor. Su primera pregunta es "¿Cómo quiero usar esto?" no "¿Cómo quiero que se vea la API?" Este último puede atraerte a una trampa oculta mientras que el primero conduce a algo que yo llamo "API intuitiva": se comporta como se esperaba.
Un consejo: no hagas de TDD tu religión. Es una herramienta y algunos problemas no se pueden resolver con algunas herramientas. Entonces, si TDD no funciona para usted por algún motivo, está bien. Use tanto TDD como desee. No seas fanático al respecto. Con los años, encontrarás tu zona de confort.
Nunca he escrito el código TDD, pero he visto una cantidad decente de discusión aquí en SO. Mi mayor preocupación es que parece que el buen diseño general de la API (por flexibilidad, facilidad de uso, simplicidad de la interfaz y rendimiento) deja de ser un elemento secundario para hacer que el código se pueda mockable, ultra modular más allá de lo necesario para cualquier uso de API caso, etc. Por ejemplo, los defensores de TDD a menudo sugieren que las cosas pasen como parámetros que, desde una perspectiva de abstracción API, el método que se llama debería "simplemente saber", o que las clases y métodos se factorizan de una manera que hace que las pruebas sean fáciles , que no es necesariamente la forma en que se relaciona mejor con el dominio del problema.
Para las personas con más experiencia con el diseño de TDD y API: ¿encuentra que TDD a menudo se interpone en el camino de un buen diseño de API? Si es así, ¿cómo lo contrarrestas?
El punto sobre TDD es que escribimos pruebas que llaman al código a medida que lo diseñamos. En consecuencia, deberíamos terminar con una interfaz extremadamente útil. Por supuesto que no sucede completamente por casualidad. Todavía tenemos que tomar las decisiones correctas.
Es posible que desee echar un vistazo al trabajo de lenguaje de newspeak por parte de ao Gilad Bracha para ver algún diseño de API y lenguaje relevante.
Estoy de acuerdo al 100% con las respuestas principales. Pero realmente esto depende de lo que quiere decir con "buen diseño de API". TDD lleva a un código de trabajo comprobable, modular, pero al final, el aspecto más importante del código TDD es la capacidad de prueba.
Descubrirá que conduce a un diseño diferente del rendimiento de otros procesos. Descubrirá que el código comprobable puede exponer algunos bits más de sus componentes internos de lo que una API pura podría exponer.
En muchos casos, puede tener sentido usar TDD para construir código de trabajo y luego, como un paso separado, extraer la API. De lo contrario, tienes dos fuerzas que trabajan un tanto en conflicto: una API simple y un código comprobable.
Sin embargo, este es un punto sutil y, en general, TDD proporcionará API mucho mejores que el diseño desde una torre de marfil.
He estado usando TDD durante varios años y descubro que impulsa un diseño de API hacia un diseño más utilizable al proporcionarle dos clientes diferentes para la API desde el principio; tiene un código de producción y un código de prueba que desean impulsar la API de diferentes maneras.
Es cierto que a veces agrego cosas por el simple hecho de que sea más fácil probar la API, pero casi siempre encuentro que las cosas que creo que estoy haciendo solo por la capacidad de testeo son realmente muy útiles para fines de monitoreo. Entonces, por ejemplo, un FooAllocator
podría terminar con un argumento de constructor opcional que es una interfaz de monitoreo ( IMonitorFooAllocations
) que es muy útil para burlarse durante las pruebas para permitirme echar un vistazo dentro, pero que también tiende a ser muy útil cuando De repente, descubres que debes exponer algunas métricas de asignación al resto del mundo mientras estás en producción. Ahora tiendo a pensar en los bits adicionales que me gustaría agregar para permitir pruebas sencillas en términos de su doble uso para el monitoreo de producción opcional. Generalmente escribo el código del servidor y ser capaz de exponer las partes internas de las cosas como contadores de perfmon es MUY útil ...
Del mismo modo, tienes razón al decir que a menudo los objetos que componen una API pueden tomar algunos otros objetos explícitamente en lugar de llegar y obtenerlos de un lugar conocido, pero esto es algo bueno. Créanme, una vez que se acostumbren a manejar dependencias explícitas, no querrán volver a tener que buscar entre clases para saber cómo y por qué sus Widgets
acceden al directorio activo cuando no hay ninguna pista en la API que deseen hacer tal cosa También es frecuente que rompas estas relaciones de dependencia durante el diseño y las pruebas, y luego las ocultes de nuevo al unir todas las piezas. Aún se ''parametriza desde arriba'', pero la mayoría de las veces un modelo de objetos de API puede significar que nunca se ve realmente ''arriba'' como usuario de la API. Usted termina con un solo lugar para configurar la API con las cosas que necesita y, a menudo, esto no se ve como se vería si tuviera una masa de singletons y globales y dependencias ocultas.
Pero recuerda, TDD es una herramienta, cuando no encaja, no la uses.
La mayoría de las cosas que ha enumerado como desventajas de TDD son consideradas por muchos como elementos de un buen diseño.
Si se tiene en cuenta la idea de que algo se transmita en lugar de algo que el método debería "simplemente saber", este último a menudo conduce a singletons u otros diseños anti-cohesivos.
Si bien puede ser cierto que puede terminar con un diseño donde algunos aspectos solo están ahí para respaldar la capacidad de prueba, esto no es necesariamente algo malo.
Sin embargo, como regla general, si tiene que replantearse su diseño para que sea comprobable, en general obtendrá un diseño que es mejor en términos de cohesión y aptitud para el propósito, al tiempo que es flexible en el sentido de que puede cambiarse fácilmente a cualesquiera que sean sus necesidades futuras a través de la refactorización porque tiene las pruebas allí para darle la confianza de hacer cambios rápidamente.
No me he dado cuenta de que TDD tiene malas elecciones de API.
Por el contrario, al crear un marco claro, en primer lugar, la API también es más clara y más obvia para sus usuarios.
No, creo que TDD generalmente fomenta un buen diseño. Las cosas que son fácilmente comprobables a menudo también son fáciles de usar en la producción ... porque al realizar una prueba, piensas: "¿Qué me gustaría hacer ahora?" y luego haz que funcione
Al practicar TDD, se ve obligado a pensar en los ''casos de uso'' de una API, cómo el programador utilizará las clases / métodos y terminará con un marco muy útil. Si tiene que crear un centenar de FooFactory
y BarAuthenticator
, o si la API se está volviendo ''ultra modular'' como usted dice, es probable que lo haga al escribir su código de prueba y piense cómo simplificarlo.
En cuanto a los parámetros y la inyección de dependencia, generalmente encuentro que las dependencias se vuelven mucho más claras usando TDD. Por lo general, son argumentos de constructor en lugar de argumentos de método, pero dejando en claro que una implementación de API necesita un autenticador, una fuente de aleatoriedad, etc. es útil para comprender lo que está haciendo. Puede estar seguro de que el código no llegará a la red o a una base de datos remota en algún lugar porque oculta estas cosas en su implementación. Este tipo de detalles ocultos es lo que hace que una API sea difícil de usar en un entorno de prueba.
Tenga en cuenta que cuando las dependencias son parte de la llamada al constructor, no es parte de la interfaz que la clase pueda implementar; las dependencias dentro de una interfaz generalmente están ocultas, pero TDD significa que la implementación las expone de manera obvia.
Puede encontrar una buena fuente de información sobre inyección de dependencia y TDD en el Blog de pruebas de Google . Presta especial atención a los mensajes de Miško Hevery o mira algunos de sus videos en Youtube: No busques cosas y more .
Por el contrario, dado que está utilizando sus métodos pensando en el usuario al escribir sus pruebas, detectará los problemas que su usuario presentará cuando los escriba. De hecho, terminará con una interfaz mucho más limpia y fácil de usar que lo que habría escrito de otra manera.
Si su API está sufriendo debido a los requisitos internos de sus objetos, para eso es el patrón de fachada. No todos los objetos deben ser públicos.
Muchos de los puntos dolorosos de TDD son, de hecho, signos de puntos dolorosos en el diseño. Si es difícil crear una clase porque requiere que se pasen 18 dependencias, el problema fundamental es que la clase tiene demasiadas dependencias y va a ser bastante quebradiza. Probablemente también hace mucho más. El "dolor TDD" en este caso es algo bueno, ya que hace que los otros problemas sean más obvios.
TDD es una técnica de diseño API. Cada vez que escribe una prueba unitaria, está creando una API por primera vez o usando una API creada previamente. Cada una de esas pruebas le permite "sentir" cuán fácil o difícil es el uso de la API. Cada nueva prueba te obliga a considerar la API desde un nuevo punto de vista. Difícilmente puede haber una mejor manera de diseñar API que usarlas exhaustivamente de la manera en que TDD te obliga.
De hecho, esta es la razón por la que TDD se considera una técnica de diseño en lugar de una técnica de prueba. Cuando practicas TDD, no diseñas tus API en el vacío. ¡Usted los diseña usándolos !
TDD conduce a un diseño emergente que finalmente produce una API muy útil y extensible. TDD no se trata de probar. Se trata de crear costuras en el código que puede usar para agregar un comportamiento a medida que descubre más acerca de su proyecto.
TDD no obstaculiza un buen diseño de API.
La burla impide el buen diseño de API.
Los dos tampoco son sinónimos: de forma independiente comencé a usar TDD antes de que otros escribieran los libros sobre él, simplemente porque deja en claro si los requisitos son verificables y si los diseños cumplen con un requisito.
Sin embargo, creo que Mocking es malo porque afecta el diseño y la implementación e introduce otros requisitos artificiales. También expone aspectos internos de una implementación que no debería ser, y hace que las pruebas sean frágiles. Prueba cómo se hace algo, en lugar de cómo se hace.
Cómo contrarrestarlo: use TDD, pero no use burlas.
Utilizo TDD y creo que funciona bien con respecto a la construcción de API, pero creo que las API, especialmente las que expones a clientes externos, son un área en la que necesitas un diseño un poco más adelantado que el típico cuando usas TDD. TDD se basa tanto en la refactorización como en la prueba primero. Con una API, no siempre tiene el lujo de refaccionar sus firmas de métodos Y mantener un diseño limpio. Si no tiene cuidado con el diseño de su interfaz por adelantado, puede encontrarse con una interfaz fea que admite múltiples métodos haciendo cosas muy similares solo para mantener la compatibilidad con su API existente al tiempo que avanza su diseño.
Si tengo una API que sé que exponeré a usuarios externos, generalmente me tomo más tiempo para pensar en la firma y obtenerla lo más cerca posible de la "derecha" antes de comenzar el proceso de desarrollo. También lo expongo temprano y con frecuencia a mis clientes para obtener retroalimentación sobre él, de modo que converjamos lo más rápido posible a una interfaz estable. Refactorizar entre bastidores para mejorar la implementación sin cambios en la interfaz no es tanto un problema, pero quiero obtener una interfaz estable rápidamente y estoy dispuesto a invertir más por adelantado para obtenerla.