web-dev-qa-db-fra.com

EasyMock vs Mockito: design vs maintenabilité?

Une façon de penser à ce sujet est la suivante: si nous nous soucions de la conception du code, EasyMock est le meilleur choix car il vous donne des commentaires par son concept d'attentes.

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

Mes questions sont:

  • 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)?
48
RAbraham

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

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

Mon analyse est que les tests EasyMock se cassent plus souvent, mais pas ceux Mockito quand vous le souhaitez. Je préfère que mes tests se cassent. Au moins, je connais les impacts de mon développement. C'est bien sûr mon point de vue personnel.

29
Henri

Je ne discuterai pas de la lisibilité, de la taille ou des techniques de test de ces frameworks, je pense qu'ils sont égaux, mais sur un exemple simple, je vais vous montrer la différence.

Compte tenu: Nous avons une classe qui est chargée 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 voir, les deux tests sont à peu près les mêmes et les deux réussissent. Imaginons maintenant que quelqu'un d'autre ait modifié l'implémentation du service et tenté d'exécuter des tests.

Implémentation d'un nouveau service:

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

un séparateur a été ajouté à la fin de la constante PATH

À quoi ressembleront les résultats des tests en ce moment? Tout d'abord, les deux tests échoueront, 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)

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

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

Bien sûr, vous pouvez me le dire - changez simplement le test et déplacez-le avant l'assertion. Wow, êtes-vous sérieux, les développeurs devraient garder à l'esprit un certain ordre magique imposé par le framework moqueur?

Soit dit en passant, cela n'aidera pas:

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, cela me dit que la méthode n'a pas été exécutée, mais elle l'a été, uniquement avec d'autres paramètres.

Pourquoi Mockito est meilleur? Ce cadre ne mélange pas deux responsabilités en un seul endroit et lorsque vos tests échoueront, vous comprendrez facilement pourquoi.

108
Ruslan Dzhabbarov

si nous nous soucions de la conception du code, alors Easymock est le meilleur choix car il vous donne des commentaires par son concept d'attentes

Intéressant. J'ai trouvé que le `` concept des attentes '' fait que de nombreux développeurs mettent de plus en plus d'attentes dans les tests uniquement pour satisfaire le problème UneattendMethodCall Comment cela influence-t-il la conception?

Le test ne doit pas s'arrêter lorsque vous modifiez le code. Le test doit s'arrêter lorsque la fonctionnalité cesse de fonctionner. Si l'on aime 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 :)

48
Szczepan

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

Comme pour tous les tests, il n'y a pas de règle stricte et rapide, vous devez équilibrer la confiance des tests et la maintenabilité. Je trouve généralement qu'il existe certains domaines fonctionnels ou techniques qui exigent un haut niveau de confiance pour lesquels j'utiliserais des simulations "strictes". Par exemple, nous ne voudrions probablement pas que la méthode debitAccount () soit appelée plus d'une fois! Cependant, il existe d'autres cas dans lesquels la maquette est vraiment un peu plus qu'un talon afin que nous puissions tester la véritable "viande" du code.

Dans les premiers jours de la vie de Mockito, la compatibilité avec l'API était un problème, mais davantage d'outils prennent désormais en charge le framework. Powermock (un favori personnel) a maintenant une extension mockito

7
user404345

Je préfère le mockito pour être honnête. utilise EasyMock avec des unitils et la combinaison des deux entraîne souvent des exceptions comme IllegalArgumentException: pas une interface ainsi que MissingBehaviorExceptions. Dans les deux cas, le code et le code de test sont parfaitement corrects. Il est apparu que l'exception MissingBehaviorException était due au fait que les objets simulés créés avec createMock (en utilisant classxtentions !!) ont produit cette erreur. Lors de l'utilisation de @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 cadre doit toujours être facile à utiliser et non ambigu. L'exception IllegalArgumentException était également due à un mélange d'interne 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 qu'il renvoie les résultats attendus. C'est en combinaison avec la couverture de code est le bon outil pour moi. Je ne veux pas que mes tests se cassent chaque fois que je mets 1 ligne de code au-dessus ou en dessous de la précédente car cela améliore les performances. Avec mockito ce n'est pas un problème. Avec EasyMock, les tests échoueront même si le code n'est pas cassé. C'est mauvais. Cela coûte du temps, donc de l'argent. Vous souhaitez tester le comportement attendu. Vous souciez-vous vraiment de l'ordre des choses? Je suppose qu'en de rares occasions vous pourriez. Utilisez alors Easymock. Dans d'autres cas, je pense que vous passerez beaucoup moins de temps à utiliser mockito pour écrire vos tests.

Cordialement, Lawrence

5
Lawrence