easymock – [Blog] du Java et du Freelance https://java-freelance.fr Vis ma vie de freelance java : techno et entreprise Mon, 12 Oct 2015 14:45:01 +0000 fr-FR hourly 1 https://wordpress.org/?v=4.4.4 Spock-(3/X) – Spock, EasyMock, Mockito et les arguments https://java-freelance.fr/java/spock-easymock_mockito_argument https://java-freelance.fr/java/spock-easymock_mockito_argument#respond Mon, 08 Nov 2010 08:30:27 +0000 https://java-freelance.fr/?p=1010 L’article précédent a permis de comparer la syntaxe des trois frameworks sur les cas classiques. Un cas où Spock se détache vraiment des deux autres frameworks, c’est sur la gestion des arguments des méthodes mockées.

Les wildcards

Il est possible d’utiliser des wildcards au niveau des arguments appelés. Ainsi 1 * calendarDao
.getInfosByDay(_) veut dire ‘la méthode calendarDao.getInfosByDay est appelée une fois (1 *) avec n’importe quel paramètre (_). On peut également spécifier la classe de l’argument : 1 * calendarDao.getInfosByDay(_ as String) .

Il est également possible par exemple de compter le nombre d’appel aux méthodes d’un simulacre (quel quel soit : 3 * calendarDao._ passera uniquement si les instructions du bloc when font exactement appel à 3 méthodes de l’instance calendarDao. Il est également possible de donner des intervalles plutôt qu’une valeur, ce qui n’est pas permis par les autres frameworks (à part le ‘au moins 1 fois’) :
(1..3) * calendarDao.getInfosByDay(_) [entre 1 et 3 fois]
(5.._) * calendarDao.getInfosByDay(_) [=> au moins 5 fois]
(_..5) * calendarDao.getInfosByDay(_) [=> au plus 5 fois]

Il existe de nombreux wildcard, la plupart ne servent pas à grand chose. Ceux qui me semblent le plus important :
calendarDao.getInfosByDay(_) : n’importe quel argument
calendarDao.getInfosByDay(!null) : n’importe quel argument non null
calendarDao.getInfosByDay(_ as String) tous les éléments de type String

Equivalent EasyMock

expect(calendarDao.getInfosByDay((String)anyObject())) : n’importe quel argument, obligation d’être une String
expect(calendarDao.getInfosByDay((String)notNull())): n’importe quel argument non null
expect(calendarDao.getInfosByDay(isA(String.class))):tous les élements de type String

Equivalent Mockito

when(calendarDao.getInfosByDay(anyString())) : n’importe quel argument, obligation d’être une String
when(calendarDao.getInfosByDay((String)notNull())): n’importe quel argument non null
when(calendarDao.getInfosByDay(isA(String.class))): tous les élements de type String

Vous pouvez trouver la liste ici : http://code.google.com/p/spock/wiki/Interactions

Les contraintes personnalisées

Les contraintes personnalisées sont très utiles dans certains cas, par exemple lorsque la méthode equals est déjà défini dans le code et qu’elle ne correspond pas à notre besoin ou que nous avons par exemple un champ date que nous souhaitons exclure de la comparaison. En règle général, il vaut mieux redéfinir la méthode equals qui est spontannément utilisée par les 3 frameworks pour comparer l’égalité des arguments attendus et reçus.

Nous allons chercher avec les 3 frameworks à créer des contraintes personnalisés (spock) ou des argument matcher (Mockito & Easymock).  Notre but est de faire en sorte que l’appel à listSpockData.add avec un paramètre ayant comme variable de classe img égale à « a » soit bien simulée. Pour cela, nous créons un objet spockData ayant bien img = « a » ainsi qu’une liste d’objets SpockData. Nous appelons ensuite la méthode listSpockData avec l’objet spockData et vérifions que cette dernière a bien été appelée.

Avec Spock :

  def "should list Spock Data"() {
    given:
    SpockData spockData = new SpockData("a", "accroche", "details", 6);
    List listSpockData = Mock();
    when :
    listSpockData.add spockData;
    then :
     1*listSpockData.add({it.img=="a"})
  }

Spock permet via les conditions particulières de définir un bloc à l’aide d’une closure , avec { } et de définir à l’intérieur plusieurs conditions. It signifie ici l’objet qui sera passé en paramètre à la méthode. Il est possible d’utiliser plusieurs expression (it.img== »img »&&it.day==6) ou une fonction définie dans la classe de test.

Avec Mockito,  qui utilise en fait les ArgumentMatcher du framework Hamcrest.

class isImgEqualToA extends ArgumentMatcher { //creation d un argument matcher
	      public boolean matches(Object spockData) {
	          return ((SpockData) spockData).getImg() == "a";
	      }
	   }
	@Test
	public void testArgumentMockito(){
		       List mock = mock(List.class);
			   when(mock.add(argThat(new isImgEqualToA()))).thenReturn(true);
			   mock.add(newSpockData("a", "b", "c", 2));
			   verify(mock).add(argThat(new isImgEqualToA()));

	}

Avec Easymock, l’opération se révèle être très verbeuse. [Pour voir une implémentation plus conforme] :

	static class Matcher implements IArgumentMatcher { //creation du matcher

		@Override// implementer cette methode permet de definir
                             // un message d erreur
		public void appendTo(StringBuffer arg0) {
		}

		@Override// definition de la methode qui verifiera que
                             // l argument img est bien egal a A. smell code.
		public boolean matches(Object spockData) {
			return ((SpockData) spockData).getImg() == "a";
		}
                // definition d une methode static pour déclarer le matcher
		public static SpockData isImgEqualToA() {
		    EasyMock.reportMatcher(new Matcher());
		    return null;
		}
	}

	@Test //Test
	public void testArgumentEasyMock() {
		List mock = createMock(List.class);
		expect(mock.add(Matcher.isImgEqualToA())).andReturn(true);
		replay(mock);
		mock.add(new SpockData("a", "b", "c", 2));
		verify(mock);
	}

Les tests mockito et easymock sont bien plus verbeux et au final sont plus restrictifs car elles utilisent des classes séparées pour définir les matchers. Spock évite la lourdeur d’avoir à définir une autre classe

Néanmoins, dans le cas de tests avec ArgumentMatcher, EasyMock & Mockito permettent de définir des messages d’erreurs personnalisés, via la méthode appendTo pour le premier et describeTo pour le deuxième. Notons que pour le premier, il faut la coder nous même alors que le deuxième en propose une par défaut construite à partir du nom de la classe isImgEqualToA donne ‘Img equal to A’. En règle général, on affiche un toString() de l’objet pour aider au debuggage et on regarde alors les 2 chaînes pour trouver les différences (ou en pas à pas en debug). Spock ne propose rien de tel dans sa version actuelle (0.4) mais en version 0.5 il est prévu de pouvoir utiliser les matchers d’Hamcrest, revenant à avoir la même syntaxe qu’avec Mockito, en un peu plus courte.

Crédit Photo : Oskay – http://www.flickr.com/photos/oskay/339996940/sizes/m/in/photostream/

]]>
https://java-freelance.fr/java/spock-easymock_mockito_argument/feed 0
Spock-(2/X) – Spock, EasyMock et Mockito sont sur un bateau https://java-freelance.fr/java/spock-mockito-easymoc https://java-freelance.fr/java/spock-mockito-easymoc#comments Thu, 04 Nov 2010 08:00:21 +0000 https://java-freelance.fr/?p=909 On a vu dans la première partie que l’on pouvait faire facilement du data-driven testing et que la syntaxe en bloc apportait beaucoup en lisibilité. Les blocs expect/where que l’on a vu sont les plus adéquats lors de cas de tests simples, où l’on peut facilement exprimer le stimulus et son résultat, typiquement monojbetA.maMethode()==monResultat. Dans le cas où l’on se retrouve face à des tests un peu plus compliqués, l’utilisation de la structure given/when/then est alors préférable. Cet article n’a pas pour vocation de comparer les fonctionnalités des trois frameworks, juste de présenter spock et de comparer la syntaxe des trois frameworks dans le cas de tests ‘classiques’.

