2011-04-09 10 views

Respuesta

9

Debería poder simular protocolos utilizando cualquier biblioteca de simulación. Debajo de las coberturas, cada protocolo usa una interfaz Java como detalle de implementación, y usted podría simplemente burlarse de esa interfaz.

Dicho esto, ¡no hagas esto! Burlarse de Java es absurdamente complejo debido a la reflexión, los niveles de protección, las clases finales, etc. Cada vez que desee un objeto Clojure que implemente un protocolo, simplemente llame al reify, p.

(defprotocol Foo (method-a [_]) (method-b [_])) 
-> Foo 

(let [stub (reify Foo (method-a [_] :stubbed))] 
    (method-a stub)) 
-> :stubbed 

Tenga en cuenta que no es necesario que resida los métodos que no desea llamar.

+1

Eso es stubbing. ¿Qué hay de la burla? ¿Cómo definirías y verificarías las expectativas _idiomáticamente? –

+0

Estoy de acuerdo con @Dadinn. Parece tener un soporte de primera clase para afirmar las afirmaciones sobre los comportamientos y verificar que sucedió va a ser descuidado con solo anotar ... p. Ej. verificamos que protocol.f fue invocado 3 veces ... qué, creo una var, la mudo, luego verifico el valor? Eso no es muy declarativo. Ciertamente, alguien ha escrito algo para que esto requiera un poco menos de repetición. –

2

Parece que las versiones más recientes de Midje proporcionan esta funcionalidad bastante bien.

En primer lugar, me gustaría señalar que este tipo de burlas es muy útil al dividir programas más grandes en componentes (por ejemplo, que se ensamblan mediante inyección de dependencia con bibliotecas como Stuart Sierra component library). Si tengo algún componente que aísla un conjunto de funciones de efectos secundarios en un componente conceptual Ciertamente quiero un marco de pruebas que me permita inyectar un componente sustituto de modo que pueda:

  1. Escribir código que usa el componente incluso antes de que exista (de arriba hacia abajo).
  2. Probar funciones que utilizan ese componente de forma aislada de la implementación real del componente.

Puede usar Mockito o alguna otra biblioteca, pero estoy de acuerdo en que una solución de este tipo no será particularmente elegante.

Desafortunadamente, protocolos y registros de generar clases que Midje no puede piratear la misma facilidad que las funciones ... por lo que no tiene que modificar ligeramente su código:

(defrecord-openly SideEffectThing [connection] 
ISideEffect 
(persist [this thing] :unfinished) 
(read [this] :unfinished) 
) 

Ver Midje's documentation on production mode para obtener detalles sobre cómo realizar esta modificación no afecta su tiempo de ejecución de producción.

Al definir su componente con defrecord-openly, se obtiene la capacidad para especificar el comportamiento de los métodos del componente utilizando "proporcionado" mecanismo de Midje:

(fact "you can test in terms of a record's methods" 
    (let [obj (->SideEffectThing :fake-connection)] 
    (user-of-side-effect-thing obj) => 0 
    (provided 
    (read obj) => -1) 
) 
) 

Se podría, por supuesto, evitar depender de un tipo de producción aquí (lo cual yo recomendaría), y también evite pifiar defrecord-abiertamente a través de su código de producción. En este caso, solo mueva SideEffectThing (como se describe arriba) en su código de prueba. Luego, su sistema de componentes en la aplicación puede conectar el componente real, pero sus pruebas pueden escribirse en esta versión de prueba no implementada.

Para completar, compararé el código Java Mockito equivalente con la solución anterior. En Java:

interface ISideEffect { int read(); void write(Object something); } 
class SideEffectThing implements ISideEffect { ... } 

// in test sources: 
public class SomeOtherComponentSpec { 
    private ISideEffect obj; 
    @Before 
    public void setup() { obj = mock(ISideEffect.class); } 
    @Test 
    public void does_something_useful() { 
     when(obj.read()).thenReturn(-1); 
     OtherComponent comp = new OtherComponent(obj); 

     int actual = comp.doSomethingUseful(); 

     assertEquals(0, actual); 
     verify(obj).read(); 
    } 

esta solución de Java se burla el componente, especifica el comportamiento requerido de ese componente, y luego no sólo comprueba que el propio componente funciona correctamente, sino también que el componente dependía de la llamada para leer() de alguna manera.Mockito también admite la coincidencia de patrones en argumentos (y captura) para analizar cómo se usó el componente, si es necesario.

El ejemplo anterior de Midje hace mucho de eso, y en una forma mucho más clara. Si indica (en la cláusula proporcionada) que se llama a la función con argumentos específicos, la prueba fallará si no lo es. Si especifica que la función se llama más de una vez (y no lo es), entonces eso es una falla. Por ejemplo, para indicar la lectura se llama 3 veces y debe devolver valores diferentes:

(fact "you can test in terms of a record's methods" 
    (let [obj (->SideEffectThing :fake-connection)] 
    (user-of-side-effect-thing obj) => 0 
    (provided 
    (read obj) => -1 
    (read obj) => 6 
    (read obj) => 99 
    ) 
) 
) 

indica que espera leer a ser llamado tres veces, y que debería devolver la secuencia de valores especificada. Consulte el docs on prerequisites para obtener más detalles, que incluyen cómo especificar el número exacto de veces en una sola especificación de función en el proporcionado, y cómo nunca se llama a la función.

Cuestiones relacionadas