¿Vale la pena la inyección de dependencia fuera de UnitTesting?

17

Dado un constructor que nunca tendrá que usar implementaciones diferentes de varios objetos que inicializa, ¿sigue siendo práctico usar DI? Después de todo, aún podríamos querer hacer una prueba unitaria.

La clase en cuestión inicializa algunas otras clases en su constructor y las clases que usa son bastante específicas. Nunca usará otra implementación. ¿Tenemos justificación para evitar intentar programar en una interfaz?

Seph
fuente
3
BESO : siempre es el mejor enfoque.
Reactgular
55
En mi opinión, esta pregunta es más específica que el duplicado propuesto. diseño para futuros cambios de-o-resolver-el-problema-en-mano , voto por lo tanto, a no cierra este
k3b
1
¿No es la capacidad de prueba suficiente razón?
ConditionRacer
Realmente no suena como un duplicado sino como una respuesta. Si nunca va a suceder, diseñar para él no es diseñar para futuros cambios. Incluso los astronautas de la arquitectura solo diseñan cambios que nunca sucederán por mal juicio.
Steve314

Respuestas:

13

Depende de qué tan seguro esté de que "nunca, nunca" es correcto (durante el tiempo que se utilizará su aplicación).

En general:

  • No modifique su diseño solo para comprobarlo. Las pruebas unitarias están ahí para servirle, no al revés.
  • No te repitas a ti mismo. Hacer una interfaz que solo tendrá un heredero es inútil.
  • Si una clase no es comprobable, es probable que tampoco sea flexible / extensible para casos de uso reales. A veces está bien, es poco probable que necesites esa flexibilidad, pero la simplicidad / fiabilidad / mantenibilidad / rendimiento / lo que sea más importante.
  • Si no sabe, codifique en la interfaz. Cambio de requisitos.
Telastyn
fuente
12
Casi te dio -1 por "no modifiques tu diseño solo por la capacidad de prueba". ¡Para ganar comprobabilidad es IMHO una de las principales razones para cambiar un diseño! Pero tus otros puntos están bien.
Doc Brown el
55
@docBrown - (y otros) Yo difiero aquí de muchos, y quizás incluso de la mayoría. ~ 95% del tiempo, hacer que las cosas sean comprobables también las hace flexibles o extensibles. Debería tener eso en su diseño (o agregarlo a su diseño) la mayor parte del tiempo: la capacidad de prueba puede ser condenada. El otro ~ 5% tiene requisitos extraños que evitan este tipo de flexibilidad a favor de otra cosa, o es tan esencial / inmutable que descubrirá con bastante rapidez si está roto, incluso sin pruebas específicas.
Telastyn
44
puede estar seguro, pero su primera declaración anterior podría malinterpretarse con demasiada facilidad como "deje un código mal diseñado como está cuando su única preocupación es la comprobabilidad".
Doc Brown
1
¿Por qué diría que es inútil crear una interfaz que solo tenga un heredero? La interfaz puede funcionar como un contrato.
kaptan
2
@kaptan: porque el objeto concreto puede funcionar tan bien como un contrato. Hacer dos solo significa que debe escribir el código dos veces (y modificar el código dos veces cuando cambie). Por supuesto, la gran mayoría de las interfaces tendrán implementaciones múltiples (que no sean de prueba) o, en caso de que necesite prepararse para esa eventualidad. Pero para ese raro caso en el que todo lo que necesita es un simple objeto sellado, simplemente utilícelo directamente.
Telastyn
12

¿Tenemos justificación para evitar intentar programar en una interfaz?

Si bien la ventaja de codificar en una interfaz es bastante evidente, debe preguntarse qué se gana exactamente al no hacerlo.

La siguiente pregunta es: ¿es parte de la responsabilidad de la clase en cuestión elegir las implementaciones o no? Ese puede o no ser el caso y debe actuar en consecuencia.

