Affichage des articles dont le libellé est java. Afficher tous les articles
Affichage des articles dont le libellé est java. Afficher tous les articles

dimanche 23 octobre 2011

plan de test multi-langages depuis Java

Problématique
Voici quelques mois, un collègue me demanda si la conception d'un plan des tests était facile.
Bien évidemment, il y a plein d'outils pour gérer des tests unitaires et des plans de tests, JUnit en tête ( et même via Ant :D )
Le soucis résidait au fait qu'il fallait lancer des scripts qui lancent d'autres scripts. Ce sont ceux-là qu'il fallait gérer. Plus tard, l'intégration dans une interface graphique devint nécessaire.
Moins trivial que cela donc, il fallait concevoir un outil. Quelques nuits passées sur le sujet, à essayer de gérer les codes retours rendus par l'invite DOS me persuadèrent de la difficulté. Les nuits portent conseils, ceux-ci furent pourtant long à venir.

Il a bon DOS celui-là
Vous trouverez sur Internet de nombreux conseils sur comment récupérer les codes retours du DOS. Malheureusement, la situation n'est pas universelle suivant les versions de Windows. Pas mal de recherches dans un navigateur ne sont pas forcément valables.
Chose étonnante, si lé récupération du code DOS est erratique en Java, la récupération d'un code Python et autre informations est d'une conformité confondante aux standards Java. Encore une enigme microsoftienne de plus.
Voici ci-après quelques copies d'écran concernant les développements en cours.

Rassembler le patchwork applicatif
  • l'interface graphique attendue avec les fonctionnalités de création, modification, exécution, suppression et historique des plans de tests.
  • les traitements sur les plans de tests
  • la création des tests unitaires successifs, conditionnés
  • l'affichage des historiques grâce aux fonctions java 6 natives.
Conclusion rapide
Les développements étant encore en phase d'ébauche, je ne donnerai pas plus d'informations concernant cet n-ième outil. La seule chose que je puisse affirmer, c'est qu'il fonctionne très bien ;) Reste à le rendre ergonomique.

vendredi 19 novembre 2010

Découvrir et lancer les classes d'un jar ayant une méthode "main"

Introduction

Encore une utilité de l’introspection : afficher les classes disposant d'une méthode "main" dans un jaret offrir la possibilité de la lancer. C'est bien pratique quand on ne veut pas lancer la main-class du manifest.

Méthode

  • Référencer le jar dans un classLoader
  • Parcourir les entrées du jar pour ne prendre que les classes ayant une méthode publique, statique dénommée "main"
  • Les afficher par exemple dans un ordre chronologique

Extrait de code concernant l’extraction

