lundi 29 août 2011

Tests unitaires de DAO avec Spring et Hibernate avec base de données en mémoire

Je profite de ce blog pour partager une configuration qui m'a beaucoup aidé pour mettre en place des tests unitaires de DAO (ou Repository suivant les goûts).

Des TU avec une base de données, c'est pas très unitaire...

Alors oui, utiliser un système externe même léger déroge à la règle du test unitaire. MAIS à mon humble avis, la plus value du code d'un DAO réside dans les requêtes que l'on développe et non dans la vérification du bon fonctionnement du moteur de persistence.

Bref, tester via des mocks que la méthode add appelle bien la méthode persist de l'EntityManager, c'est bien mais pour les requêtes "faites mains", c'est nettement moins efficace... D'où l'idée de tester en "vrai", les requêtes et leur comportement.

L'utilisation d'une base de données en mémoire pour le déroulement des tests est donc pratique pour plusieurs points :
  • facilité des tests unitaires
  • rapidité
  • indépendance des tests par rapport à l'utilisation d'une base de données commune aux développeurs
Il y a évidemment quelques inconvénients comme certaines requêtes utilisant des fonctions propriétaires ou des séquences qui ne sont pas toujours supportés par la base en mémoire mais dans 90% des cas d'usages, il n'y a pas de problèmes.

Gérer automatiquement la mise à niveau de la base de données c'est fastidieux

Oui mais non... Pas pour les tests unitaires.
Si on réfléchit, notre code contient une description de la base de données sur laquelle il est sensé s'exécuter, c'est le mapping ORM du moteur de persistence. Et le mapping, il est souvent à jour.

Dans notre cas, Hibernate dispose de la fonctionnalité de générer la création de la base de données lors de son lancement. Donc pour mettre à jour la base de données, il suffit de modifier le mapping. Cerise sur le gâteau, cela motive les développeurs à ajouter un maximum d'informations dans le mapping (unicité, contraintes, taille des champs, etc).

Configuration

Spring va nous aider beaucoup pour accomplir cette tache en nous simplifiant la définition de la Datasource.

Nous utilisons HSQLDB qui est une base sous forme de fichier qui dispose aussi de la fonctionnalité recherché et qui est assez connu (présente dans OpenOffice, notamment). Mais il en existe d'autres (H2, ...).

Définition de la Datasource dans Spring

Un fichier applicationContext-test.xml dans le répertoire src/test/resources avec ces éléments :
<!-- Datasource -->
<bean id="dataSource"
 class="org.springframework.jdbc.datasource.DriverManagerDataSource">
   <property name="driverClassName" value="org.hsqldb.jdbcDriver" />
   <property name="url" value="jdbc:hsqldb:mem:test" />
   <property name="username" value="sa" />
   <property name="password" value="" />
</bean>

<!-- Entity Manager Factory -->
<bean id="entityManagerFactory"
 class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
  <property name="persistenceUnitName" value="releasemanager" />
  <property name="dataSource" ref="dataSource" />
  <property name="jpaVendorAdapter">
    <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
      <property name="showSql" value="true" />
      <property name="generateDdl" value="true" />
    </bean>
  </property>
</bean>

<!-- bean post-processor for JPA annotations -->
<bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor" />

<!-- Exception translation bean post processor -->
<bean class="org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor" />

Cette configuration permet de définir les éléments suivants :
  • une datasource JDBC vers une BDD en mémoire
  • une EntityManagerFactory avec Hibernate pour injecter les EntityManagers
  • une gestion transactionnelle avec les @Transactional

Et les tests dans tout ça ?

Les tests de DAO sont simples et sont les mêmes que pour un accès vers une vraie base de données.

Dans l'exemple suivant, on teste une méthode login qui permet de récupérer un utilisateur à partir de son login/password :

//On configure JUnit pour utiliser directement Spring
//On pointe vers le fichier de configuration Spring à la racine du classpath
//On définit toutes les méthodes comme transactionnelle pour les requêtes d'écriture
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"/applicationContext-test.xml"})
@Transactional
public class UserDaoImplTest {

  @Autowired
  private UserDaoImpl dao; // on injecte automatiquement le DAO

  @Test
  public void testLoginExistingUser() {
       User loggedUser = dao.login("login","password");
       assertNotNull(loggedUser);
  }

  @Test(expected=EmptyResultDataAccessException.class)
  public void testLoginWithWrongCredentials() {
       dao.login("login","wrongPassword");
  }

}

Conclusion


Avant d'exécuter les tests, Hibernate va s'initialiser et générer une base de données correspondant au mapping. Cette configuration permet donc de tester rapidement et facilement les DAO et leurs requêtes dans un contexte quasi-réel avec les avantages suivants :
  • les tests sont répétables et identiques pour tous les développeurs
  • facile à mettre en place (pas de bases à installer sur son poste),
  • facile à faire évoluer (pas de gestion de scripts de modification)
Have fun with tests !

dimanche 7 août 2011

Un Mockito sans glace, svp !

Le framework de mock Mockito depuis sa version 1.8.3 dispose de nouvelles annotations permettant de diminuer notre code de test.

L'annotation @InjectMocks

Posée sur un attribut, elle permet de définir l'objet qui recevra vos mocks. Un example valant mieux qu'un long discours :
@RunWith(MockitoJunitRunner.class)
public class AccountServiceTest {

  @InjectMocks
  private AccountService service = new AccountService();

  @Mock
  private AccountDao dao;