Test given/when/then

En effet, un bloc then permet de déclarer des conditions, des exceptions, des interactions et des définitions de variables là où un bloc expect ne peut contenir que des conditions et des déclarations de variables. L’écriture given/when/then est également plus intuitive dans le cas où vous souhaitez tester des stories. C’est également une des clés du Behavior Driven Development et une méthode saine pour structurer ses tests, qui oblige à réfléchir vraiment à ce que l’on teste.  Ce que j’aime chez spock, c’est que c’est obligatoirement intégré via ces blocs, on ne peut pas faire autrement :)

Test condition simple – comportement

Spock permet le data-driven testing mais c’est également un framework facilitant la création de bouchons/simulacres [Plus d’infos sur les différences bouchon/simulacre et test d’état/de comportement]. On s’intéresse ici au le test par comportement, c’est à dire qu’on va s’occuper des chainages des appels des méthodes entre elles et moins du résultat. On cherche alors à vérifier que l’appel à spockResource.findCalendarByDay(‘1’) entraîne bien un unique appel à calendarDao.getInfosByDay(‘1’).

 def "test par comportement"() {
    given:
    def calendarDao = Mock(CalendarDao)
    def spockResource = new SpockResource(calendarDao)
    when :
    spockResource.findCalendarByDay("1")
    then :
    1 * calendarDao.getInfosByDay("1")
  }

Le bloc given permet de définir les variables nécessaires à l’exécution du test. Ici, on bouchonne le dao que l’on affecte ensuite au service que l’on souhaite tester. Le bloc when correspond à l’appel de la méthode à tester.
Le bloc then comporte ici uniquement la condition à tester. La syntaxe veut dire on vérifie que la méthode calendarDao
.getInfosByDay
est appelée uniquement une fois (1 *) avec le paramètre ‘1’. Les paramètres sont évalués via l’appel à la méthode equals.

A la différence d’EasyMock, qui fonctionne par défaut avec des simulacres, Spock comme Mockito renvoie de base pour toutes les méthodes mockées sans spécification null ou 0 ou false. Ici par exemple, l’appel à

calendarDao
    .getInfosByDay("1")

renverra null. Pour spécifier une valeur de retour différente, il suffit d’utiliser la syntaxe suivante :

 calendarDao
    .getInfosByDay(_) >> new SpockInfo("1");

Le même code avec EasyMock avec une valeur de retour :

@Test
	public void testEasyMock() {
                //given
		CalendarDao calendarDao = createNiceMock(CalendarDao.class);
		SpockResourcespockResource = new SpockResource(
				calendarDao);
		expect(calendarDao.getInfosByDay("1")).andReturn(
				new SpockInfo("1"));
		replay(calendarDao);
                //when
		SpockInfo spockInfo= spockResource.findCalendarByDay("1");
		//then
                verify(calendarDao);
	}

Avec EasyMock, on annote la méthode par @Test [annotation JUnit] puis on crée le mock à l’aide de EasyMock.createNiceMock, pour avoir un mock lénient. On précise ensuite que l’on s’attend à ce que la méthode calendarDao.getInfosByDay(‘1’) retourne l’objet new SpockInfo(‘1’) avec expect(calendarDao.getInfosByDay(‘1’)).andReturn(new SpockInfo(‘1’)); . On ‘charge’ ensuite les mocks via le replay et à la ligne suivante on lance l’appel à la méthode testée. Le verify à la dernière ligne permet de vérifier qu’un unique appel à la méthode a bien été effectué.

Test condition simple – état

L’inconvénient de l’utilisation des simulacres, c’est que les tests et le code testé sont très (trop!) liés. Ainsi une modification du code peut entraîner beaucoup de refactoring au niveau des tests sans valeur ajouté. L’exemple le plus marquant est la séparation d’une méthode en deux méthodes distinctes : il faut reprendre tous les simulacres alors que ce n’est qu’une modification de ‘clarté’. Il est donc souvent préférable de ne pas tester le comportement mais uniquement le résultat à chaque fois que cela est possible et judicieux, c’est à dire du faire du test sur l’état des objets.

  def "test stub avec retour"() {
    given:
    def calendarDao = Mock(CalendarDao)
    def spockResource = new SpockResource(calendarDao)

    when :
    def spockInfo = spockResource.findCalendarByDay("1")

    then :
     calendarDao
    .getInfosByDay("1") >> new SpockInfo("1");
    spockInfo.day == 1
  }

Ici, on ne fait plus de contrôle sur le nombre d’appel à la méthode getInfosByDay(‘1’). On indique juste que lorsque cette méthode est appelée, on renvoie (>>) une nouvelle instance de SpockInfo.  Ici pas de assertEquals ou autre méthode du genre, le spockInfo.day==1 est en fait un raccourci pour écrire assert spockInfo.day == 1.On vérifie que la variable day de l’objet spockInfo est bien égale à 1.
Voilà le code équivalent avec Mockito :

        @Test
	public void testMockito() {
		//Given
                CalendarDao calendarDao = mock(CalendarDao.class);
		SpockResource spockResource = new SpockResource(
				calendarDao);
		when(calendarDao.getInfosByDay("1")).thenReturn(
				new SpockData("1"));
		//when
                SpockData spockData= spockResource.findCalendarByDay("1");
		//then
                assertEquals("1", spockData.getDay());
	}

A la première ligne, on construit le bouchon calendarDao que l’on affecte à la ligne suivante à l’objet spockResource. On indique au bouchon calendarDao que quand la méthode getInfosByDay est appelée avec le paramètre ‘1’, alors elle retourne new SpockData(‘1’). On effectue ensuite l’appel à la méthode testée spockResource.findCalendarByDay(‘1’) et on vérifie que la variable day du résultat spockData est bien égale à 1.

Et si on veut chainer les retours ?

Test  retour multiple

Il est parfois nécessaire de renvoyer des valeurs différentes pour une même méthode bouchonnée. Pour les 3 cas suivants, le premier appel à la méthode calendarDao.getInfosByDay avec le paramètre ‘1’ renverra SpockInfo(‘1’), le deuxième new SpockInfo(‘2’) :

Avec Easymock :

expect(calendarDao.getInfosByDay("1")).andReturn(new SpockInfo("1")).andReturn(new SpockInfo("2"))

Avec Mockito :

when(calendarDao.getInfosByDay("1")).thenReturn(new SpockData("1")).thenReturn(new SpockInfo("2"))

Avec Spock :

calendarDao.getInfosByDay("1") >>> [new SpockInfo("1"),new SpockInfo("2")];

Le prochain article abordera les fonctionnalités plus avancées de la gestion des arguments des fonctions mockées (ArgumentMatcher) également dans les trois frameworks.

Crédit Photo : Mistinguette18

]]>
https://java-freelance.fr/java/spock-mockito-easymoc/feed 3
Let’s make this test suite run faster! SoftShake 2010 https://java-freelance.fr/java/lets-make-this-test-suite-run-faster-softshake-2010 https://java-freelance.fr/java/lets-make-this-test-suite-run-faster-softshake-2010#comments Tue, 26 Oct 2010 07:00:51 +0000 https://java-freelance.fr/?p=920 Qui n’a jamais attendu de nombreuses minutes pour builder un projet ? Et qui n’en a jamais profité pour aller boire un café/regarder twitter/… ? :) Parce que l’on teste fréquemment, il est important d’essayer d’optimiser et de réduire la durée d’exécution des tests.  Tester souvent ne veux pas dire attendre souvent ! Il est donc primordial d’essayer de réduire cette durée en jouant sur différents paramètres. Comment obtenir le résultat des tests le plus rapidement possible ?

