mercredi 31 octobre 2012

Le test builder

"Putain c'est quoi ce que tu testes avec ce code de merde que tu dis être un test unitaire"

C'est par cette douce pensée que j'envisage souvent les codes reviews des tests unitaires qui me sont soumis.

Moi j'aime bien les tests unitaires, je les respecte et j'essaye de les rendre heureux ou du moins utiles. Sauf que des fois, c'est le drame, une méthode à mocker toute tordue, dix scénarios de test pour la même méthode, le resultat à vérifier qui devient trop compliqué et le test devient trop complexe, illisible et donc un peu inutile.


D'ou un petit pattern que je me suis amusé à créer : le test builder 


Le test builder c'est quoi ?


C'est une classe utilitaire permettant d'ecrire les scénarios de test unitaire pour une méthode à la mode DSL.


Heu... le test builder c'est quoi ?

bon d'accord, prenons un exemple concret. J'ai une méthode qui s'appele

public Collection<ObjetAVendreAvecLeurPrix> queVendreAMémée(Mémée laMémée){
}

dans la classe VRPService

Vous l'aurez compris, cette méthode permet de connaître une liste d'object que l'on peux vendre à mémée et leur prix en fonction de la mémée.

Cette méthode est sans doute assez compliquée à tester et il faudra sans doute de nombreuses mémées différentes pour la tester correctement.

 on aura par exemple les tests suivants :
 testQueVendreAMémée_qandMéméeEstRicheEtGénéreuse
 testQueVendreAMémée_qandMéméeEstRicheMaisRadine
 testQueVendreAMémée_qandMéméeEstPauvreEtRadine
 testQueVendreAMémée_qandMéméeEstPauvreEtGénéreuse

On supose aussi, pour les besoins de l'exemple, que la méthode queVendreAMémée utilise un service richesseDeMéméeService avec une méthode isMéméeRiche(Mémée laMémée)



Un des test unitaire resemblerait à ca

@Test
public void testQueVendreAMémée_qandMéméeEstRicheEtGénéreuse(){
 
 
  RichesseDeMéméeService richesseDeMéméeService = mock(RichesseDeMéméeService.class);
  Mémée méméeRicheEtGénéreuse = new Mémé();
  mémée.setGénérosité(Mémée.Généreuse);
  mémée.ajouteAuxObjetsQuePossedentMémée(ObjectAVendre.Aspirateur);
  when(richesseDeMéméeService.isMéméeRiche(méméeRicheEtGénéreuse)).thenReturn(true);

  Collection<ObjetAVendreAvecLeurPrix> resultats = new VRPService().QueVendreAMémée(méméeRicheEtGénéreuse);

  assertEquals(1,resultats.size);
  assertEquals(ObjectAVendre.Télévision,resultats.get(0).getObjectAVendre()); // ok get(0) n'existe pas dans Collection mais bon, les mémées riches et généreuses non plus et personne ne me fait chier avec ça
  assertEquals(100.000,resultats.get(0).getPrix());

}

vous avez vu c'est assez chiant à lire et pourtant c'est assez clair je pense. Et puis pour tester la mémée pauvre ça va faire de la copie de code inutile.


Bref transformons ça avec notre test builder

tout d'abord on va ecrire une classe interne notre "test builder"


private static class QueVendreAMéméeTestBuilder{
  Collection<ObjetAVendreAvecLeurPrix> resultats;
  Mémée laMéméeATesté = new Mémée();
  RichesseDeMéméeService richesseDeMéméeService = mock(RichesseDeMéméeService.class);

  public QueVendreAMéméeTestBuilder méméeGénéreuse(Boolean généreuse){
    laMéméeATesté.setGénérosité((généreuse)?Mémée.Généreuse:Mémée.Radine);
    return this;
  }

  public QueVendreAMéméeTestBuilder méméePossède(ObjectAVendre object){
    laMéméeATesté.ajouteAuxObjetsQuePossedentMémée(object);
    return this;
  }


  public QueVendreAMéméeTestBuilder méméRiche(Boolean riche){
    when(richesseDeMéméeService.isMéméeRiche(laMéméeATesté)).thenReturn(riche);
    return this;
  }

  public QueVendreAMéméeTestBuilder regardeCeQueTuPeuxVendreAMémée(){
    resultats = new VRPService().QueVendreAMémée(laMéméeATesté);
    return this;
  }

  public QueVendreAMéméeTestBuilder nombreDobjetAVendre(int nb){
    assertEquals(nb,resultats.size);
    return this;
  }

  public QueVendreAMéméeTestBuilder onPeuxVendre(ObjectAVendre object, double aQuelPrix){
    for (ObjectAVendre o : resultats){
      if (o.getObjectAVendre().equals(object)){
    assertEquals("Mémée peux acheter " + object + " a " o.getPrix() " et pas à " + aQuelPrix, aQuelPrix, o.getPrix());
    return this;
      }
    }

    assertFail("Mémée n'a jamais voulu acheter : " + object);
  }
}