File f = new File(location);
loader = new URLClassLoader(new URL[] { f.toURI().toURL()  } );
JarFile jf = new JarFile(location);
Enumeration e = jf.entries();
JarEntry je = null;
for( int j=0 ; e.hasMoreElements() ; j++ )
      {
      je = (JarEntry)e.nextElement();
//---------------------------------------------
public static boolean hasPublicStaticVoidMainMethod ( Class _c )
{
      Method [] publicMethods = _c.getMethods() ;
      for ( Method uneMethode : publicMethods )
      {
            int m = uneMethode.getModifiers() ;
            if ( ( Modifier.isPublic(m) ) && ( Modifier.isStatic(m) ) && ( uneMethode.getReturnType().toString().compareTo("void") == 0 ) && ( uneMethode.getName().compareTo("main") == 0 ) )
            {
                  return true ;
            }
      }
      return false ;
}
//---------------------------------------------

Extrait de code concernant l'invocation de la méthode

Class c = Class.forName(s);
Class[] argTypes = new Class[] { String[].class };
Method main = c.getDeclaredMethod("main", argTypes);
String[] mainArgs = {} ;
main.invoke(null, (Object)mainArgs);

Copies d’écrans correspondantes


Exemple concernant rt.jar


dimanche 19 septembre 2010

Rétro-conception de Design Patterns java

Objectif

être capable de retrouver des Design Patterns éprouvés dans des classes Java.

Intérêt

Même si de nos jours les outils de rétro-conception savent très bien passer de classes java à des diagrammes UML, un pas supplémentaire peut être fait en retrouvant les Design Patterns noyés anonymement dans des schémas. Un niveau d'abstraction supplémentaire serait donc le bienvenu.

Bonnet blanc et blanc bonnet

Il existe en fait de nombreuses façons de concevoir ces Design Patterns, avec des variantes mineures, qui font tout le charme des algorithmes visant à retrouver les 3 types de DP ( construction, structuration, comportement ). Un groupe connu sous le nom de "bande des quatre" ( Gang of four en Anglais ) est cité sur le site de SUN comme étant le premier à s'être penché sur le problème.
Cependant, l'expérience a prouvé que les concepteurs de SUN n'avaient pas toujours suivi ces bonnes pratiques standard.

Cas du Pattern "Singleton"

Il nécessite :
  • un constructeur privé
  • une méthode d'instance
  • un attribut d'instance

Exemple du pattern Singleton :

public class Singleton
{
    /** Récupère l'instance unique de la class Singleton.<p>
     * Remarque : le constructeur est rendu inaccessible
     */
    //---------------------------------------------  
    public static Singleton getInstance()
    {
        if (null == instance)
        { // Premier appel
            instance = new Singleton();
        }
        return instance;
    }
    //---------------------------------------------
    /** Constructeur redéfini comme étant privé pour interdire
     * son appel et forcer à passer par la méthode <link
     */
    private Singleton()
    {
    }
    //---------------------------------------------
    /** L'instance statique */
    private static Singleton instance;
    //---------------------------------------------
}

Exemple de code pour le détecter :

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;

public class CheckSingleton
{
    boolean debug = false ;
    //---------------------------------------------
    public CheckSingleton( String _className )
    {
        traiter(_className) ;
    }
    //---------------------------------------------
    boolean hasNoPublicConstructor ( Class _c )
    {
        boolean b = false ;
        Constructor[] publicConstructeurs = _c.getConstructors();
        if ( publicConstructeurs.length == 0 )
        {
            b = true ;
            if ( debug )
                System.out.println( "hasNoPublicConstructor" ) ;
        }
        return b ;
    }
    //---------------------------------------------
    boolean hasSomePrivateConstructor ( Class _c )
    {
        boolean b = false ;
        Constructor[] tousConstructeurs = _c.getDeclaredConstructors();
        for ( Constructor unConstructeur : tousConstructeurs )
        {
            String name = unConstructeur.getName();
            int m = unConstructeur.getModifiers() ;
            if ( Modifier.isPrivate(m) )
            {
                b = true ;
                if ( debug )
                    System.out.println( "hasSomePrivateConstructor" ) ;
            }
        }
        return b ;
    }
    //---------------------------------------------
    boolean hasSomePublicStaticDedicatedMethod ( Class _c )
    {
        boolean b = false ;
        Method [] publicMethods = _c.getMethods() ;
        for ( Method uneMethode : publicMethods )
        {
            String type = uneMethode.getReturnType().getName() ;
            int m = uneMethode.getModifiers() ;
            if (
                    ( Modifier.isPublic(m) )
                    && ( Modifier.isStatic(m) )
                    && ( _c.getName().compareTo(type) == 0 )
            )
            {
                b = true ;
                if ( debug )
                    System.out.println( "hasSomeDedicatedMethod" ) ;
            }
        }
        return b ;
    }
    //---------------------------------------------
    boolean hasSomePrivateStaticDedicatedProperty( Class _c )
    {
        boolean b = false ;
        Field [] tousChamps = _c.getDeclaredFields() ;
        for ( Field unAttribut : tousChamps )
        {
            int m = unAttribut.getModifiers() ;
            String type = unAttribut.getType().toString() ;
            type = type.replace("class ", "") ;
            //System.out.println( "type=" + type ) ;
            if (
                    (Modifier.isPrivate(m))
                    && (Modifier.isStatic(m))
                    && ( _c.getName().compareTo(type) == 0 )
            )
            {
                b = true ;
                if ( debug )
                    System.out.println( "hasSomeDedicatedProperty" ) ;
            }
        }
        return b ;
    }
    //---------------------------------------------
    void traiter ( String _className )
    {
        //System.out.println( "..." ) ;
        try
        {
            Class c = Class.forName(_className);

            if     (
                    hasNoPublicConstructor(c)
                    && hasSomePrivateConstructor(c)
                    && hasSomePublicStaticDedicatedMethod(c)
                    && hasSomePrivateStaticDedicatedProperty(c)
            )
            {
                System.out.println( "Singleton" ) ;
                System.out.println( " classe        : " + _className ) ;
            }
        }          
        catch (LinkageError ee)
        {
            System.out.println("pb pour traiter : " + _className );
        }
        catch (ClassNotFoundException e)
        {
            System.out.println("pb pour traiter : " + _className );
        }
    }
    //---------------------------------------------
}

Cas déjà traités

  • Singleton
  • Proxy ( avec variantes )
  • Observateur

Pour approfondir le sujet

vendredi 30 juillet 2010

Rétro-conception UML de classes par introspection java


Objectif

Utiliser les mécanismes d’introspection java pour afficher un diagramme de classes UML.

Introduction

Hier je me suis demandé s’il était faisable de générer un diagramme UML à partir d’une classe de base Java. Après une nuit blanche passée à plancher sur le problème, voici la première version d’un outil développé en 24 heures pouvant se révéler fort pratique et qui va bientôt concurrencer doxygen ou umlGraph ;)

