JMockit 101

1. Introducción

Con este artículo, comenzaremos una nueva serie centrada en el kit de herramientas de simulación JMockit.

En esta primera entrega hablaremos de qué es JMockit, sus características y cómo se crean y usan los simulacros con él.

Los artículos posteriores se centrarán y profundizarán en sus capacidades.

2. JMockit

2.1. Introducción

En primer lugar, hablemos de lo que es JMockit: un marco de Java para simular objetos en las pruebas (puede usarlo tanto para JUnit como para TestNG).

Utiliza las API de instrumentación de Java para modificar el código de bytes de las clases durante el tiempo de ejecución para modificar dinámicamente su comportamiento. Algunos de sus puntos fuertes son su expresibilidad y su capacidad inmediata para burlarse de métodos estáticos y privados.

Tal vez sea nuevo en JMockit, pero definitivamente no se debe a que sea nuevo. El desarrollo de JMockit comenzó en junio de 2006 y su primer lanzamiento estable data de diciembre de 2012, por lo que ha existido por un tiempo (la versión actual es 1.24 al momento de escribir el artículo).

2.2. Dependencia de Maven

Primero, necesitaremos agregar la dependencia jmockit a nuestro proyecto:

 org.jmockit jmockit 1.41 

2.3. La expresibilidad de JMockit

Como se dijo anteriormente, uno de los puntos más fuertes de JMockit es su expresibilidad. Para crear simulaciones y definir su comportamiento, en lugar de llamar a métodos desde la API simulada, solo necesita definirlos directamente.

Esto significa que no harás cosas como:

API.expect(mockInstance.method()).andThenReturn(value).times(2);

En cambio, espere cosas como:

new Expectation() { mockInstance.method(); result = value; times = 2; }

Puede parecer que es más código, pero podría simplemente poner las tres líneas en una. La parte realmente importante es que no terminará con un gran "tren" de llamadas a métodos encadenados. En cambio, terminas con una definición de cómo quieres que se comporte el simulacro cuando lo llamen.

Si tiene en cuenta que en la parte resultado = valor puede devolver cualquier cosa (valores fijos, valores generados dinámicamente, excepciones, etc.), la expresividad de JMockit se vuelve aún más evidente.

2.4. El modelo Record-Replay-Verify

Las pruebas que utilizan JMockit se dividen en tres etapas diferenciadas: grabar, reproducir y verificar.

  1. En la fase de registro , durante la preparación de la prueba y antes de las invocaciones a los métodos que queremos que se ejecuten, definiremos el comportamiento esperado para todas las pruebas que se utilizarán durante la siguiente etapa.
  2. La fase de reproducción es aquella en la que se ejecuta el código bajo prueba. Ahora se reproducirán las invocaciones de métodos / constructores simulados previamente registrados en la etapa anterior.
  3. Por último, en la fase de verificación , afirmaremos que el resultado de la prueba fue el esperado (y que los simulacros se comportaron y se utilizaron de acuerdo con lo definido en la fase de registro).

Con un ejemplo de código, un wireframe para una prueba se vería así:

@Test public void testWireframe() { // preparation code not specific to JMockit, if any new Expectations() {{ // define expected behaviour for mocks }}; // execute code-under-test new Verifications() {{ // verify mocks }}; // assertions }

3. Creando simulacros

3.1. Anotaciones de JMockit

Al usar JMockit, la forma más fácil de usar simulacros es usar anotaciones. Hay tres para crear simulacros ( @Mocked , @Injectable y @Capturing ) y uno para especificar la clase bajo prueba ( @Tested ).

Al usar la anotación @Mocked en un campo, creará instancias simuladas de todos y cada uno de los objetos nuevos de esa clase en particular.

Por otro lado, con la anotación @Injectable , solo se creará una instancia simulada .

La última anotación, @Capturing se comportará como @Mocked, pero extenderá su alcance a cada subclase que extienda o implemente el tipo de campo anotado.

3.2. Pasar argumentos a pruebas

Cuando se usa JMockit, es posible pasar simulacros como parámetros de prueba. Esto es bastante útil para crear un simulacro solo para esa prueba en particular, como un objeto modelo complejo que necesita un comportamiento específico solo para una prueba, por ejemplo. Sería algo como esto:

@RunWith(JMockit.class) public class TestPassingArguments { @Injectable private Foo mockForEveryTest; @Tested private Bar bar; @Test public void testExample(@Mocked Xyz mockForJustThisTest) { new Expectations() {{ mockForEveryTest.someMethod("foo"); mockForJustThisTest.someOtherMethod(); }}; bar.codeUnderTest(); } }

Esta forma de crear un simulacro pasándolo como parámetro, en lugar de tener que llamar a algún método API, vuelve a mostrarnos la expresibilidad de la que estamos hablando desde el principio.

3.3. Ejemplo completo

Para finalizar este artículo, incluiremos un ejemplo completo de una prueba con JMockit.

En este ejemplo, probaremos una clase Performer que usa Collaborator en su método perform () . Este método perform () , recibe un objeto Model como parámetro del cual usará su getInfo () que devuelve un String, este String será pasado al método colaborate () de Collaborator que devolverá verdadero para esta prueba en particular, y este valor se pasará al método receive () de Collaborator .

Entonces, las clases probadas se verán así:

public class Model { public String getInfo(){ return "info"; } } public class Collaborator { public boolean collaborate(String string){ return false; } public void receive(boolean bool){ // NOOP } } public class Performer { private Collaborator collaborator; public void perform(Model model) { boolean value = collaborator.collaborate(model.getInfo()); collaborator.receive(value); } }

Y el código de la prueba terminará siendo como:

@RunWith(JMockit.class) public class PerformerTest { @Injectable private Collaborator collaborator; @Tested private Performer performer; @Test public void testThePerformMethod(@Mocked Model model) { new Expectations() {{ model.getInfo();result = "bar"; collaborator.collaborate("bar"); result = true; }}; performer.perform(model); new Verifications() {{ collaborator.receive(true); }}; } }

4. Conclusión

Con esto, concluiremos nuestra introducción práctica a JMockit. Si desea obtener más información sobre JMockit, permanezca atento a los artículos futuros.

La implementación completa de este tutorial se puede encontrar en el proyecto GitHub.

4.1. Artículos de la serie

Todos los artículos de la serie:

  • JMockit 101
  • Una guía para JMockit - Expectativas
  • Uso avanzado de JMockit