En última instancia, siempre existe la posibilidad de que surja alguna restricción tonta de la nada. Es posible que desee ejecutar el mismo código al mismo tiempo, y la posibilidad de inyectar la implementación podría ayudar con la sincronización. O después de perfilar su aplicación, puede descubrir que desea una estrategia de asignación diferente de la instanciación simple. O surgen inquietudes transversales y no tiene el apoyo de AOP a mano. ¿Quién sabe?

YAGNI sugiere que al escribir código, es importante no agregar nada superfluo. Una de las cosas que los programadores tienden a agregar a su código sin siquiera darse cuenta, son supuestos superfluos. Como "este método puede ser útil" o "esto nunca cambiará". Ambos agregan desorden a su diseño.

back2dos
fuente
2
+1 por citar a YAGNI aquí como argumento para DI, no en contra.
Doc Brown
44
@DocBrown: Teniendo en cuenta que YAGNI puede usarse aquí, para justificar también no usar DI. Creo que es más un argumento en contra de que YAGNI sea una medida útil para cualquier cosa.
Steven Evers
1
+1 por supuestos superfluos incluidos en YAGNI, que nos han golpeado en la cabeza varias veces
Izkata
@SteveEvers: Creo que usar YAGNI para justificar ignorar el principio de inversión de dependencia impide una falta de comprensión de YAGNI o DIP o tal vez incluso algunos de los principios más fundamentales de programación y razonamiento. Solo debe ignorar el DIP si es necesario , una circunstancia en la que YAGNI simplemente no es aplicable por su propia naturaleza.
back2dos
@ back2dos: la suposición de que DI es útil debido a 'quizás-requisitos' y "¿quién sabe?" es precisamente el tren de pensamiento que YAGNI está destinado a combatir, en lugar de eso lo estás utilizando como justificación para herramientas e infraestructura adicionales.
Steven Evers
7

Depende de varios parámetros, pero aquí hay algunas razones por las que aún podría querer usar DI:

  • la prueba es una muy buena razón en sí misma, creo
  • las clases requeridas por su objeto podrían usarse en otros lugares; si las inyecta, le permite agrupar los recursos (por ejemplo, si las clases en cuestión son hilos o conexiones de base de datos, no desea que cada una de sus clases cree nuevos conexiones)
  • Si la inicialización de esos otros objetos puede fallar, el código más arriba en la pila probablemente será mejor para tratar los problemas que su clase, lo que probablemente simplemente reenviará los errores

En pocas palabras: probablemente pueda vivir sin DI en ese caso, pero hay posibilidades de que el uso de DI termine en un código más limpio.

asilias
fuente
3

Cuando diseña un programa o una característica, parte del proceso de pensamiento debe ser cómo lo va a probar. La inyección de dependencia es una de las herramientas de prueba y debe considerarse como parte de la mezcla.

Los beneficios adicionales de la Inyección de dependencias y el uso de interfaces en lugar de clases también son beneficiosos cuando se extiende una aplicación, pero también se pueden adaptar al código fuente más tarde de manera bastante fácil y segura mediante la búsqueda y el reemplazo. - Incluso en proyectos muy grandes.

Michael Shaw
fuente
2
 > Dependency Injection worth it **outside** of UnitTesting?
 > Are we justified in avoiding trying to program to an interface?

Muchas respuestas a esta pregunta se pueden debatir como "es posible que lo necesite porque ..." como YAGNI si no desea realizar pruebas unitarias.

Si está preguntando qué razones, además de las pruebas unitarias, requieren programar en una interfaz

, la inyección de dependencia vale la pena fuera de UnitTesting si necesita una inversión de control . Por ejemplo, si la implementación de un módulo necesita otro módulo al que no se puede acceder en su capa.

Ejemplo: si tiene los módulos gui=> businesslayer=> driver=> common.

En este escenario businesslayerpuede usar driverpero driverno puede usar businesslayer.