Pseudo cahier des charges

  • Traiter toutes les classes dépendantes d’une classe mère ( ou classe de base )
  • Ne pas introspecter les packages java natifs ( commençant par java, javax et sun )
  • Afficher les superClasses, les interfaces et les classes utilisées par les attributs de classe
  • Afficher un diagramme UML global au format PNG à partir de l’outil graphviz

Améliorations ( version 1 )

  • Traiter toutes les classes d’une arborescence ( package ou racine )
  • Discriminer les interfaces des classes normales
  • Mettre les superclasses au dessus des classes spécialisées

Améliorations ( version 2 )

  • Faire des sous-graphes par packages
  • Gérer en objet les éléments Graphviz
  • Paramètres gérés : afficher les détails, détailler les interfaces, afficher les superclasses

Améliorations possibles

  • Traiter des jar
  • Générer un fichier petal rose
  • Remplir un fichier de propriétés avec les informations ( quasiment déjà intégré depuis la version 0 )

Diagramme UML de l’outil ( version 0 )

Diagramme UML de l’outil ( version 1 )

Diagramme UML utilisé pour les tests

Diagrammes UML utilisés pour les tests ( version 2 )

 

Pour approfondir les sujets

vendredi 28 mai 2010

copie iso-fonctionnelle de données Oracle avec java


Objectif

Copier le schéma Scott d’Oracle sur le Schéma d’un user nouvellement créé. En utilisant les outils de migration standard Oracle et un très simple programme de copie.

Introduction

