2010-01-11 9 views
7

Comencé a usar Guice para realizar una inyección de dependencia en un proyecto, principalmente porque necesito inyectar simulaciones (usando JMock actualmente) una capa lejos de la prueba de la unidad, lo que hace que la inyección manual sea muy incómoda.¿Cuál es la mejor manera de usar Guice y JMock juntos?

Mi pregunta es ¿cuál es el mejor enfoque para presentar un simulacro? Lo que tengo actualmente es hacer un nuevo módulo en la prueba de unidad que satisface las dependencias y se unen con un proveedor que tiene este aspecto:

public class JMockProvider<T> implements Provider<T> { 
    private T mock; 

    public JMockProvider(T mock) { 
     this.mock = mock; 
    } 

    public T get() { 
     return mock; 
    } 
} 

Pasando el simulacro en el constructor, por lo que una instalación JMock podría parecer esto:

final CommunicationQueue queue = context.mock(CommunicationQueue.class); 
    final TransactionRollBack trans = context.mock(TransactionRollBack.class); 
    Injector injector = Guice.createInjector(new AbstractModule() { 
     @Override 
     protected void configure() { 
      bind(CommunicationQueue.class).toProvider(new JMockProvider<QuickBooksCommunicationQueue>(queue)); 
      bind(TransactionRollBack.class).toProvider(new JMockProvider<TransactionRollBack>(trans)); 
     } 
    }); 
    context.checking(new Expectations() {{ 
     oneOf(queue).retrieve(with(any(int.class))); 
     will(returnValue(null)); 
     never(trans); 
    }}); 
    injector.getInstance(RunResponse.class).processResponseImpl(-1); 

¿Hay una manera mejor? Sé que AtUnit intenta solucionar este problema, aunque me falta cómo inyecta automáticamente un simulacro creado localmente como el anterior, pero estoy buscando una razón convincente por la que AtUnit sea la respuesta correcta aquí (otro que su capacidad para cambiar los marcos de DI y de burla sin tener que cambiar las pruebas) o si hay una mejor solución para hacerlo a mano.

Respuesta

12

No debería necesitar inyectar simulacros a través de un marco DI. Estoy usando Guice y JMock con bastante éxito y mis pruebas de unidad no hacen referencia a nada relacionado con Guice. Solo uso burlas y paso en null donde corresponda.

DI permitirá la inyección y la construcción de las dependencias de la clase actual, por lo que si desea agregar una clase simulada (que detiene efectivamente el gráfico de dependencia) solo tiene que pasarla. Misko Hevery declaró en una de las Google Tech Talks que las pruebas unitarias deben estar llenas de new y null porque su alcance está localizado en el método de prueba individual de la unidad; tengo que estar de acuerdo con él.

¿Hay alguna razón para necesitar usar Guice en las pruebas, es decir, si no son pruebas funcionales/de integración?

¿No funcionaría la prueba si excluye el inyector? ¿No podría refactorizar su prueba a algo como:

final CommunicationQueue queue = context.mock(CommunicationQueue.class); 
final TransactionRollBack trans = context.mock(TransactionRollBack.class); 

context.checking(new Expectations() {{ 
    oneOf(queue).retrieve(with(any(int.class))); 
    will(returnValue(null)); 
    never(trans); 
}}); 

RunResponse r = new RunResponse(queue, trans); // depending on the order 
r.processResponseImpl(-1); 
+0

En el ejemplo específico, tiene toda la razón que DI no es necesario para burlarse (si hice el paquete constructor privado, supongo), pero desde aquí tengo dependencias que necesitan ser inyectadas a dos pasos del código de prueba (RunResponse obtiene un nombre de clase de la cola y lo ejemplifica), así que estaba mirando a Guice para hacer la inyección de campo para este propósito. – Yishai

+4

Ya veo. Nada le impide usar Guice en las pruebas, pero si es posible, le sugiero que trate de evitarlo (a menos que esté probando en un nivel funcional/de integración) El problema es que está violando la Ley de Demeter . Su clase actual solo debería preocuparse por sus dependencias inmediatas y las dependencias transitivas no deberían tenerse en cuenta. En las pruebas unitarias para las dependencias de las clases actuales, usted hace exactamente lo mismo y solo considera las dependencias inmediatas.Tal patrón hace que su código sea más limpio y más compatible con DI :) – gpampara

1

De lo que ha dicho, parece que usted no está haciendo una unidad de prueba real. Al hacer una prueba unitaria para una clase, solo te enfocas en una sola clase. Cuando ejecutas tu prueba de unidad, la ejecución solo debe ejercitar la clase de sujeto que se probará y evitar ejecutar métodos de clases relacionadas.

JMock/Easymock son herramientas para ayudar a lograr la prueba de unidad de su clase. No son útiles en la prueba de integración.

En el contexto de las pruebas unitarias, Guice se superpone con JMock/Easymock. Guice obliga a los usuarios a contaminar su código con todo tipo de anotaciones (como @Inject) para lograr el mismo resultado que JMock/Easymock. Realmente estoy muy en desacuerdo con Guice en la forma en que ayudan en las pruebas. Si los desarrolladores encuentran que usan Guice en lugar de JMock/Easymock, o bien no realizan una prueba de unidad real o intentan probar las clases de controlador.

La clase de controlador de prueba no se debe realizar en el nivel de prueba unitaria sino en el nivel de prueba de integración. Para la prueba de integración, usar Jakarta Cactus es muy útil. En la prueba de integración, los desarrolladores deben tener todas las dependencias disponibles y, por lo tanto, no es necesario usar Guice.