Ce qui suit est tiré de la conférence de David Gageot [ @dgageot / http://javabien.net ] à SoftShake 2010.

Sur une vingtaine de personnes présentes dans la salle, les durées de build vont de quelques minutes à plusieurs heures. David présente brièvement quelques frameworks comme Infinitest et JUnitMax. Ce sont des plugins pour IDE Java qui permettent de lancer les tests unitaires en continu et de manière intelligente, c’est à dire uniquement ceux impactés par le code modifié.
La première idée lorsque l’on cherche à optimiser cette durée d’exécution, c’est de vouloir déléguer le problème. C’est faire  tourner les tests sur des serveurs distribués qui permettront d’exécuter les tests en tâches de fond. C’est une mauvaise idée, les serveurs coûtent chers et on peut se retrouver submerger. Il existe des méthodes plus simples pour réduire cette durée.

Le KISS ( Keep It Simple, Stupid ) est également applicable lorsque l’on crée des tests. Chercher à optimises ses tests peut améliorer votre produit : ce qui est simple à tester sera simple à coder et à utiliser. Ce qui est compliqué n’en vaut surement pas la peine.

Le tricheur

La manière la plus simple pour accélérer les tests c’est d’acheter une machine plus rapide. Exécuter les tests sur une machine plus rapide peut être un vrai gain de temps, David nous donne l’exemple d’une exécution 15% plus rapide sur la machine la plus rapide par rapport à la plus lente. Il est également possible d’utiliser la nouvelle fonctionnalité de maven de build en parallèle (mvn -T2 clean install / mvn -t4 clean install). Nous avons essayé sur un de nos projets, l’exécution du build est passé de 1m30 à 30 secondes !
Il est également possible de faire en sorte que les tâches maven surefire pour JUnit et TestNG soient exécutés en parallèle. Comme les tests se doivent d’être indépendant et isolés, ce sont de bons candidats à une exécution en parallèle. Faire quand même attention que vous pouvez vous retrouver avec des problèmes de concurrence dans certains cas.

<plugin>
   <groupId>org.apache.maven.plugins</groupId>
   <artifactId>maven-surefire-plugin</artifactId>
   <version>2.5</version>
   <configuration>
     <parallel>methods</parallel>
     <threadCount>4</threadCount>
   </configuration>
</plugin>

Il existe deux façons de paralléliser : méthodes ou classes. Utiliser la méthode de parallélisation par méthode peut se révéler risqué car il est fort probable que tous les tests n’auront pas été designés dans l’optique d’être exécutés en parallèle, la méthode ‘classes’ est un choix plus prudent. Plus d’infos sur le blog de Wakaleo Consulting.

Le paresseux

Il y a souvent des tests redondants dans un projets : débusquez les, cela permettra de gagner en exécution et en lisibilité. C’est tellement simple qu’on ne le fait pas ! Ne pas hésiter à supprimer des fonctionnalités et les tests associés si elles ne servent plus rien, le projet y gagnera en simplicité.
Les accès réseaux et disques sont trop lents. Il faut essayer au maximum de s’en passer et privilégier les bases de données en mémoire comme h2 (qui ressemblera plus à mysql que hsqldb). De même pour les accès mails, il est possible d’utiliser dans les tests des serveurs SMTP en mémoire comme ethereal. Si beaucoup de tests accèdent à des fichiers, Spring Resource ou Apache VFS (Virtual File System) sont de bonnes alternatives.

Le brave

Il est préférable de tester les règles métiers dans les tests unitaires plutôt que dans les test d’intégrations. Il ne faut pas confondre tests d’intégrations et tests unitaires : les premiers sont , bien qu’essentiel, plus longs à tester, ils doivent être utiliser avec parcimonie. Par exemple, pour plusieurs tests qui accèderaient à une base de données peuvent être remplacés par un test qui permet de garantir que l’on peut bien accéder à la base de données et par plusieurs autres tests où l’accès à la base de données aura été bouchonné.
Une méthode lorsque l’on cherche à diminuer la durée d’exécution de tests est de prendre le test d’intégration le plus long et de l’analyser jusqu’à réussir à le découper en un test d’intégration plus petit et plusieurs tests unitaires. Si c’est l’accès à certaines couches qui sont lentes lors de tests d’intégrations, il est recommandé de les bouchonner, les frameworks de mocks ne servent pas que dans le cas de tests unitaires :)
De même, méfiez vous des tests d’interface, ils prennent beaucoup de temps et souvent, ce qu’ils testent peut être tester unitairement. Selenium est à utiliser avec modération. Méfiez vous vraiment quand vous commencez à tester vos fonctionnalités via Selenium. Et ne dites pas ‘mon utilisateur veut de l’AJAX‘ ‘J’ai besoin de tester la compatibilité des différents navigateurs’.

Chaque complexité a un coût. Et cela se paye à chaque fois que les tests sont exécutés.  Si c’est compliqué à tester : danger, la fonctionnalité peut sûrement être faite plus simplement. Il est possible de faire des tests unitaires en Javascript plutôt que tester dans les browsers (ex : QUnit).
David préfère limité AJAX aux réels besoins et d’effectuer au maximum le code server-side.

Et pour finir : simplifier et optimiser votre code. Ce sont des choses qui se font. Le build va être plus rapide et l’application aussi 😀 A vous l’effet Kiss Cool :)

]]>
https://java-freelance.fr/java/lets-make-this-test-suite-run-faster-softshake-2010/feed 4
Bouchon (stub) ou Simulacre (mock) ? https://java-freelance.fr/java/bouchon-stub-ou-simulacre-mock https://java-freelance.fr/java/bouchon-stub-ou-simulacre-mock#comments Fri, 02 Jul 2010 05:04:37 +0000 https://java-freelance.fr/?p=795 Le test unitaire est un test qui provoque l’exécution d’une partie du code et qui l’analyse.  Il permet de garantir que le code exécuté fonctionne correctement. Il se doit donc d’être automatisé et répétable. Pour cela, cela nécessite fréquemment le besoin d’isoler le test unitaire des systèmes extérieurs (base de données, appels web-service distants).

Pour ce faire, il existe plusieurs méthodes, l’utilisation de bouchon (‘stub’) ou de simulacre (‘mock’). L’article de référence est un article de Martin Fowler « Les simulacres ne sont pas des bouchons« . Pour résumer, une méthode bouchonnée est appelée sur un objet bouchon réel, centré sur le système testé. Il ne peut jamais faire échouer le test. On regarde l’état de l’objet final à la fin du test et non les étapes qui ont permis d’obtenir cet état. C’est un test basé sur des états.

En ce qui concerne les simulacres, les assertions ne portent pas sur l’objet final mais sur la manière dont ce dernier a été obtenu. C’est un test basé sur le comportement de la méthode. On peut contrôler le nombre de fois qu’une méthode a été invoquée, vérifier que ses paramètres correspondent bien entre ce qui est défini et ce qui est exécuté et faire échouer le test si l’enchaînement de méthodes ne correspond pas à l’enchaînement attendu par exemple.

Easymock utilise dans son fonctionnement le plus basique des simulacres, qui permettent de construire un test de manière très simple : l’ordre des méthodes invoquées, le nombre d’appel à une méthode peut engendrer un échec du test. Potentiellement, les tests sont plus fragiles et plus difficile à maintenir.
Mais EasyMock permet également la création de bouchons qui peuvent être réutilisé. Il est ainsi possible de maintenir des bouchons partagés entre différents tests unitaires, ce qui permet une meilleure maintenabilité. Dans ce cas, nous ne nous intéressons pas au comportement, uniquement au résultat.

La création de bouchon à l’aide de andStubReturn()

