Contenu connexe

Workshop Spring 3 - Tests et techniques avancées du conteneur Spring

  1. Workshop Spring - Session 3 Tests & Techniques avancées du conteneur Spring Conçu en décembre 2011 Réactualisé en novembre 2014 # 1
  2. Sommaire Tests avec pring-test , DBUnit et Mockito 3 Injection de beans de portées différentes 8 Support des JSR 250 et 330 11 Usage des post-processeurs de bean 13 Externalisation de la configuration 17 Accès aux ressources externes 19 2
  3. Spring et les Tests Conteneur léger accessible aux tests unitaires et d’intégration Support de JUnit 4 et TestNG Chargement du contexte Spring Injection de dépendances Mise en cache du contexte Extensions JUnit par Runner : SpringJUnit4ClassRunner Annotations : @ContextConfiguration, @Rollback, @Repeat … Listeners : DependencyInjectionTestExecutionListener … Bouchons prêts à l’emploi MockHttpSession, MockHttpServletRequest, MockPortletSession ... 3 Les apports du module Spring Test
  4. 4 Spring et les Tests Test d’intégration d’un DAO @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("applicationContext-daos.xml") @Transactionnal public class IntTestHibernateAccountDao { @Autowired private HibernateAccountDao accountDao; @Rollback(false) @Test public void chargerCompteParIBAN(){ String iban = = "FR70 3000 2005 5000 0015 7845 Z02"; Account account = accountDao.findAccountByIBan(iban); assertNotNull(account); assertEquals("Account 1", account.getDescription()); } }
  5. Retour d’Expérience projet Fixe un cadre technique d’utilisation de frameworks pour les tests : JUnit, spring-test, DbUnit, H2, Logback et Mockito Fournit une hiérarchie de classes réutilisables pour écrire un test unitaire Selon le besoin : test simple, avec Spring, JDBC, Hibernate, JSF Dans le but de tester une classe : notion de systemUnderTest (SUT) Fournit des annotations simplifiant la configuration des tests : @InjectInto : injection de propriétés dans l’instance testée @DataSet : charge un dataset DBUnit avant l’exécution du test @LoggingConfiguration : spécifie le fichier de configuration logback Outil de génération de dataset XML DbUnit à partir d’entités Hibernate ou JPA 5 Présentation d’une extension maison
  6. 6 Retour d’Expérience projet Test unitaire d’un DAO avec cette extension Jeu de données DbUnit : TestHibernateAccountDao-dataset.xml <?xml version='1.0' encoding='UTF-8'?> <dataset> <ACCOUNT ID="1" BIC="FR7030002005500000157845Z02" LABEL=“Account 1"/> <ACCOUNT ID="2" BIC="FR70300023455000021Z4234Y45" LABEL="Account 2"/> </dataset> + @LoggingConfiguration("classpath:logback-test-dao.xml") public class TestHibernateAccountDao extends AbstractDatasetSpringContextTest<HibernateAccountDao> { @Test public void findAccountByIBan() { String iban = "FR70 3000 2005 5000 0015 7845 Z02"; Account account = systemUnderTest.findAccountByIBan(iban); assertNotNull(account); assertEquals("Account 2", account.getDescription()); } }
  7. 7 Retour d’Expérience projet Test unitaire d’un service avec cette extension @LoggingConfiguration("classpath:logback-test-service.xml") public class TestPublishingService extends AbstractCustomSpringContextTest<IPublishingService> { @Mock @InjectInto private IPublishingWebService publishingWs; @Test(expected = UnavailablePublishingException.class) // Assertions public void generatePDFwithPublishingTimeout () throws InvalidDataException, UnavailablePublishingException { // Prépare les données financialInvestment = new FinancialInvestment(); // Bouchonne les services tiers when(publishingWs.save(any(Document.class) .thenThrow(new SocketTimeoutException()); // Exécute le scénario systemUnderTest.generatePDF(financialInvestment ); } }
  8. Injection de beans de portées différentes • Rappels • Un bean de portée Singleton se doit d’être thread-safe • Par défaut, un singleton est créé au démarrage du conteneur • Un bean de portée session est créé pour chaque session web utilisateur • Problématique – Injecter par auto-wiring un bean de portée Session dans un bean de portée Singleton • Cas d’usage – Avoir accès dans un service métier aux informations de l’utilisateur connecté – Besoin : contrôle des habilitations, historisation, logs … • Solution • Proxifier le bean de portée Session • Le proxy est chargé d’aiguiller les appels vers le bean session approprié 8 Injecter un bean de portée session dans un singleton (1/2) @Autowired protected UserDetails userDetails;
  9. 9 Injection de beans de portées différentes Injecter un bean de portée session dans un singleton (2/2) Service singleton Proxy Informations Utilisateur Conteneur Spring Contrôleur singleton James session John session +MISE EN OEUVRE <bean id="authenticatedUserDetails" scope="session" class="com.javaetmoi.core.security.UserDetailsFactory" factory-method="getUserDetails"> <aop:scoped-proxy /> </bean> getName() getName() +ILLUSTRATION Implémentation basée sur le SecurityContextHolder de Spring Security
  10. Injection de beans de portées différentes • RAPPEL – Une nouvelle instance est créée à chaque fois qu’un bean prototype est référencé • Problématique – Avoir un nouveau bean à chaque fois qu’on y fait appel depuis un singleton – Cas d’usage : Utiliser le pattern commande ou une classe non thread-safe depuis un singleton • Solution : Lookup ou injection de méthode 10 Injecter un prototype dans un singleton <bean id=“contractBuilder" class=“ContractBuilder" scope=“prototype“/> <bean id=“publishingService" class=“PublishingService"> <lookup-method name="getContractBuilder" bean="contractBuilder"/> </bean> public abstract class PublishingService { public void generatePDF() { ContractBuilder bld = getContractBuilder(); bld.setNumber("CA0000019876") // ... } abstract ContractBuilder getContractBuilder(); }
  11. Support des JSR • Common annotations 1.0 • Introduites dans Java EE 5 et dans Java 6 dans le package javax.annotation • Leur usage permet de coller aux standards et de se découpler de Spring • Annotations supportées • @PostConstruct : méthode appelée une fois le bean instancié et ses dépendances injectées • @PreDestroy : méthode appelée avant la destruction du bean • @RolesAllowed : sécurise l’appel d’une méthode (équivalent à @Secured) • @Resource : permet d’injecter une ressource JNDI (ex: DataSource) • Activation des annotations 1. Par un post processeur de beans Spring <bean class="org.springframework.context. annotation.CommonAnnotationBeanPostProcessor"/> 2. Ou par la balise XML annotation-config : <context:annotation-config/> 11 JSR 250 - Common annotations
  12. Support des JSR • API Légère dédiée à l’injection de dépendances o Standardisation des annotations utilisées dans Spring et Google Guice o 5 annotations et 1 interface disponibles dans le package javax.inject : @Inject @Named, Provider, @Qualifier, @Scope, @Singleton • Support o JSR-330 Supportée à 100% par Spring 3.x o L’annotation @Inject peut se subsTituer à @Autowired o Les annotations @Qualifier fonctionnent différemment o Le changement de portée par défaut des beans spring peut être résolu par la classe Jsr330ScopeMetadataResolver • Préconisations o A utiliser pour le développement de frameworks / OSS o Intéressant dans une application lorsque Spring est uniquement utilisé comme simple moteur d’injection de dépendances 12 JSR 330 - Dependency Injection for java
  13. Usage des post-processeurs de bean • Caractéristiques o Appelé pendant le processus de création de tout bean Spring o Implémente 2 méthodes appelées avant et après la méthode d’initialisation du bean o Peuvent être ordonnés • Etapes de mise à disposition d’un bean 1. Création du bean par constructeur ou appel de méthode statique 2. Valorisation des propriétés du bean 3. Résolution des références vers d’autres beans (wiring) 4. Appel de la méthode postProcessBeforeInitialization() de chaque post-processor 5. Appel des méthodes d’initialisation du bean 6. Appel de la méthode postProcessAfterInitialization() de chaque post-processor 7. Le bean est prêt à être utilisé 13 Fonctionnement
  14. Usage des post-processeurs de bean • Par spring 14 Utilisation au quotidien Post-Processeur Description AutowiredAnnotationBeanPostProcessor Active l’auto-wiring via les annotations @Autowired ou @Inject CommonAnnotationBeanPostProcessor Active la détection des annotations de la JSR-250 AsyncAnnotationBeanPostProcessor Active la détection de l’annotation @Async ApplicationContextAwareProcessor Permet de passer le contexte applicatif Spring aux beans implémentant l’interface ApplicationContextAware ScriptFactoryPostProcessor Permet d’accéder au résultat de scripts (Groovy, JRuby ) • Par des frameworks tiers Post-Processeur Description BusExtensionPostProcessor Active la détection automatique d’extension de bus CXF Jsr181BeanPostProcessor Support de l’annotation @WebService par XFire
  15. Usage des post-processeurs de bean • Problématique : enregistrer son propre plugin auprès du conteneur Spring pour traiter les instances de beans avant leur utilisation • Cas d’usage : Injecter un logger dans un bean 15 Créer sa propre annotation spring (1/2) @Service public class BankingService { private static final Logger LOGGER = LoggerFactory.getLogger(BankingService.class); … } @Service public class BankingService { @Logger private static Logger logger; … }
  16. Usage des post-processeurs de bean Créer sa propre annotation spring (2/2) • Solution : créer une annotation et le post-processeur de bean chargé de l’interprêter @Retention(RetentionPolicy.RUNTIME) @Target( { ElementType.FIELD }) public @interface Logger {} public class LoggerBeanPostProcessor implements BeanPostProcessor { 16 public Object postProcessBeforeInitialization(Object bean, String beanName) { ReflectionUtils.doWithFields(bean.getClass(), new ReflectionUtils.FieldCallback() { public void doWith(Field field) { if (field.getAnnotation(Logger.class) != null) { ReflectionUtils.makeAccessible(field); Logger logger =getLogger(bean.getClass().getName()); ReflectionUtils.setField(field, bean, logger); } } }); return bean; } … }
  17. Externalisation de la configuration • Problématique o Le paramétrage d’une application web dépend de son environnement o Exemples : URL d’un web service, login / mot de passe, host d’un serveur mail o Comment externaliser leur paramétrage ? • Contraintes o L’EAR Validé en recette est celui déployé en production o Les paramètres doivent être externalisés en dehors de l’EAR o L’externalisation ne doit pas dépendre du serveur d’application o L’utilisation des URLResource de Websphere sont par exemple écartées • Solution envisagée o Utiliser un fichier de configuration qui sera accessible via le classpath o Activer le post-processeur de fabrique de beans PropertyPlaceholderConfigurer 17 Etude de Cas dans une application web
  18. Externalisation de la configuration • Fichier de configuration 18 Injection de valeur Contenu du fichier config.properties: banking.iban.valid = true • Chargement par Spring <context:property-placeholder location="classpath:com/javaetmoi/config.properties" /> • Code Java @Service public class BankingService implements IBankingService{ @Value("${banking.valid.iban}") private Boolean validIban; … }
  19. Chargement des ressources externes • Constat o Java n’offre pas de mécanisme unifié pour récupérer le contenu d’une ressource • Exemples de ressources : – Fichier texte, fichier de configuration XML, fichier properties, images … • Exemples de localisation : – Sur le système de fichier, dans un JAR inclu dans le classpath, via une URL … • Méthodes possibles o getClass().getResourceAsStream("path") o new URL("url").openConnection(). getInputStream() o new FileInputStream(new File("path")) • Problématique o Comment s’abstraire de la localisation d’une ressource ? 19 Méthodes hétérogènes
  20. Chargement des ressources externes • Solution – Utiliser l’abstraction de ressources proposées par Spring via l’interface Resource • Une implémentation par type de localisation – FileSystemResource, ClassPathResource, UrlResource • Offre plusieurs fonctionnalités – Ouverture d’un flux, existante physique, description … • Localisation d’une ressource – La syntaxe des chemins d’une ressource est spécifique à Spring – Spring se base sur le préfixe pour déterminer quelle implémentation utiliser 20 Un mécanisme Unifié avec Spring Localisation de la ressource Exemple de chemins Classpath de la JVM classpath:com/javaetmoi/applicationContext-service.xml Accessible par une URL http://javaetmoi.com/feed.rss Sur le système de fichiers file:/d:/appli/demo/config.properties
  21. Chargement des ressources externes • Utilisation par Spring – En interne, de nombreuses classes du framework utilisent l’interface Resource – Le contexte applicatif Spring dispose d’un chargeur de ressources – Exemple : new ClassPathXmlApplicationContext ( tableau de fichiers de conf ) • Injection de Ressources 21 Exemples d’utilisation public class MailAutomatiqueService { private Resource mailTemplate; // Getter } <bean id="emailService" class="com.javaetmoi.EmailService"> <property name="mailTemplate"> <value>classpath:com/javaetmoi/bfa/mail.vl</value> </property> </bean>