Como conclusión, no veo ningún motivo para utilizar Guice en la prueba de un producto. Google Guice puede ser útil en diferentes contextos, pero las pruebas.

+0

Gracias Thang, que es básicamente donde estoy ahora, en la mayoría de los casos no uso Guice para probar. Encontré a Guice para conducir un mejor diseño, así que definitivamente está ganando su mantenimiento. Sin embargo, en mi caso, el principio de "una prueba unitaria prueba solo una clase" no es posible, porque una clase necesita conducir a la instanciación de otra a través de un marco. Es complicado por qué este es el caso, pero no hay una buena manera (como en una forma sin otros inconvenientes que afectan la robustez de la producción) de su entorno, lo he intentado. – Yishai

+0

Jakarta Cactus ha sido retirado: http://jakarta.apache.org/cactus –

0

Yishai, tengo que corregir que manejando un mejor diseño debe acreditarse al patrón de Inyección de Dependencia introducido por Martin Fowler. Guice es una herramienta para ayudar si sus códigos ya están siguiendo el patrón DI. Guice no contribuye nada a tu mejor codificación. Guice está resolviendo DI de manera ad-hoc (a nivel de anotación), por lo que no se debe considerar que se use como marco DI. Esta es la gran diferencia entre el contenedor DI basado en xml de Spring y el contenedor DI basado en anotaciones de Guice.

Pasé un tiempo leyendo el código publicado al principio (generalmente no) para entender por qué dijiste que la prueba de unidad no es posible. Finalmente entiendo el problema al que te enfrentas. Por lo que veo es a nivel de prueba unitaria, intentaste imitar el flujo de ejecución del fragmento de código cuando se ejecuta dentro del servidor Java EE. Esa es la razón por la que te quedaste atrapado. No sé cuál es el incentivo aquí, pero me temo que su esfuerzo de preparar todas estas dependencias ambientales se desperdiciará :(

La prueba de unidad de escritura es para probar una "lógica comercial" que es independiente de cualquiera que sea la tecnología que está utilizando. es posible que haya un aparte requisito que indica que esta lógica de negocio debe ser ejecutado dentro de calidad de servicio (QoS) medio ambiente. este requisito es "requisito técnico". En su ejemplo, CommunicationQueue y TransactionRollBack funcionan en su mayoría para satisfacer el "requisito técnico". La situación es que su "requisito comercial" está rodeado por todas estas unidades ambientales. Lo que debe hacer es refactorizar su c ode para que su "requisito de negocio" esté en métodos separados que no tienen dependencias. Luego, escribe la prueba unitaria para estos métodos.

Todavía considero que estás haciendo pruebas funcionales. Hacer pruebas funcionales a nivel de prueba unitaria consume mucho tiempo y tampoco vale la pena hacer tal cosa. Espero que ayude

+0

Con respecto a Guice, creo que el enfoque de anotación es mejor por razones de control estático mejorado y duplicación reducida, pero estoy de acuerdo en que Guice es una herramienta para un concepto de diseño. Con respecto a por qué necesito esto, es demasiado largo para encajar en un comentario, pero en resumen, el código requiere que el contenedor esté en el medio, por lo que estoy intentando probar el código sin el contenedor (llame a uno lado para recuperar el resultado en el otro), pero tienen dependencias diferentes, deben estar en diferentes clases, y cada uno debe crearse una instancia a través del código de marco. – Yishai

0

Yishai, parece que necesita AOP para caminar alrededor de estas dependencias. AspectJ y AspectWerkz son recursos útiles. Sin embargo, AspectWekz tiene una muy buena característica de tejer clases de aspectos en tiempo de ejecución utilizando la tecnología JVM hotswap. Por supuesto, estos esfuerzos de ingeniería solo deberían aplicarse al nivel de pruebas unitarias.

He puesto mucho esfuerzo en resolver las dependencias al realizar pruebas unitarias. Herramientas como JMock, EasyMock, microcontenedor Java EE, AspectJ, AspectWerkz, Spring IoC se pueden usar para resolver el problema de DI. Ninguna de estas herramientas está impulsando a los desarrolladores a poner a prueba el conocimiento en sus códigos de producción. Lástima que Guice esté violando el concepto de mantener sus códigos limpios de cualquier tipo de conocimiento de prueba. Y no lo considero una herramienta para usar en mi prueba unitaria.

Espero haberle dado suficientes razones para eliminar @Inject annotation de su código de producción.

+0

Thang, no sé por qué crees que Guice hace que tu código sea más "consciente de las pruebas" que Spring, AspectJ o AspectWekz, pero no me has dado ninguna razón, solo alternativas y afirmaciones. – Yishai

+0

AspectJ y AspectWekz están teniendo problemas de DI en el nivel de código de bytes. AspectJ está utilizando el compilador 'ajc' para inyectar comportamiento de aspecto en clases de códigos de bytes. AspectWekz va más allá sin la necesidad de un ajc mediante el uso de la tecnología de bytecode de hotswap. Estas dos herramientas están funcionando en tiempo de precompilación, tiempo de compilación o tiempo de carga de clase para realizar la inyección. El resorte está funcionando un poco a un nivel más alto usando DI basado en xml. Por supuesto, su proyecto debe adoptar Spring para aprovechar esta ventaja (como obtener instancia a través de fábricas) – Thang

Cuestiones relacionadas