Copier des bases de données entre différents supports, c’est possible à condition que les types de données soient compatibles.
Si l’on regarde et exécute les outils d’import export d’Oracle ( cf. http://oracle.developpez.com/guide/sauvegarde/generalites/ par exemple ) , nous pouvons faire très simple … ou très compliqué selon le cas.
Il faut prendre en compte pas mal de choses :
  • Les séquences ( pour ceux qui ont envie de gérer les séquences ).
  • concernant Access, il faut différencier les types Integer et Counter ( java.sql.Types.4 ), qui est un Integer auto-incrémenté
  • les types non-compatibles ( voir les autres billets )
  • Les objets non compatibles sql92 et sql99 comme les LOB et tous les autres non connus par Java.
  • etc.
Pour rester simple, voici expliqué un bout de code simple et concis permettant de gérer la recopie du schéma Scott d’Oracle.

Procédure

  • Exporter le schéma Scott
  • Créer le schéma testJdbc
  • Importer le schéma Scott dans testJdbc
  • Vérifier la présence des tables
  • Lancer la classe java de recopie

Exporter le schéma Scott

Taper en ligne de commande sous Windows :
exp scott/tiger file=/schemaScottVide.dmp rows=n

Créer l’utilisateur testJdbc

Taper en ligne de commande sous Windows :
sqlplus system/manager
drop user testjdbc cascade;
create user testjdbc identified by testjdbc;
grant connect , resource  to testjdbc;
grant CREATE SESSION to testjdbc;
conn testjdbc/testjdbc
exit

Importer le schéma Scott dans testJdbc

Taper en ligne de commande sous Windows :
imp testjdbc/testjdbc file=/schemaScottVide.dmp commit=y fromuser=scott touser=testjdbc

Vérifier la présence des tables

Taper en ligne de commande sous Windows :
sqlplus testjdbc/testjdbc
select table_name from cat

Le code permettant la copie

package speh.jdbc.test.exec;

import java.sql.DatabaseMetaData;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Vector;

import speh.jdbc.core.DBUtilities;
import speh.jdbc.core.UneBdD;

public class CopySchemaScottPreparedStatement
{
      //      ===============================================================================    
      DBUtilities dbi = null ;
      DBUtilities dbo = null ;
      Vector types ;
      ResultSet rsIn ;
      PreparedStatement pstmt;
      //      ===============================================================================    
      public CopySchemaScottPreparedStatement( String _in , String _out , boolean _mustDeleteBefore , String _tables[] )
      {
            //
            // connexions schemas in et out
            //
            System.out.println ( "connexion" ) ;
            UneBdD bddIn = new UneBdD()._getByAlias( _in , true ) ;
            dbi = new DBUtilities(bddIn) ;
            UneBdD bddOut = new UneBdD()._getByAlias( _out , true ) ;
            dbo = new DBUtilities(bddOut) ;
            //
            // vider contenus si demande
            // remarque : vider sens inverse insert cause FK
            //
            if ( _mustDeleteBefore )
            {
                  for ( int i = _tables.length -1  ; i > -1  ; i-- )
                  {
                        System.out.println( "delete " + _tables[i] + " ..." ) ;
                        boolean b = dbo._executerDeleteTable( _tables[i] , true ) ;
                  }
            }
            //
            // recuperer structure types jdbc ( java.sql.Types.xxx )
            // copier tuple par tuple, champ par champ
            //
            int nbTables = 0 ;
            for ( int i = 0 ; i < _tables.length ; i++ )
            {
                  System.out.println( "copy " + _tables[i] + " ..." ) ;
                  _structureTable( _tables[i] ) ;
                  if ( types.size() > 0 )
                  {
                        _copyTable( _tables[i] ) ;
                        nbTables++ ;
                  }
            }
            System.out.println( "tables traitées : " + nbTables ) ;
            //
            // deconnexions schemas in et out
            //
            dbi._deConnexion() ;
            dbo._deConnexion() ;
            System.out.println ( "déconnexion" ) ;
      }
      //      ===============================================================================    
      void _structureTable( String _tableName )
      {
            //
            // recuperer structure types jdbc ( java.sql.Types.xxx )
            // teste pas catalogue et schema : peut y avoir ambiguite
            //
            types = new Vector() ;
            try
            {
                  DatabaseMetaData meta;
                  meta = dbi.conn.getMetaData();
                  ResultSet rs = meta.getColumns(null, null, _tableName, null);
                  while ( rs.next() )
                  {
                        int type = rs.getInt("DATA_TYPE") ;
                        types.add(type) ;
                  }
                  rs.close();
            }
            catch (SQLException e)
            {
                  e.printStackTrace();
                  System.err.println( "STOP." ) ;
                  System.exit(0) ;
            }
      }
      //      ===============================================================================    
      void _copyTable( String _tableName )
      {
            //
            // cree un PreparedStatement pour faciliter copy
            // et eviter les transcodages de champs affiches
            //
            String sql1 = "INSERT INTO " ;
            String sql2 = " VALUES( ? " ;
            String sql3 = " )" ;
            String sqlRepet = ", ? " ;
            String sqlInsert = sql1 + _tableName + sql2 ;
            for ( int i = 1 ; i < types.size() ; i++ )
            {
                  sqlInsert = sqlInsert + sqlRepet ;
            }
            sqlInsert = sqlInsert + sql3 ;

            //
            // lecture toute la table en entree
            //
            String selectSql = "select * from " + _tableName ;
            rsIn = dbi._executerSelect(selectSql) ;
            try
            {
                  pstmt = dbo.conn.prepareStatement(sqlInsert);
                  //
                  // recopier tuple par tuple, champ par champ
                  //
                  int nbTuples = 0 ;
                  while (rsIn.next())
                  {
                        for ( int ix = 1 ; ix < types.size() + 1 ; ix++ )
                        {
                              int type = Integer.parseInt(types.elementAt(ix-1).toString()) ;
                              switch (type)
                              {
                              case java.sql.Types.DECIMAL: // 3
                                    pstmt.setBigDecimal( ix , rsIn.getBigDecimal(ix) );
                                    break;
                              case java.sql.Types.VARCHAR: // 12 
                                    pstmt.setString( ix , rsIn.getString(ix) ); 
                                    break;
                              case java.sql.Types.TIMESTAMP: // 93 
                                    pstmt.setTimestamp( ix , rsIn.getTimestamp(ix) ); 
                                    break;
                              default: System.out.println("pas trouvé type # " + type); System.exit(0) ; break;
                              }
                        }
                        pstmt.executeUpdate();
                        nbTuples++ ;
                  }
                  System.out.println("   ... tuples traités : " + nbTuples ) ;
            }
            catch (SQLException e)
            {
                  e.printStackTrace();
                  try
                  {
                        dbo.conn.rollback() ;
                  }
                  catch (SQLException e1)
                  {
                        e1.printStackTrace();
                  }
                  System.out.println("STOP.") ;
                  System.exit(0) ;
            }
      }
      //      ===============================================================================    
      public static void main( String args[] )
      {
            //
            // alias des schemas in et out
            // tableau des tables a traiter dans l'ordre des insert
            // qui respecte les FK pre-existantes
            //
            String in = "jdbcOracleMabase" ;
            String out = "jdbcOracleTestJdbc" ;
            String tables[] = {"ACCOUNT","BONUS","RECEIPT","SALGRADE","DEPT","EMP" } ;

            CopySchemaScottPreparedStatement appli = new CopySchemaScottPreparedStatement(in, out, true , tables) ;
            appli.toString() ;
            System.out.println ( "Traitement terminé." ) ;
      }
      //      ===============================================================================    
}

Quelques commentaires

  • les classes DBUtilities et UneBdD ne sont là que pour gérer l’interface avec les bases de données déclarées dans un fichier properties
  • l’utilisation d’un PreparedStatement est bien pratique parce qu’elle permet d’éviter d’écrire en toutes lettres les ordres INSERT dans les formats propriétaires – notamment aux dates –
  • il faut rapprocher les types java/jdbc gérés des méta-données et les ordres resultSet.getXXX et setXXX correspondant
  • les ordres resultSet.setNull n’ont pas à être gérés en valorisant directement avec pstmt.setString( ix , rsIn.getString(ix) );
  • les tables à copier sont écrites dans l’ordres des contraintes de clés étrangères à vérifier, les plus contraintes à la fin.

Trace de la sortie

connexion
charge les bd recensées dans bin/speh/jdbc/sgbd.properties
  +- charge jdbcMysqlScottTiger
  +- charge jdbcOracleMabase
  +- charge jdbcOracleTestJdbc
  +- charge jdbcSqlServerAdventureWorks
  +- charge odbcAccess2000
  +- charge odbcAccess97
  +- charge odbcAccessDeluxecd
  +- charge odbcDbase
  +- charge odbcDbaseSourcedbase
  +- charge odbcMabase
  +- charge odbcSqlServerScottTiger
delete EMP ...
 rc = 14
delete DEPT ...
 rc = 4
delete SALGRADE ...
 rc = 5
delete RECEIPT ...
 rc = 1
delete BONUS ...
 rc = 0
delete ACCOUNT ...
 rc = 5
copy ACCOUNT ...
   ... tuples traités : 5
copy BONUS ...
   ... tuples traités : 0
copy RECEIPT ...
   ... tuples traités : 1
copy SALGRADE ...
   ... tuples traités : 5
copy DEPT ...
   ... tuples traités : 4
copy EMP ...
   ... tuples traités : 14
tables traitées : 6
déconnexion
Traitement terminé.

Pour approfondir les sujets