  @Test
  public void createAccountShouldCallSave() {
     Account account = new Account();

     service.createAccount(account);
     verify(dao).save(account);
  }
L'annotation @Mock permettait déjà de se passer de la création des mocks suivante :
AccountDao mockedDao = mock(AccountDao.class);


L'annotation @InjectMocks permet de se passer du code d'injection des mocks dans la classe à tester, qui dans l'exemple serait effectué par un simple setter :
service.setAccountDao(mockedDao);

ou par l'appel à l'utilitaire de réflexion de Spring :
ReflectionTestUtils.setField(service, "accountDao", mockedDao);

Pas grand chose en soi, mais avec des fois beaucoup de tests et plusieurs dépendances ça simplifie la vie et le code est plus lisible.

Helper 1 : Activation des annotations

Il existe 2 manières d'activer les annotations Mockito :

1 - Utiliser le Runner Junit (solution ci-dessus)
2 - Vous utilisez déjà un autre Runner (Spring par exemple ?), alors l'initialisation statique est faite pour vous ! Il suffit d'écrire un @Before pour initialiser le test :
@Before
public void setUpMockito() {
   MockitoAnnotations.initMocks(this);
}

Helper 2 : Instantiation automatique

Depuis la version 1.9, l'initialisation du champ annoté par @InjectMocks est inutile :
@InjectMocks
private AccountService service;

dimanche 26 juin 2011

Jenkins dans les nuages

Une abeille m'a dit récemment qu'il était possible d'avoir un serveur d'intégration continue et de déployer son application sur le cloud en 2 minutes.
Challenge Accepted !
Voici donc les étapes qu'il m'a fallu pour arriver à ce résultat :


1 - Création du compte
Une fois sur le site de CloudBees, 2 chemin possibles : Soit par "Pricing", Soit directement "Sign Up".
Passé le formulaire d'enregistrement, on est invité à souscrire aux offres que l'on souhaite.


2 - Souscriptions
A ce moment, c'est un peu les soldes, on dispose d'un large choix :

  • le DEV@Cloud avec un serveur Jenkins et le quota de build de 300 minutes par mois
  • le RUN@Cloud avec 5 applications que l'on peut déployer sans toutefois la sécurité ni la scalabilité
  • une base de données MySQL à hauteur de 5 MB
  • en parlant de soldes, le partenariat avec SauceLabs donne une offre gratuite jusqu'au 20 Septembre avec des quotas qui permettent de se faire une bonne idée du service je pense (16 000 minutes / mois et 48 tests simultanés)
  • pour le coup, le Sonar est payant (snif !)
  • le repository d'artefact fourni par JFrog est payant aussi
  • une offre New Relic gratuite qui offre un monitoring global des applications
  • enfin une offre Cloudant pour une base de données distribuée As a Service basée sur Apache CouchDB
Une phase de validation à la Google avec un serveur vocal qui nous donne un code. Le message est en français s'il vous plait !


3 - Configuration
Fin du shopping, voyons voir ce qu'on peut faire. Sans surprise, on accède à une instance de Jenkins, une liste déjà conséquente de plugins est disponible en mise à jour.
Création du projet, configuration des sources, type de build, on n'est pas perdu.
A noter que CloudBees propose avec son offre un repository Git, mais il est tout à fait possible de connecter le Jenkins à son compte GitHub.
Pour ce faire, ne faites pas comme moi, pensez à ajouter la clé fournie par le Job Jenkins dans votre compte GitHub pour qu'il aille récupérer les sources. (RTFM...)


4 - Build
Lancez votre premier build, et pour les essais que j'ai fait avec des projets Maven, je n'ai pas eu de soucis de ce coté là. Si vous l'avez configuré, vous recevez les rapports de build sur votre boite mail, et c'est fini.


5 - Deploy
Le déploiement sur son instance RUN@Cloud est assez simple, on crée une application via son compte et il suffit d'installer le plugin CloudBees deploy dans le Jenkins. Pour la configuration, on est tenu par la main en nous proposant les noms disponibles.
On relance le build et quelques minutes plus tard, on peut commencer à faire joujou avec son application.


PS : ne mettez pas d'accent dans le Name pour la configuration du plugin de déploiement, il n'aime pas du tout.


6 - Mais mon application, comment je la configure pour la base de données ?
Très simple là encore, plusieurs solutions sont proposées via l'instance, il suffit de copier-coller le code proposé dans les bons fichiers et on y est.
cela correspond à un adressage JNDI, donc rien d'exotique, cela marche très bien qu'on soit en Spring ou JavaEE. Le seul fichier spécifique n'a pas d'effet de bord sur les conteneurs habituels. Ah oui et la base de données est sauvegardable en un clic !


Conclusion
L'abeille avait raison ! Tout ça reste du grand classique mais qui a le mérite d'être simple et il ne m'a fallu une bonne vingtaine de minutes pour monter un serveur Jenkins, un repository Git, une base MySQL et un Tomcat, 10 minutes de plus et j'ai mon premier build et l'application déployée et accessible. Sans aucune administration matérielle à assurer...


Certes, un compte gratuit ne permettra pas d'utiliser la plateforme pour un véritable projet de développement d'entreprise, mais il est largement suffisant pour se faire une idée de la plus value de la solution. D'ailleurs, même le compte gratuit, j'y vois un intérêt pour des formations, présentations, démos ou ateliers.


En ce qui concerne le service sur le cloud, par rapport à un AppEngine par exemple, c'est sans doute une question de goût. Pour me part, je suis plus séduit par la possibilité d'avoir une base de données MySQL sur laquelle mon code JPA pourra fonctionner complètement sans avoir un guide de développement décrivant les fonctionnalités possibles ou non. Même si je reconnais que la base de Google BigTable doit sans aucun doute mieux monter en charge, je n'en ai pas forcément besoin. Par contre, je suis certain d'avoir besoin d'un serveur d'intégration continue qui peut déployer une application à la demande ou en continu. Donc, la plus value du Jenkins remporte pour moi tous les suffrages.