ensuite on va créer le test qui va avec

@Test
public void testQueVendreAMémée_qandMéméeEstRicheEtGénéreuse(){
  new QueVendreAMéméeTestBuilder()
      .méméeGénéreuse(true)
      .méméeRiche(true)
      .méméePossède(ObjectAVendre.Aspirateur)
      .regardeCeQueTuPeuxVendreAMémée()
      .nombreDobjetAVendre(1)
      .onPeuxVendre(ObjectAVendre.Télévision,100.000);
}


et comme je suis généreux comme mémée je vous offre la version méméePauvreEtGénéreuse
@Test
public void testQueVendreAMémée_qandMéméeEstPauvreEtGénéreuse(){
  new QueVendreAMéméeTestBuilder()
      .méméeGénéreuse(true)
      .méméeRiche(false)
      .méméePossède(ObjectAVendre.Aspirateur)
      .regardeCeQueTuPeuxVendreAMémée()
      .nombreDobjetAVendre(1)
      .onPeuxVendre(ObjectAVendre.ProgrammeTV,500);
}


Reste une question en suspens, que fait la mémé pauvre avec son programme TV sans TV ? Laissez vos commentaires.

mercredi 18 janvier 2012

developpement d'une librairie pour prioriser des traitements

Imaginons que vous travaillez pour une chaine de restauration rapide. A l'heure du rush, un bus rempli de chef d'état se gare sur votre parking et se disent "on se tapperais bien un ptit mc do". Des lors, vous vous dites que ça serait pas mal de les servir rapidement... mais quand même faut pas déconner les clients normaux c'est quand même eux qui ramène les pépetes le reste de l'année et il va bien falloir les servir sans qu'ils soient trop laisés.

Il y a quelques mois, j ai été  confronté à ce genre de problématique. Sauf que ce n'était pas des sandwichs que je fournissais mais de jolis retour de méthode.

De là est né une petite librairie que vous n' avez jamais entendu parlé : priority queue proxy.

Le priority proxy c'est l' idée de rendre transparent la gestion de la priorisation des appels de services que ce soit au niveau consumer ou producer. Et comme son nom l' indique on fait tout ça avec un beau proxy java et des thread queues.

Tout ca marche merveilleusement bien avec un bon vieux spring mais pas que.  

Petit exemple avec spring:

On crée l'éxecuteur, c'est lui qui s'occupe de dire qui s'execute ou 

<bean id="priorityQueueProxyExecutor" class"....priorityExecutor.Executor">
<constructor-args value="10">
</bean>

L'argument du constructeur est la taille du tread pool partagé par tous les clients. C'est sur ce thread pool que sont gérées les priorités.


On créer les services :

<bean id="lowPriorityService" factory-bean="je vous l'explique plus tard">
              <constructor-arg value="l'interface du service"/>
              <constructor-arg><bean class="monImplementationDeService"></constructor-arg>
              <constructor-arg>5</constructor-arg>
              <constructor-arg>0</constructor-arg>

              <constructor-arg>priorityQueueProxyExecutor</constructor-arg>
</bean>


<bean id="highPriorityService" ...

              <constructor-arg value="l'interface du service"/>
              <constructor-arg>...</constructor-arg>
              <constructor-arg>10</constructor-arg>
              <constructor-arg>5</constructor-arg>
              <constructor-arg>priorityQueueProxyExecutor</constructor-arg>
</bean>


Explication, On a maintenant deux services différents qui représentent en réalité la même chose mais avec des priorité différentes : 

Le premier service a une priorité de 5 et n'a pas de thread réservé pour lui tout seul.

Le deuxième service a une priorité de 10 et possede 5 slot privé qu'il est seul a utilisé. (Des caisses réservé pour les présidents dans le cas du McDo)




Tout ça est tres joli et ou je le telecharge me direz vous ? Bah soit vous venez travaillez avec moi dans ma boite, soit vous ne pouvez-pas.

Mais n' envisagez pas pour autant le suicide car je vous propose de faire la version 2 en LGPL avec des fonctionnalités à vous faire pousser des rouflaquettes:
  • Gestion de pool de thread privé plus fine.
  • Meilleure gestion de la concurence (utilisation eventuel des packages java.util.concurent).
  • Tread affinity pour une configuration intelligente des threads. 
  • Gestion des priorités même sur une ferme de provider de service (un cloud pour faire jeune)
  • Gestion des priorités changeantes (Finalement les types dans le bus etaient certes des chefs d' état mais surtout d'anciens dictateurs balayés par les révolutions arabes) 
  • Configuration des priorités au runtime...   
  • D'autres idées m' étaient venues mais je mettrais à jour plus tard car ça ne vient plus  


Alors tenté par une aventure open source ?