Tests unitaires avec JUnit et Spring

Dans une architecture raisonnable, la couche de services qui contient les traitements métier et la couche d'accès aux données, ou DAO, qui contient toutes les requêtes JDBC ou la manipulation des sessions hibernate, sont strictement disjoints.

Dans cette architecture, les transactions sont gérées par la couche de services. Avec Spring, ces transactions peuvent être gérées de façon automatique, à l'aide d'annotations ou de techniques orientées aspect.



Architecture-spring-test.png

Le "Tip" présenté ici a été testé avec Spring 2.5, JUnit 4.4 et Hibernate 3.2. Les tests ont été exécutés sous Eclipse 3.3.

Test des beans de services

Dans cette architecture, tester les beans de service est la tâche la plus importante. Elle ne pose aucun problème puisqu'aucun prérequis n'est exigé, du point de vue transactionnel. Il suffit donc de créer une classe de test par classe de service.

Initialisation manuelle

La classe de test peut initialiser elle-même le contexte Spring, et récupérer le bean à tester, dans une méthode annotée @BeforeClass.

 public class CourseServiceTest {
   private static CourseService service;
   private static ClassPathXmlApplicationContext context;
   
   @BeforeClass
   public static void initHibernate() throws Exception {
     //service = new CourseServiceImpl(new CourseDaoJdbc());
     context = new ClassPathXmlApplicationContext("applicationContext.xml");
     service = (CourseService)context.getBean("courseService");
   }
   
   ...
 }

La suite du test se déroule de façon traditionnelle : une méthode de test, annotée par @Test, par méthode à tester, ou à peu près...

 public class CourseServiceTest {
   ...
 
   @Test
   public void testFindAll() {
     List<CourseData> allCourses = service.findAll();
     assertNotNull("null !", allCourses);
     assertTrue("Rien trouvé", allCourses.size() > 0);
     System.out.println(all);
   }
 
   @Test
   public void testFindByCriteria() {
     ...
   }
 
   @Test
   public void testInsertUpdateDelete() {
     ...
   }
 
   @Test
   public void testInsertWithRollback() {
     ...
   }
 }

Initialisation par annotation

Plutôt que de gérer les initialisations manuellement, il est possible de demander à Spring d'injecter les beans nécessaires.

La première opération nécessaire est d'annoter la classe pour que le test transite par Spring, avec le bon fichier de contexte.

 @RunWith(SpringJUnit4ClassRunner.class)
 @ContextConfiguration(locations={"/applicationContext.xml"})
 public class CourseService25Test {
   ...
 }

La seconde opération est d'injecter le bean à tester, par son type avec l'annotation @Autowired ou par son nom avec l'annotation @Resource.

   @Resource
   private CourseService courseService;

La fin est identique à la façon traditionnelle : développer les méthodes de test.


Test des beans de DAO

Le test des beans de DAO pose un problème supplémentaire : celui des transactions. En effet, lorsqu'une méthode de DAO est appelée, un transaction doit être démarrée, puis doit être validée après l'appel de la méthode.

Cette gestion de transaction doit donc être faite par la classe de test.

Transactions par programmation

En accédant le TransactionManager, il est possible de démarrer et de conclure des transactions depuis la classe de test. Ces transactions engloberons naturellement les requêtes soumises à la base par le composant DAO.

 public class CourseDaoSpringTest {
 
   private static PlatformTransactionManager transactionManager;
 
   @BeforeClass
   public static void setUpBeforeClass() throws Exception {
     ...
     transactionManager = (PlatformTransactionManager) context.getBean("txManager");
   }
   
   @Before
   public void setUp() {
     status = transactionManager.getTransaction(null);
     logger.info("Session et transaction initialisés");
   }
   
   @After
   public void tearDown() {
     transactionManager.commit(status);
   }
   
   ...
 }

Le reste des tests peut se faire comme pour une classe de service : initialisation du contexte et du bean, puis méthodes de test.

Transactions par annotation

Pour que la classe de test puisse démarrer et valider les transactions, il faut ajouter l'annotation @Transactional, et pour spécifier le nom du bean de gestion de transaction, s'il est différent de "transactionManager", on ajoute l'annotation @TransactionConfiguration.

 @RunWith(SpringJUnit4ClassRunner.class)
 @ContextConfiguration(locations={"/applicationContext.xml"})
 @TransactionConfiguration(transactionManager="txManager")
 @Transactional
 public class CourseDaoSpring25Test {
   ...
 }

Le reste des tests peut se faire comme pour une classe de service : injection du bean (@Autowired ou @Resource) puis méthodes de test.