public Service getServiceMock() {
   Service serviceMock =  createMock(Service.class);

   expect(serviceMock.appel1()).andStubReturn(Integer.valueOf(5));
   expect(serviceMock.appel2(anyObject())).andStubReturn(BigDecimal.TEN);

   replay(serviceMock);

}

La méthode andStubReturn signifie que cette méthode peut être appelée sans condition de nombre (0,1 …n) ni d’ordre. Il est simplement défini que si cette méthode est appelée, elle renverra un paramètre tel que défini via le andStubReturn. Il n’y a pas de verify(serviceMock) car les contrôles sur le comportement du mock ne nous intéressent pas.

La création de bouchon à l’aide des ‘niceMock’

Une autre possibilité lorsque l’on souhaite utiliser les bouchons (stub) est de créer un ‘nice mock’, qui est par défaut, renvoie 0, null ou false selon le paramètre de retour des méthodes :

myNiceMock  = createNiceMock(Service.class);

Toujours sans utiliser de verify.

Les avantages du stub sont nombreux : les tests sont plus faciles à maintenir, il est possible de réutiliser les stubs au sein de plusieurs classes de tests… Néanmoins,  il y a des situations où le test d’un comportement est plus adéquat que l’utilisation de bouchon comme par exemple, vérifier que certains paramètres sont bien calculés avant d’être envoyé à des services externes, ce qui est fréquemment mon cas.

]]>
https://java-freelance.fr/java/bouchon-stub-ou-simulacre-mock/feed 6
Les erreurs courantes avec EasyMock https://java-freelance.fr/java/les-erreurs-courrantes-avec-easymock https://java-freelance.fr/java/les-erreurs-courrantes-avec-easymock#comments Wed, 16 Jun 2010 08:24:21 +0000 https://java-freelance.fr/?p=772 logo easymockEasyMock est un framework de test qui peut dérouter dans un premier abord. Une fois qu’on a compris comment l’utiliser, on tombe sur un certain nombre d’erreurs qui reviennent très souvent et qui ne sont pas souvent explicites. Même si EasyMock 3.0 a clarifié un certain nombre d’erreurs, cet article (fait sous la 2.5.2) servira de pense-bête à ceux qui débutent avec ce framework.

Les classes qui permettent de tester : une interface CalculService et son implémentation dont on cherche à tester unitairement les différentes méthodes.  L’implémentation de CalculService fait appel à plusieurs méthodes de l’interface FormatService. Comme chacun des appels à la méthode FormatService est mocké, nous n’avons pas besoin de définir une implémentation correspondante.

package fr.java.freelance.service;

import java.math.BigDecimal;

public interface CalculService {

  String calcul(BigDecimal a);
  String calcul(BigDecimal a, BigDecimal b);
  String calcul(String year, String month,
      String day);
}
package fr.java.freelance.service.impl;

import java.math.BigDecimal;

import fr.java.freelance.service.CalculService;
import fr.java.freelance.service.FormatService;

public class CalculServiceImpl implements
    CalculService {

  private final FormatService formatService;
  public CalculServiceImpl(
      FormatService formatService) {
    this.formatService = formatService;
  }
  public CalculServiceImpl() {
    formatService=null;
  }
  public String calcul(BigDecimal a, BigDecimal b) {
    return formatService.formatComplexe(a, b);
  }
  public String calcul(BigDecimal a) {
    return formatService.formatSimple(a);
  }
  public String calcul(String year,
      String month, String day) {
    return formatService.formatSimple(year,
        month, day);
  }
}
package fr.java.freelance.service;

import java.math.BigDecimal;

public interface FormatService {
  String formatSimple(BigDecimal a);
  String formatComplexe(BigDecimal a,BigDecimal b);
  String formatSimple(String year, String month,
      String day);
}

Erreur 1 :Unexpected method call methodX(X)

java.lang.AssertionError:
Unexpected method call formatSimple(5):
at org.easymock.internal.MockInvocationHandler.invoke(MockInvocationHandler.java:43)
at org.easymock.internal.ObjectMethodsFilter.invoke(ObjectMethodsFilter.java:72)
at $Proxy5.formatSimple(Unknown Source)

@Test
public void testUnexpectedMethodCall() {
   BigDecimal a = BigDecimal.valueOf(5);
   BigDecimal b = BigDecimal.valueOf(5);
   replay(formatServiceMock);
   calculService.calcul(a);
  verify(formatServiceMock);
}

Explication : La méthode formatSimple est appelée avec le paramètre 5 sans qu’elle ait été attendue.

Erreur 2 :methodA(A,B): expected: X, actual: Y

java.lang.AssertionError:
Expectation failure on verify:
formatComplexe(5, 5): expected: 1, actual: 0
at org.easymock.internal.MocksControl.verify(MocksControl.java:111)
at org.easymock.EasyMock.verify(EasyMock.java:1608)

@Test
  public void testExpectedActual() {
    BigDecimal a = BigDecimal.valueOf(5);
    BigDecimal b = BigDecimal.valueOf(5);
    expect(
        formatServiceMock
            .formatSimple(eq(BigDecimal
                .valueOf(5)))).andReturn("XXX");
    expect(
        formatServiceMock
            .formatComplexe(eq(BigDecimal
                .valueOf(5)),eq(BigDecimal
                    .valueOf(5)))).andReturn("XXX");
    replay(formatServiceMock);
    calculService.calcul(a);
    verify(formatServiceMock);
  }

Explication : La méthode formatComplexe(5, 5) était attendue une fois (expected:1) et n’a jamais été appelée (actual:0)

Erreur 3 : X matchers expected, Y recorded

java.lang.IllegalStateException: 2 matchers expected, 1 recorded.
at org.easymock.internal.ExpectedInvocation.createMissingMatchers(ExpectedInvocation.java:56)

@Test
  public void testExpectedRecorded() {
    BigDecimal a = BigDecimal.valueOf(5);
    BigDecimal b = BigDecimal.valueOf(6);
    expect(
        formatServiceMock
            .formatComplexe(BigDecimal
                .valueOf(5),eq(BigDecimal
                    .valueOf(6)))).andReturn("XXX");
    replay(formatServiceMock);
    calculService.calcul(a,b);
    verify(formatServiceMock);
  }

Il est interdit d’utiliser partiellement les comparateurs. Il faut les utiliser complètement ou pas du tout.
Ainsi

 formatServiceMock.scale(BigDecimal
.valueOf(5),eq(BigDecimal
.valueOf(5)));

n’est pas OK (le deuxième argument utilise un comparateur, le premier la méthode equals => non OK).

formatServiceMock.scale(BigDecimal
.valueOf(5),BigDecimal
.valueOf(5));

OK

formatServiceMock.scale(eq(BigDecimal
.valueOf(5)),eq(BigDecimal
.valueOf(5)));

OK

Erreur 4 : Calling verify is not allowed in record state

java.lang.IllegalStateException: calling verify is not allowed in record state
at org.easymock.internal.MocksControl.verify(MocksControl.java:109)
at org.easymock.EasyMock.verify(EasyMock.java:1608)

 @Test
  public void testRecordState() {
    BigDecimal a = BigDecimal.valueOf(5);
    BigDecimal b = BigDecimal.valueOf(6);
    expect(
        formatServiceMock
            .formatComplexe(eq(BigDecimal
                .valueOf(5)),eq(BigDecimal
                    .valueOf(6)))).andReturn("XXX");
    calculService.calcul(a,b);
    verify(formatServiceMock);
  }

Explication : un simple oubli du replay

Erreur 5 :missing behavior definition for the preceding method call

java.lang.IllegalStateException: missing behavior definition for the preceding method call formatComplexe(5, 6)
at org.easymock.internal.MocksControl.replay(MocksControl.java:101)
at org.easymock.EasyMock.replay(EasyMock.java:1540)

 @Test
  public void testMissingBehavior() {
    BigDecimal a = BigDecimal.valueOf(5);
    BigDecimal b = BigDecimal.valueOf(6);
    formatServiceMock
            .formatComplexe(eq(BigDecimal
                .valueOf(5)),eq(BigDecimal
                    .valueOf(6)));
    replay(formatServiceMock);
    calculService.calcul(a,b);
    verify(formatServiceMock);
  }

