48 votes

EasyMock vs Mockito : design vs maintenabilité ?

Une façon de penser à cela est la suivante : si nous nous soucions de la conception du code, alors EasyMock est le meilleur choix car il vous donne un retour d'information grâce à son concept d'attentes.

Si nous nous préoccupons de la maintenabilité des tests (plus faciles à lire, à écrire et ayant des tests moins fragiles qui ne sont pas beaucoup affectés par le changement), alors Mockito semble être un meilleur choix.

Mes questions sont les suivantes :

  • Si vous avez utilisé EasyMock dans des projets à grande échelle, trouvez-vous que vos tests sont plus difficiles à maintenir ?
  • Quelles sont les limites de Mockito (autres que les tests endo) ?

112voto

Ruslan Dzhabbarov Points 792

Je ne discuterai pas de la lisibilité, de la taille ou de la technique de test de ces cadres, je pense qu'ils sont égaux. Mais, je vais vous montrer quelques faits qui sont distingués sur l'exemple :

Donné : Nous avons une classe suivante qui est responsable de stocker quelque chose quelque part :

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;
    }
}

Et nous voulons le tester :

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);
    }
}

Comme vous pouvez le constater, les tests sont relativement identiques et les deux sont réussis. Imaginons que quelqu'un d'autre ait changé l'implémentation du service et essaie d'exécuter les tests.

La nouvelle mise en œuvre ressemble :

dao.store(PATH + separator, NAME, IOUtils.toInputStream(CONTENT));

a été ajouté à la fin du chemin

Quel sera le résultat des tests maintenant ? Les deux échouent, mais avec des messages d'erreur différents :

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)
 <Click to see difference>

Que s'est-il passé dans le test EasyMock, pourquoi le résultat n'a-t-il pas été capturé ? La méthode store n'a pas été exécutée, mais attendez une minute, elle l'a été, pourquoi EasyMock nous ment-il ?

C'est parce qu'EasyMock mélange deux responsabilités dans une seule ligne - stubbing et vérification. C'est pourquoi, lorsque quelque chose ne va pas, il est difficile de comprendre quelle partie a causé l'échec.

Bien sûr, vous pouvez me dire - il suffit de changer le test et de déplacer la vérification avant l'assertion. Wow, vous êtes sérieux, les développeurs doivent garder à l'esprit un ordre magique imposé par le framework mocking ?

Au fait, ça ne servira à rien :

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)

Pourtant, il me semble que la méthode n'a pas été exécutée, alors qu'elle l'a été, mais avec d'autres paramètres.

Pourquoi Mockito est meilleur, parce que ce framework ne mélange pas deux responsabilités en un seul endroit et quand vos tests échoueront, vous comprendrez facilement pourquoi.

49voto

Szczepan Points 251

si nous nous préoccupons de la conception du code, Easymock est le meilleur choix car il vous donne un retour d'information grâce à son concept d'attentes.

Intéressant. Je trouve que le "concept d'attentes" fait que beaucoup de développeurs mettent de plus en plus d'attentes dans les tests seulement pour satisfaire le problème UnexpectedMethodCall. Comment cela influence-t-il la conception ?

Le test ne doit pas être interrompu lorsque vous modifiez le code. Le test doit s'interrompre lorsque la fonctionnalité cesse de fonctionner. Si l'on veut que les tests se cassent lorsqu'un changement de code se produit, je suggère d'écrire un test qui affirme la somme de contrôle md5 du fichier java :)

30voto

Henri Points 194

Je suis un développeur EasyMock, donc un peu partial, mais j'ai bien sûr utilisé EasyMock sur des projets à grande échelle.

Mon opinion est que les tests EasyMock vont effectivement se casser de temps en temps. EasyMock vous oblige à faire un enregistrement complet de ce que vous attendez. Cela demande une certaine discipline. Vous devriez vraiment enregistrer ce qui est attendu et non ce dont la méthode testée a besoin actuellement. Par exemple, si le nombre de fois qu'une méthode est appelée sur un mock n'a pas d'importance, n'ayez pas peur d'utiliser andStubReturn. De même, si vous ne vous souciez pas d'un paramètre, utilisez anyObject() et ainsi de suite. Penser en TDD peut vous aider sur ce point.

Mon analyse est que les tests EasyMock se briseront plus souvent, mais que ceux de Mockito ne le feront pas lorsque vous le voudrez. Je préfère que mes tests se cassent. Au moins, je suis conscient de l'impact de mon développement. Il s'agit bien sûr de mon point de vue personnel.

7voto

Toby Hobson Points 933

Je ne pense pas que vous devriez être trop inquiet à ce sujet. Easymock et Mockito peuvent tous deux être configurés pour être "stricts" ou "agréables". La seule différence est que par défaut, Easymock est strict alors que Mockito est agréable.

Comme pour tous les tests, il n'y a pas de règle absolue, vous devez trouver un équilibre entre la fiabilité des tests et la maintenabilité. Je trouve typiquement qu'il y a certains domaines fonctionnels ou techniques qui demandent un haut niveau de confiance pour lesquels j'utiliserais des mocks "stricts". Par exemple, nous ne voudrions probablement pas que la méthode debitAccount() soit appelée plus d'une fois ! Cependant, il y a d'autres cas dans lesquels l'objet fantaisie est un peu plus qu'un stub pour que nous puissions tester la vraie 'viande' du code.

Au début de l'existence de Mockito, la compatibilité avec les API était un problème, mais de plus en plus d'outils supportent désormais le framework. Powermock (un favori personnel) a maintenant une extension Mockito.

5voto

Lawrence Points 111

Pour être honnête, je préfère mockito. J'ai utilisé EasyMock avec unitils et la combinaison des deux donne souvent lieu à des exceptions comme IllegalArgumentException : not an interface ainsi que MissingBehaviorExceptions. Dans les deux cas, le code et le code de test sont parfaitement normaux. Il est apparu que l'exception MissingBehaviorException était due au fait que les objets fantaisie créés avec createMock (en utilisant des classesxtentions !!) produisaient cette erreur. En utilisant @Mock, cela a fonctionné ! Je n'aime pas ce genre de comportement trompeur et pour moi, c'est une indication claire que les développeurs ne savent pas ce qu'ils font. Un bon framework doit toujours être facile à utiliser et sans ambiguïté. L'exception IllegalArgumentException était également due à un mélange des internes d'EasyMock. De plus, l'enregistrement n'est pas ce que je veux faire. Je veux tester si mon code lève des exceptions ou non et s'il renvoie les résultats attendus. En combinaison avec la couverture de code, c'est l'outil qu'il me faut. Je ne veux pas que mes tests se cassent chaque fois que je mets une ligne de code au-dessus ou au-dessous de la précédente parce que cela améliore les performances ou autre. Avec mockito, ce n'est pas un problème. Avec EasyMock, les tests vont échouer même si le code n'est pas cassé. C'est mauvais. Cela coûte du temps, donc de l'argent. Vous voulez tester le comportement attendu. Vous souciez-vous vraiment de l'ordre des choses ? Je suppose qu'en de rares occasions vous pourriez le faire. Utilisez Easymock alors. Dans tous les cas, je pense que vous passerez beaucoup moins de temps à utiliser mockito pour écrire vos tests.

Sincères salutations Lawrence

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X