EasyMock vs Mockito: diseño vs mantenibilidad?
(5)
Una forma de pensar sobre esto es: si nos preocupamos por el diseño del código, entonces EasyMock es la mejor opción, ya que le brinda retroalimentación por su concepto de expectativas.
Si nos preocupamos por la facilidad de mantenimiento de las pruebas (más fácil de leer, escribir y tener menos pruebas frágiles que no se vean afectadas por el cambio), entonces Mockito parece una mejor opción.
Mis preguntas son:
- Si ha utilizado EasyMock en proyectos a gran escala, ¿encuentra que sus pruebas son más difíciles de mantener?
- ¿Cuáles son las limitaciones de Mockito (que no sean pruebas endo)?
Si nos preocupamos por el diseño del código, Easymock es la mejor opción, ya que nos brinda su opinión sobre el concepto de expectativas.
Interesante. Descubrí que el ''concepto de expectativas'' hace que muchos desarrolladores pongan más y más expectativas en las pruebas solo para satisfacer el problema de UnexpectedMethodCall. ¿Cómo influye en el diseño?
La prueba no se debe interrumpir cuando cambias el código. La prueba se debe interrumpir cuando la característica deja de funcionar. Si a uno le gusta que las pruebas se rompan cuando ocurre algún cambio en el código, sugiero que escriba una prueba que confirme la suma de comprobación md5 del archivo java :)
No creo que debas preocuparte demasiado por esto. Tanto Easymock como Mockito se pueden configurar para ser "estrictos" o "agradables", la única diferencia es que, por defecto, Easymock es estricto cuando Mockito es agradable.
Al igual que con todas las pruebas, no existe una regla rígida, es necesario equilibrar la confianza de la prueba con la capacidad de mantenimiento. Normalmente encuentro que hay ciertas áreas funcionales o técnicas que exigen un alto nivel de confianza para el cual usaría burlas "estrictas". Por ejemplo, probablemente no querríamos llamar al método debitAccount () más de una vez. Sin embargo, hay otros casos en los que el simulacro es realmente poco más que un talón, por lo que podemos probar la verdadera "carne" del código.
En los primeros días de la vida de Mockito, la compatibilidad API era un problema, pero ahora hay más herramientas que soportan el marco. Powermock (un favorito personal) ahora tiene una extensión de mockito
No discutiré sobre la legibilidad de las pruebas, el tamaño o las técnicas de prueba de estos marcos, creo que son iguales, pero en un ejemplo simple, les mostraré la diferencia.
Dado: Tenemos una clase que es responsable de almacenar algo en alguna parte:
public class Service {
public static final String PATH = "path";
public static final String NAME = "name";
public static final String CONTENT = "content";
private FileDao dao;
public void doSomething() {
dao.store(PATH, NAME, IOUtils.toInputStream(CONTENT));
}
public void setDao(FileDao dao) {
this.dao = dao;
}
}
y queremos probarlo:
Mockito:
public class ServiceMockitoTest {
private Service service;
@Mock
private FileDao dao;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
service = new Service();
service.setDao(dao);
}
@Test
public void testDoSomething() throws Exception {
// given
// when
service.doSomething();
// then
ArgumentCaptor<InputStream> captor = ArgumentCaptor.forClass(InputStream.class);
Mockito.verify(dao, times(1)).store(eq(Service.PATH), eq(Service.NAME), captor.capture());
assertThat(Service.CONTENT, is(IOUtils.toString(captor.getValue())));
}
}
EasyMock:
public class ServiceEasyMockTest {
private Service service;
private FileDao dao;
@Before
public void setUp() {
dao = EasyMock.createNiceMock(FileDao.class);
service = new Service();
service.setDao(dao);
}
@Test
public void testDoSomething() throws Exception {
// given
Capture<InputStream> captured = new Capture<InputStream>();
dao.store(eq(Service.PATH), eq(Service.NAME), capture(captured));
replay(dao);
// when
service.doSomething();
// then
assertThat(Service.CONTENT, is(IOUtils.toString(captured.getValue())));
verify(dao);
}
}
Como puede ver, ambas pruebas son bastante iguales y ambas pasan. Ahora, imaginemos que alguien más cambió la implementación del Servicio e intentó ejecutar las pruebas.
Nueva implementación del servicio:
dao.store(PATH + separator, NAME, IOUtils.toInputStream(CONTENT));
separador fue agregado al final de la constante de PATH
¿Cómo se verán los resultados de las pruebas en este momento? En primer lugar, ambas pruebas fallarán, pero con diferentes mensajes de error:
EasyMock:
java.lang.AssertionError: Nothing captured yet
at org.easymock.Capture.getValue(Capture.java:78)
at ServiceEasyMockTest.testDoSomething(ServiceEasyMockTest.java:36)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
Mockito:
Argument(s) are different! Wanted:
dao.store(
"path",
"name",
<Capturing argument>
);
-> at ServiceMockitoTest.testDoSomething(ServiceMockitoTest.java:34)
Actual invocation has different arguments:
dao.store(
"path/",
"name",
java.io.ByteArrayInputStream@1c99159
);
-> at Service.doSomething(Service.java:13)
¿Qué pasó en la prueba EasyMock, por qué no se capturó el resultado? ¿El método de la tienda no se ejecutó, pero espere un minuto, fue, por qué EasyMock nos miente?
Es porque EasyMock mezcla dos responsabilidades en una sola línea: el troquelado y la verificación. Es por eso que cuando algo está mal, es difícil entender qué parte está causando el fracaso.
Por supuesto, puede decirme: simplemente cambie la prueba y mueva la verificación antes de la afirmación. Wow, ¿hablas en serio, los desarrolladores deben tener en cuenta algún orden mágico inspirado por el marco burlón?
Por cierto, no ayudará:
java.lang.AssertionError:
Expectation failure on verify:
store("path", "name", capture(Nothing captured yet)): expected: 1, actual: 0
at org.easymock.internal.MocksControl.verify(MocksControl.java:111)
at org.easymock.classextension.EasyMock.verify(EasyMock.java:211)
Aún así, me está diciendo que el método no se ejecutó, pero fue solo con otros parámetros.
¿Por qué Mockito es mejor? Este marco no combina dos responsabilidades en un solo lugar y cuando sus pruebas fallarán, comprenderá fácilmente por qué.
Prefiero mockito para ser honesto. He estado usando EasyMock con unidades y la combinación de ambas a menudo da como resultado excepciones como IllegalArgumentException: no una interfaz tan bien como MissingBehaviorExceptions. En ambos casos, aunque el código y el código de prueba están perfectamente bien. ¡Parecía que la excepción MissingBehaviorException se debía al hecho de que los objetos simulados creados con createMock (usando classextentions !!) produjeron este error. ¡Cuando usas @Mock, funcionó! No me gusta ese tipo de comportamiento engañoso y para mí es una clara indicación de que los desarrolladores de él no saben lo que están haciendo. Un buen marco siempre debe ser fácil de usar y no ambiguo. La excepción IllegalArgumentException también se debió a algunos contactos internos de EasyMock. Además, la grabación no es lo que quiero hacer. Quiero probar si mi código arroja excepciones o no y que devuelve los resultados esperados. Eso en combinación con la cobertura del código es la herramienta correcta para mí. No quiero que se rompan mis pruebas cada vez que coloque 1 línea de código por encima o por debajo de la anterior porque eso mejora el rendimiento más o menos. Con mockito no hay problema. Con EasyMock, las pruebas fallarán aunque el código no esté roto. Eso es malo. Cuesta tiempo, así dinero. Desea probar el comportamiento esperado. ¿Realmente te importa el orden de las cosas? Supongo que en raras ocasiones podrías. Use Easymock entonces. En otro caso, creo que pasarás mucho menos tiempo usando mockito para escribir tus pruebas.
Saludos cordiales a Lawrence
Soy desarrollador de EasyMock, pero un poco parcial, pero por supuesto que he usado EasyMock en proyectos de gran escala.
Mi opinión es que las pruebas de EasyMock se romperán de vez en cuando. EasyMock te obliga a hacer una grabación completa de lo que esperas. Esto requiere algo de disciplina. Realmente debe registrar lo que se espera, no lo que el método probado actualmente necesita. Por ejemplo, si no importa la cantidad de veces que se llama un método en un simulacro, no tenga miedo de usar andStubReturn
. Además, si no le importa un parámetro, use anyObject()
y así sucesivamente. Pensar en TDD puede ayudar en eso.
Mi análisis es que las pruebas de EasyMock se romperán más a menudo, pero las de Mockito no lo harán cuando usted lo desee. Prefiero que mis pruebas se rompan. Al menos soy consciente de cuáles fueron los impactos de mi desarrollo. Esto es, por supuesto, mi punto de vista personal.