Explication : La méthode formatServiceMock.formatComplexe(BigDecimal a,BigDecimal b) renvoie un paramètre. Il faut donc indiquer quel paramètre de retour cette dernière doit renvoyée via la méthode expect et le andReturn. Dans ce cas, l’écriture correcte est :

expect(
formatServiceMock
.formatComplexe(eq(BigDecimal
.valueOf(5)),eq(BigDecimal
.valueOf(6)))).andReturn("XXX");

Erreur 6 : NullPointerException

 @Test
  public void testNullPointer() {
    BigDecimal a = BigDecimal.valueOf(5);
    BigDecimal b = BigDecimal.valueOf(6);
    formatServiceMock
            .formatComplexe(eq(BigDecimal
                .valueOf(5)),eq(BigDecimal
                    .valueOf(6)));
    replay(formatServiceMock);
    calculServiceDouble.calcul(a,b);
    verify(formatServiceMock);
  }

avec

 @Before
  public void before() {
    formatServiceMock = createMock(FormatService.class);
    calculService = new CalculServiceImpl(
        formatServiceMock);
    calculServiceDouble = new CalculServiceImpl();
  }

Explication : Dans 90% des cas, le mock n’a pas été affecté au service testé.

Le code source est dispo ici https://code.google.com/p/easymock-error/

]]>
https://java-freelance.fr/java/les-erreurs-courrantes-avec-easymock/feed 5
Kawa BarCamp 2 https://java-freelance.fr/java/kawa-bar-camp-2 https://java-freelance.fr/java/kawa-bar-camp-2#comments Sun, 30 May 2010 17:47:01 +0000 https://java-freelance.fr/?p=691 J’ai participé hier à la deuxième édition du kawa barcamp. Un bar camp c’est une rencontre ouverte autour de thèmes choisis par les participants. Nous étions hier une trentaine de personnes réunis pendant environ 3 heures. Après un bref tour de table où nous nous sommes présentés (nom + prénom + 3 mots clés), 8 personnes ont inscrits des thèmes d’ateliers sur un tableau. Nous devions choisir parmis 2 sujets au choix :
en première partie No SQL pour les entreprises / TDD et ATDD / HTML 5 et en deuxième User Experience / Les nouveaux frameworks web (Play! Vaadin) / Les commentaires dans le code . Et d’autres que j’ai oublié !

J’ai participé à TDD/ATTD et à la session sur les commentaires dans le code.

TDD – ATTD

Le Test Driven Development est une méthode de programmation basée sur les règles suivantes :

  • on écrit un test
  • on vérifie qu’il échoue
  • on écrit juste ce qu’il faut pour que le test passe (et rien d’autre!)
  • on vérifie qu’il passe
  • on refactorise encore et encore

L’Acceptance Test Driven Development (ATDD) est basée sur l’idée de faire des tests d’acceptance (automatisés) avant même de commencer les développements. La bonne mise en pratique se fait en deux temps : le client, les utilisateurs, la MOA écrit les test pendant que les développeurs se chargent de les automatiser. Plusieurs outils existent pour cela comme par exemple Greenpepper, Fit/Fitnesse, Robot Framework … La plupart se base sur des wiki pour permettre aux personnes fonctionnelles d’écrire facilement leurs tests.

Dans une approche basée sur l’ATDD, les spécifications et les implémentations sont dirigées par des exemples concrets. Décrire précisément des cas de tests oblige celui qui spécifie à aller plus loin que les spécifications habituelles. Cette méthode a également l’avantage d’être plus claire pour le développeur.

Le TDD

Peu de personnes font du TDD pur et dur, la plupart de la dizaine de personnes présentes écrivent un bout de code avant de tester.  Néanmoins,  la personne ayant le plus d’expérience est un adepte du TDD pur et dur depuis de nombreuses années. Les frameworks de mocks permettent de simplifier sensiblement la tâche du développeur. Néanmoins, le TDD est loin d’être une évidence pour tout le monde. Le risque de perdre de vue l’architecture générale du projet, la difficulté de la sensibilisation d’une équipe aux tests, les problèmes de maintenabilité et de refactoring sont quelques uns des exemples soulevés pour montrer les difficultés engendrés par l’application de cette méthode.

L’ATDD

Au niveau des tests d’acceptance, seul une seule personne les utilise dans un contexte d’entreprise.
Le gros problème soulevé par les tests d’acceptance c’est la difficulté, lors d’utilisation d’outils type fit/fitness greenpepper est la refactorisation des tests : les tests écrits sont difficilement maintenable dans le temps. L’ATDD permet au développeur d’avoir des spécifications claires et précises, permettant entre autre de réduire les difficultés de communication entre la maitrîse d’oeuvre et d’ouvrage. Un autre point bloquant est que l’ATDD demande à l’équipe fonctionnelle de s’impliquer et de se former à un nouvel outil, ce qui n’est pas simple dans certains contextes.

Digressions diverses

Un autre aspect négatif est le temps pris par les tests, qu’ils soient d’intégration, d’acceptance …  Ils ont tendance à grossir encore et encore, à devenir de moins en moins maintenables jusqu’à devenir contre productif. Plutôt qu’un enchaînement d’action, difficile maintenable dans le temps, il faut se focaliser sur les work flows et sur les règles business. Il faut également essayer de limiter quand cela est possible l’utilisation de l’IHM dans les tests d’acceptance.

Les commentaires dans le code

Olivier Croisier a pour projet de synthétiser les bonnes pratiques de commentaires. Plusieurs points ont été abordés dans cet atelier.
Au niveau des APIs publics, la javadoc est coûteuse à maintenir mais obligatoire, et en anglais. Lorsqu’il y a du code, il est souvent obsolète donc à utiliser avec précaution.
Au niveau du code non public, les personnes présentes utilisent beaucoup moins la javadoc. On remarque souvent qu’il n’y en a pas, qu’elle est obsolète ou inintéressante. Un point pour commenter facilement le code est l’encapsulation du code dans des méthodes private qui, via leurs noms, sont auto-documentées. On peut donc alors comprendre facilement une méthode principale en lisant la succession de méthodes appelées. A noter qu’il ne suffit pas de découper son code n’importe comment, il est important de ne pas avoir une profondeur d’appels de méthodes de ce type trop importantes (A qui appelle B qui appelle C qui appelle D qui appelle E). Un moyen de découper convenablement le code est d’éviter d’appeler dans la méthode principale des méthodes fonctionnelles et des méthodes techniques. Le code est plus lisible si on découpe du fonctionnel vers le technique.
J’ai utilisé cette méthode pour découper des classes de plusieurs centaines de lignes avec un fonctionnel un peu compliqué et je trouve que le code est beaucoup plus clair, en cas d’évolution ou de recherche d’un bug, on sait directement où aller. Le problème de cette méthode est que parfois, on ne voit pas certains refactoring qui pourrait faire gagner en lisibilité (là encore, il est possible de perdre de vue la ‘big picture’). C’est pour cette raison que certains estiment qu’une méthode n’a d’utilité que lorsqu’elle est réutilisée et que les méthodes de plusieurs dizaines de lignes sont en fait plus claires. Sur ce sujet, lire Clean Code et Effective Java est essentiel.

Un autre point est l’utilisation des commentaires du style /**** GETTERS AND SETTERS *****/ /***** CONSTRUCTORS *****/ . La encore, cette méthode ne fait pas l’unanimité (et je suis loin d’en être la première fan).

Au niveau de l’utilisation français/anglais, c’est assez mitigé. La plupart écrivent leurs noms de variables et de commentaires en anglais, d’autres se permettent quelques écarts en français et pour quelques un le français est la norme.