Si drivernecesita alguna funcionalidad de nivel superior, puede implementar esta funcionalidad en la businesslayerque implementa una interfaz en commoncapa. driversolo necesita conocer la interfaz en commoncapa.

k3b
fuente
1

Sí, en primer lugar, olvídate de las pruebas unitarias como una razón para diseñar tu código en torno a las herramientas de prueba unitarias, nunca es una buena idea doblar el diseño de tu código para que se ajuste a una restricción artificial. Si sus herramientas lo obligan a hacerlo, obtenga mejores herramientas (por ejemplo, Microsoft Fakes / Moles que le permiten muchas más opciones para crear objetos simulados).

Por ejemplo, ¿dividiría sus clases en métodos solo públicos solo porque las herramientas de prueba no funcionan con métodos privados? (Sé que la sabiduría predominante es fingir que no es necesario probar métodos privados, pero creo que esto es una reacción a la dificultad de hacerlo con las herramientas actuales, no una reacción genuina a la no necesidad de probar pruebas privadas).

En general, todo se reduce a qué tipo de TDDer eres: el "simulacro" como los describe Fowler , necesita cambiar el código para adaptarlo a las herramientas que usa, mientras que los probadores "clásicos" crean pruebas que son más integrales en la naturaleza. (es decir, pruebe la clase como una unidad, no cada método) para que haya menos necesidad de interfaces, especialmente si utiliza las herramientas que pueden burlarse de clases concretas.

gbjbaanb
fuente
3
No creo que la sabiduría predominante de que los métodos privados no deberían probarse tenga algo que ver con la dificultad de probarlos. Si hubo un error en un método privado, pero no afectó el comportamiento del objeto, entonces no afecta a nada. Si hubo un error en un método privado que afectó el comportamiento del objeto, entonces hay una prueba fallida o faltante en al menos uno de los métodos públicos de un objeto.
Michael Shaw el
Eso se reduce a si evalúa el comportamiento o el estado. Los métodos privados deben probarse de alguna manera, obviamente, pero si eres una persona TDD clásica, los probarás probando la clase "en su conjunto", las herramientas de prueba que la mayoría de las personas usan se centran en probar cada método individualmente ... y mi punto es que estos últimos no están probando adecuadamente, ya que pierden la oportunidad de realizar una prueba completa. Así que estoy de acuerdo con usted, mientras trato de resaltar cómo TDD ha sido subvertido por cómo algunas personas (¿la mayoría?) Hacen pruebas unitarias hoy.
gbjbaanb
1

La inyección de dependencia también aclara que su objeto TIENE dependencias.

Cuando alguien más vaya a usar su objeto ingenuamente (sin mirar al constructor), descubrirá que necesitará configurar servicios, conexiones, etc. Eso no era obvio porque no tenía que pasarlos al constructor. . Pasar las dependencias deja más claro lo que se necesita.

También está ocultando potencialmente el hecho de que su clase puede estar violando SRP. Si está pasando muchas dependencias (más de 3), su clase puede estar haciendo demasiado y debería ser refactorizada. Pero si los está creando en el constructor, este olor a código se ocultará.

Schleis
fuente
0

Estoy de acuerdo con ambos campos aquí y el asunto aún está abierto a debate en mi opinión. YAGNI exige que no aborde el cambio de mi código para que se ajuste a la suposición. Las pruebas unitarias no son un problema aquí porque creo firmemente que la dependencia nunca cambiará. Pero si comenzara a realizar las pruebas unitarias, nunca habría llegado a este punto de todos modos. Como me habría deslizado en DI finalmente.

Permítanme ofrecer otra alternativa para mi escenario específicamente

Con un patrón de fábrica puedo localizar dependencias en un solo lugar. Al probar, puedo cambiar la implementación según el contexto, y eso es lo suficientemente bueno. Esto no va en contra de YAGNI debido a los beneficios de abstraer varias construcciones de clase.

Seph
fuente