Au niveau des tâches (//FIXME //XXX //TODO), la plupart des personnes présentes sont d’accord avec le fait que cela n’est utile que si la tâche nous concerne.

En ce qui concerne les warnings sur les classes, l’idée est d’en avoir aucun : l’utilisation des outils comme PMD, FindBugs, CheckStyle est à bien configurer avant utilisation, car la multiplication des warnings noie les vrais warnings dans de la pollution.

Je retiens également que Implementation Patterns de Kent Beck sera une de mes lectures estivales et que l’outil mvn eclipse:eclipse permet de faire bien plus de choses que prévues !

J’ai passé un agréable moment, j’y étais venue surtout par curiosité et je compte bien retourner à la prochaine ! Ça a duré jusqu’à un peu plus de 23 heures pour moi, ce qui fait quand même une bonne soirée. Énormément de digressions, ça part dans tous les sens mais c’est souvent là que c’est le plus intéressant !

Sur twitter : http://twitter.com/KawaCampParis

]]>
https://java-freelance.fr/java/kawa-bar-camp-2/feed 2
java.lang.IllegalStateException: 1 matchers expected, 2 recorded https://java-freelance.fr/java/java-lang-illegalstateexception-1-matchers-expected-2-recorded https://java-freelance.fr/java/java-lang-illegalstateexception-1-matchers-expected-2-recorded#comments Mon, 10 May 2010 04:12:03 +0000 https://java-freelance.fr/?p=650 « java.lang.IllegalStateException: 1 matchers expected, 2 recorded » est une exception bien connue du développeur utilisant EasyMock. Elle veut tout simplement dire que lorsque l’on utilise un matcher (genre eq,anyObject …) il faut en utiliser pour tous les paramètres de la méthode, j’y reviendrai plus en détail dans un prochain article. Ainsi :

expect(maMethode("XYZ","BZT")).andReturn("XXX")
// est correct tout comme :
expect(maMethode((String)anyObject(),eq("BZT"))).andReturn("XXX")
// mais pas :
expect(maMethode((String)anyObject(),"BZT")).andReturn("XXX")

Sauf que mon erreur du jour, c’est que cette expression arrive sur la méthode :
« expect(formatServiceMock.scale(eq(BigDecimal.valueOf(10)))).andReturn(c) » qui elle est correcte a première vue.

Le test passe unitairement dans Eclipse et dans Surefire. L’ensemble des tests passent dans Eclipse, il n’y a que dans lors de la tâche « install » de Maven que l’erreur se produit (et non, Maven n’a rien à voir là dedans :p).

Si on zoome sur le code simplifié au maximum, on obtient :
Une classe et son interface, FormatServiceImpl et FormatService qui ne contiennent que 2 méthodes vides

public class FormatServiceImpl implements
FormatService {
private static final int SCALE = 5;
public void scale(BigDecimal a) {
}
public void scale2(BigDecimal a) {
}

Une classe et son interface, « CalculServiceImpl » et « CalculService » qui contient une méthode et un champ privé de type « FormatService ».

public class FormatServiceImpl implements
public class CalculServiceImpl implements
    CalculService {

  private final FormatService formatService;

  public CalculServiceImpl(
      FormatService formatService) {
    this.formatService = formatService;
  }

  public BigDecimal addAndscale(BigDecimal a,
      BigDecimal b) {
    formatService.scale(a.add(b));
    return a.add(b);
  }
}

La classe de test de « CalculServiceImpl » contient une méthode de test « testAddAndScale » qui pose problème uniquement dans le cas d’une exécution des tests via Surefire, le plugin Maven.

public class CalculServiceImplTest {
private CalculServiceImpl calculService;
private FormatService formatServiceMock;

@Before
public void before() {
formatServiceMock = createMock(FormatService.class);
calculService = new CalculServiceImpl(
formatServiceMock);
}

@Test
public void testAdd() {
BigDecimal a = BigDecimal.valueOf(5);
BigDecimal b = BigDecimal.valueOf(5);
BigDecimal c = BigDecimal.valueOf(10);

formatServiceMock.scale(eq(BigDecimal
.valueOf(10)));
replay(formatServiceMock);
assertEquals(c, calculService.addAndscale(a,
b));
verify(formatServiceMock);
}

Le fonctionnement de cette classe est classique : on définit un mock, ici : « formatServiceMock » que l’on affecte à l’implémentation testée calculService. On lui affecte un comportement spécifique : la méthode « scale » de « FormatService » est appelée avec le paramètre équivalent à « BigDecimal.valueOf(10) ». Le mock est ensuite chargée avec la méthode « replay(formatServiceMock) ».
L’action testée est ensuite lancée : « calculService.addAndscale(a,b) » que l’on vérifie être égale à « c ». Puis avec : « verify(formatServiceMock) », on vérifie que le « mock formatServiceMock » a bien eu le comportement attendu c’est à dire que la méthode « scale » de « FormatService » a bien été appelée avec le paramètre équivalent à BigDecimal.valueOf(10) .

Cette classe de test ne soulève une erreur que lorsqu’elle est appelé via les test Surefire de Maven. Si on la lance unitairement via Eclipse (Run as Junit test) ou via Maven sSurefire individuellement (via mvn -Dtest=CalculServiceImplTest test)  aucun problème. Si on lance l’ensemble des tests unitaires via Eclipse (Run as JUnit test) idem aucun problème.

Par contre, si on la lance avec l’ensemble des tests Surefire, on obtient l’erreur :

java.lang.IllegalStateException: 1 matchers expected, 2 recorded.
at org.easymock.internal.ExpectedInvocation.createMissingMatchers(ExpectedInvocation.java:56)
at org.easymock.internal.ExpectedInvocation.<init>(ExpectedInvocation.java:48)
at org.easymock.internal.ExpectedInvocation.<init>(ExpectedInvocation.java:40)
at org.easymock.internal.RecordState.invoke(RecordState.java:76)
at org.easymock.internal.MockInvocationHandler.invoke(MockInvocationHandler.java:38)
at org.easymock.internal.ObjectMethodsFilter.invoke(ObjectMethodsFilter.java:72)
at $Proxy5.scale(Unknown Source)
at fr.java.freelance.easymock.CalculServiceTest.testAdd(CalculServiceTest.java:40)

Impossible dans un premier temps de voir d’où vient l’erreur. On utilise alors le système permettant de débugger les tests à distance à l’aide de : « mvn -Dmaven.surefire.debug test ». Lorsque l’on lance la commande, l’exécution se met en pause tant qu’elle n’a pas reçu de connexion sur son port 5005 (en configuration par défaut, customizable).

[INFO] Scanning for projects...
[INFO] ------------------------------------------------------------------------
[INFO] Building EasyMockTest
[INFO]
[INFO] Id: fr.java.freelance:TestSpringPath:jar:0.0.1-SNAPSHOT
[INFO] task-segment: [test]
[INFO] ------------------------------------------------------------------------
[INFO] [resources:resources]
[INFO] Using default encoding to copy filtered resources.
[INFO] [compiler:compile]
[INFO] Nothing to compile - all classes are up to date
[INFO] [resources:testResources]
[INFO] Using default encoding to copy filtered resources.
[INFO] [compiler:testCompile]
[INFO] Nothing to compile - all classes are up to date
[INFO] [surefire:test]
[INFO] Surefire report directory: E:\workspace\EasyMock\target\surefire-reports
Listening for transport dt_socket at address: 5005

Il suffit alors dans Eclipse d’aller dans le mode Debug Configurations et de créer une remote application sur le port 5005 et on peut débugger le test dans Eclipse .

Un moyen de voir  le nombre d’objet enregistré dans EasyMock est de mettre un point d’arrêt juste avant la levée de l’exception :  la méthode mockée doit avoir autant d’arguments que la liste enregistrée. Ici, la méthode est appelée avec un argument (on ne se formalise pas sur le null, seul compte ici le nombre d’argument) alors que la liste en possède 2 : un matcher « Any » et un matcher « Equals », d’où le « IllegalStateException ».
Cliquer pour voir en grand :

La recherche pour savoir d’où provient le « Any » est assez fastidieuse (le « Equals » étant celui du test). Les objets enregistrés le sont via un autre chemin et partagés via l’objet « LastControl ». Pour comprendre comment les matchers sont enregistrés, il suffit de faire un pas à pas en mode debug.

Si on entre (via F5) dans le détail de l’appel à « formatServiceMock.scale(eq(BigDecimal
.valueOf(10))); », on arrive tout d’abord sur le calcul du « valueOf » puis sur la méthode « eq » de la classe EasyMock :

public static <T> T eq(T value) {
reportMatcher(new Equals(value));
return null;
}

On entre à nouveau dans « reportMatcher(new Equals(value)); »
En continuant un peu avec F5 on arrive à dans la classe « EasyMock » :

/**
* Reports an argument matcher. This method is needed to define own argument
* matchers. For details, see the EasyMock documentation.
*
* @param matcher
*/
public static void reportMatcher(IArgumentMatcher matcher) {
LastControl.reportMatcher(matcher);
}

On continue à regarder ce qui se passe dans « LastControl ». On arrive sur une classe contenant plusieurs threads : « threadToControl » – « threadToCurrentInvocation » – « threadToArgumentMatcherStack ». Celui qui nous intéresse est « threadToArgumentMatcherStack ». Il contient une pile d’ « ArgumentMatcher ».Le « reportMatcher » permet d’ajouter chacun des matchers pour chacun des arguments.  A cet étape là du calcul, cette pile doit être vide.

public static void reportMatcher(IArgumentMatcher matcher) {
        Stack stack = threadToArgumentMatcherStack.get();
        if (stack == null) {
            stack = new Stack();
            threadToArgumentMatcherStack.set(stack);
        }
        stack.push(matcher);
    }


Comme attendu, la pile n’est pas vide,  une autre méthode y a déjà renseigné une valeur …

En supprimant des tests, je suis tombée sur la méthode fautive en utilisant un point d’arrêt sur ce chemin , tout au fond d’un des tests d’intégration, dans une partie où EasyMock n’était pas du tout utilisée et sur des classes n’ayant aucun rapport avec les classes testées, il y  avait quelque chose comme cela:

@Test
public void testJunitMalConstruit() {
FormatServiceImpl formatService = new FormatServiceImpl();
formatService.scale2((BigDecimal) anyObject());
}

Le « (BigDecimal) anyObject() » est ici une erreur d’étourderie qui au final se retrouve à avoir un impact très loin dans le code. C’est pour moi un bug d’EasyMock, même si ce « (BigDecimal) anyObject()  » n’a rien à faire ici !

Le fait que les tests passent ou non dans Surefire ou dans Eclipse via « run as JUnit test » dépend en fait de l’ordre dans lequel les 2 lanceurs de tests ordonnent les différents tests. Pour enlever cette notion variable, on peut utiliser le test suivant :

public class CalculServiceImplTest {
private CalculServiceImpl calculService;
private FormatService formatServiceMock;

@Before
public void before() {
formatServiceMock = createMock(FormatService.class);
calculService = new CalculServiceImpl(
formatServiceMock);
}
@Test
public void testJunitMalConstruit() {
FormatServiceImpl formatService = new FormatServiceImpl();
formatService.scale2((BigDecimal) anyObject());
}

@Test
public void testAdd() {
BigDecimal a = BigDecimal.valueOf(5);
BigDecimal b = BigDecimal.valueOf(5);
BigDecimal c = BigDecimal.valueOf(10);
formatServiceMock.scale(eq(BigDecimal
.valueOf(10)));
replay(formatServiceMock);
calculService.addAndscale(a,
b);
verify(formatServiceMock);
}
}

Et là ca plante à chaque fois :)

Et pour faire plaisir aux adorateurs de Mockito, j’ai testé avec Mockito la classe ci-dessous :

public class CalculServiceImplMockitoTest {
  private CalculServiceImpl calculService;
  private FormatService formatServiceMock;

  @Before
  public void before() {
    formatServiceMock = mock(FormatService.class);
    calculService = new CalculServiceImpl(
        formatServiceMock);
  }
  @Test
  public void testJunitMalConstruit() {
    FormatServiceImpl formatService = new FormatServiceImpl();
    formatService.scale2((BigDecimal) anyObject());
  }

  @Test
  public void testAdd() {
    BigDecimal a = BigDecimal.valueOf(5);
    BigDecimal b = BigDecimal.valueOf(5);
    BigDecimal c = BigDecimal.valueOf(10);
    formatServiceMock.scale(eq(BigDecimal
        .valueOf(10)));

    calculService.addAndscale(a,
        b);
    verify(formatServiceMock);
  }
}

Et l’erreur est ici bien claire :

org.mockito.exceptions.misusing.InvalidUseOfMatchersException:
Misplaced argument matcher detected here:
-> at fr.java.freelance.easymock.CalculServiceImplMockitoTest.testJunitMalConstruit(CalculServiceImplMockitoTest.java:24)

You cannot use argument matchers outside of verification or stubbing.
Examples of correct usage of argument matchers:
    when(mock.get(anyInt())).thenReturn(null);
    doThrow(new RuntimeException()).when(mock).someVoidMethod(anyObject());
    verify(mock).someMethod(contains("foo"))

Mockito 1 – EasyMock 0 :)

]]>
https://java-freelance.fr/java/java-lang-illegalstateexception-1-matchers-expected-2-recorded/feed 3
EasyMock – Techniques avancées https://java-freelance.fr/java/easymock-techniques-avancees https://java-freelance.fr/java/easymock-techniques-avancees#comments Thu, 11 Mar 2010 09:11:37 +0000 https://java-freelance.fr/?p=409 Easymock est un framework de test offrant une API permettant de tester plus facilement le code. Cet article ne constitue pas une présentation d’easymock et s’adresse aux développeurs souhaitant progresser sur l’utilisation de cette API.

Lorsque l’on met en place les mocks, il est fréquent que l’on ait besoin de tester qu’un objet créé à l’intérieur de la méthode testée correspond bien à ce que l’on attend, cet objet étant passé en tant que paramètre à une méthode d’un objet mocké.

Prenons pour exemple, la classe Service et sa méthode processRequest. La méthode processRequest prend en entrée deux paramètres et se sert de ces paramètres pour créer un objet Bond, qui sera passé ensuite à la méthode persist du service BondDao.

package fr.java.freelance.easymock;

import java.math.BigDecimal;

public class Service {
	private BondDao bondDao;

	public String processRequest(String name,BigDecimal quantity) {
		Bond bond = new Bond(name,quantity);
		return bondDao.persist(bond);
	}

	public void setBondDao(BondDao bondDao) {
		this.bondDao = bondDao;
	}
}

package fr.java.freelance.easymock;

public interface BondDao {
	String persist(Bond bond);
}

package fr.java.freelance.easymock;

import java.math.BigDecimal;

public class Bond {
	private String name;
	private BigDecimal quantity;

	public Bond(String name, BigDecimal quantity) {
		super();
		this.name = name;
		this.quantity = quantity;
	}
	public String getName() {
		return name;
	}
	public BigDecimal getQuantity() {
		return quantity;
	}
}

Il est a priori impossible pour un test unitaire d’accèder à l’objet « bond » pour le vérifier.

Ce que l’on cherche à garantir, c’est que la méthode processRequest construit un objet de type Bond ayant comme attributs name et quantity les 2 valeurs passées en paramètres, qu’elle le transmette à la méthode persist de ServiceDao et retourne le paramètre de retour de cette méthode persist.

Utilisation d’EasyMock.anyObject()

La première méthode pour tester ceci est d’utiliser la méthode anyObject de l’objet EasyMock.

Service service = new Service();
BondDao bondDaoMock = EasyMock.createMock(BondDao.class);
service.setBondDao(bondDaoMock);

final String persist_return = "123AX";
final String name = "Name 5%";
final BigDecimal quantity = BigDecimal.TEN;

EasyMock.expect(bondDaoMock.persist((Bond) EasyMock.anyObject()))
.andReturn(persist_return);

EasyMock.replay(bondDaoMock);
String idInternal = service.processRequest(name, quantity);
Assert.assertEquals(persist_return,idInternal);
EasyMock.verify(bondDaoMock);

Le test fonctionne, néanmoins rien ne garantit que l’objet que l’on transmet à la méthode persist de BondDao est conforme à ce que l’on attend.

Pour pouvoir analyser l’objet Bond transmis, il existe plusieurs possibilités, dont une utilisant la redéfinition de la méthode equals de l’objet Bond et deux fournies par la librairie EasyMock, l’utilisation d’un IArgumentMatcher ou de EasyMock.capture.

Rédéfinition de la méthode equals()

Supposons que l’objet bond redéfinisse la méthode equals de manière à tester l’égalité des paramètres name et quantity.

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		Bond other = (Bond) obj;
		if (name == null) {
			if (other.name != null)
				return false;
		} else if (!name.equals(other.name))
			return false;
		if (quantity == null) {
			if (other.quantity != null)
				return false;
		} else if (quantity.compareTo(other.quantity)!=0)
			return false;
		return true;
	}

Il suffit alors d’écrire le test suivant pour garantir que l’objet passé à la méthode persist est bien égal au bond attendu :

        @Test
	public void testProcessRequestWithEquals() {
		Service service = new Service();
		BondDao bondDaoMock = EasyMock.createMock(BondDao.class);
		service.setBondDao(bondDaoMock);

		final String persist_return = "123AX";
		final String name = "Name 5%";
		final BigDecimal quantity = BigDecimal.TEN;
		final Bond bondExpected = new Bond(name,quantity);

		EasyMock.expect(bondDaoMock.persist(bondExpected))
				.andReturn(persist_return);
		EasyMock.replay(bondDaoMock);
  	        String idInternal = service.processRequest(name, quantity);
		Assert.assertEquals(persist_return,idInternal);
		EasyMock.verify(bondDaoMock);
	}

Cette démarche est la plus naturelle, néanmoins elle ne s’applique pas dans le cas où la méthode equals n’est n’ai pas redéfinie (dans ce cas, c’est la méthode equals de Object qui est prise en compte et donc, les deux objets ne seraient pas égaux (pas la même instance)). Elle ne s’applique pas non plus dans le cas où la méthode equals ne correspondrait pas à ce que l’on a définit comme étant utile pour tester l’égalité. Par exemple une méthode equals qui ne testerait que l’égalité de l’attribut name dans l’objet Bond alors que nous souhaitons garantir les valeurs des deux paramètres. On ne peut évidement pas modifier la méthode equals d’un objet pour le tester, cela changerai son comportement !!
Il faut donc se tourner vers d’autres solutions.

Utilisation d’un IArgumentMatcher

EasyMock permet d’utiliser un certain nombre de matcher déjà définit ( eq, isNull, matches ..). Néanmoins dans le cas présent, il nous faut définir notre propre matcher pour pouvoir tester l’égalité des paramètres name et quantity.
La redéfinition d’un matcher s’effectue en deux temps.
D’abord, il faut créer une classe implémentant IArgumentMatcher.

package fr.java.freelance.easymock;

import org.easymock.EasyMock;
import org.easymock.IArgumentMatcher;

public class BondEquals implements IArgumentMatcher {
	private final Bond expected;

	public BondEquals(Bond expected) {
		this.expected = expected;
	}

	public boolean matches(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (expected.getClass() != obj.getClass())
			return false;
		Bond other = (Bond) obj;
		if (this.expected.getName() == null) {
			if (other.getName() != null)
				return false;
		} else if (!this.expected.getName().equals(other.getName()))
			return false;
		if (this.expected.getQuantity() == null) {
			if (other.getQuantity() != null)
				return false;
		} else if (this.expected.getQuantity().compareTo(other.getQuantity()) != 0)
			return false;
		return true;
	}

	public void appendTo(StringBuffer buffer) {
		buffer.append("eqException ").append(expected.toString());
	}

	public static Bond eqBond(Bond in) {
	    EasyMock.reportMatcher(new BondEquals(in));
	    return null;
	}
}

Pour pouvoir utiliser le BondEquals ainsi crée, la méthode persist de l’interface BondDao n’acceptant que les objets de type Bond, on utilise l’astuce suivante :

public static Bond eqBond(Bond in) {
	    EasyMock.reportMatcher(new BondEquals(in));
	    return null;
	}

On peut alors utiliser le test suivant, pour garantir d’une part le bon retour de la méthode processRequest ainsi que le passage du bond ayant les caractéristiques souhaitées à la méthode persist du BondDao.

        @Test
	public void testProcessRequestWithArgumentMatcher() {
		Service service = new Service();
		BondDao bondDaoMock = EasyMock.createMock(BondDao.class);
		service.setBondDao(bondDaoMock);

		final String persist_return = "123AX";
		final String name = "Name 5%";
		final BigDecimal quantity = BigDecimal.TEN;

		final Bond bondExpected = new Bond(name,quantity);
		EasyMock.expect(bondDaoMock.persist(BondEquals.eqBond(bondExpected)))
				.andReturn(persist_return);
		EasyMock.replay(bondDaoMock);
		String idInternal = service.processRequest(name, quantity);
		Assert.assertEquals(persist_return,idInternal);
		EasyMock.verify(bondDaoMock);
	}

Utilisation de org.easymock.Capture

Une autre méthode consiste à capturer l’objet bond passé à la méthode persist du DAO et de faire des tests dessus dans un deuxième temps.
Une capture s’effectue en 3 temps.
1. Déclaration de la capture : Capture capture = new Capture();
2. Capture du paramètre lors de l’exécution via : EasyMock.expect(bondDaoMock.persist(EasyMock.and(EasyMock.isA(Bond.class), EasyMock.capture(capture))))
3. Récupération de l’objet capturé : Bond captured = capture.getValue();

	@Test
	public void testProcessRequestWithCapture() {
		Service service = new Service();
		BondDao bondDaoMock = EasyMock.createMock(BondDao.class);
		service.setBondDao(bondDaoMock);

		final String persist_return = "123AX";
		final String name = "Name 5%";
		final BigDecimal quantity = BigDecimal.TEN;

               Capture capture = new Capture();

		EasyMock.expect(bondDaoMock.persist(EasyMock.and(EasyMock
                          .isA(Bond.class), EasyMock.capture(capture))))
                .andReturn(persist_return);

		EasyMock.replay(bondDaoMock);
		String idInternal = service.processRequest(name, quantity);
		Assert.assertEquals(persist_return,idInternal);

		Bond captured = capture.getValue();
		Assert.assertEquals(name,captured.getName());
		Assert.assertTrue(quantity.compareTo(captured.getQuantity())==0);
		EasyMock.verify(bondDaoMock);
	}

Il existe également d’autres méthodes utilisant des outils externes pour atteindre un but similaire mais je n’ai pas encore trouvé de limitation à la création d’un nouveau matcher ou la capture de l’élément à tester. Si il y a besoin plusieurs fois de tester l’objet de manière identique, j’ai tendance à créer un nouveau matcher et à utiliser la capture dans le cadre d’objets plus petits ou de besoin spécifique à un test.

]]>
https://java-freelance.fr/java/easymock-techniques-avancees/feed 5