• Aucun résultat trouvé

2.5 Discussion sur la génération automatique de tests unitaires à l’aide du CBR

2.6.2 Techniques d’analyse de la similarité

Nous venons de couvrir les différents types de similarité. Jusqu’à maintenant il n’existe pas de technique d’analyse de la similarité pour générer des tests unitaires. Mais il existe plusieurs techniques pour détecter les clones de code. Nous détaillons celles qui sont le plus couram- ment utilisées, afin d’identifier les techniques que nous pourrons réutiliser et adapter à notre contexte.

Même si ces techniques ne permettent pas d’analyser directement la similarité dans notre contexte, notre but n’étant pas de détecter les cas de duplication, nous essayons de trouver un ancien test déjà écrit, afin de l’utiliser pour notre nouveau cas.

La grande différence avec la détection des duplications, c’est qu’il est plus important dans notre cas que le résultat du code soit similaire plutôt que le code lui-même le soit. En effet, deux codes très différents mais produisant le même résultat (comportement) pourront être testés par le même test. Ainsi, il est possible de réécrire complètement et autrement le contenu d’une méthode, sans avoir à changer le test.

Dans les prochaines sections, nous expliquerons ces différences en détail à l’aide de chacune des techniques utilisées pour la détection de clones. Nous pourrons donc mieux comprendre notre contexte mais également identifier des adaptations possibles des techniques pour nos recherches.

Technique basée sur le texte.

Elle prend deux codes sources et compare ses chaînes de caractères une à une. Puis, elle essaie de trouver les séquences qui sont exactement identiques. Normalement, avec cette technique, le prétraitement consiste à enlever les lignes blanches, puis enlever les commentaires.

Roy et al. [RC07] ont résumé des travaux concernant la technique basée sur le texte [Bak95,

DNR06, Joh94,MM01]. Ils ont présenté différentes approches pour détecter les clones, mais toutes les approches utilisent la technique basée sur le texte.

Malheureusement, cette technique est généralement meilleure pour analyser la similarité pour des clones que pour générer des tests unitaires, parce que les tests unitaires ne s’occupent pas de l’intérieur du code. Ainsi, si les deux bouts de code ont le même résultat (comportement), nous les considérerons comme similaires.

Comme l’exemple que nous avons montré au chapitre1, le code source2.1montre deux bouts de codes sources qui produisent exactement le même résultat (un tableau trié), mais de manière différente (algorithmes).

Le code2.2montre un test unitaire écrit initialement pour l’algorithme QuickSort. Cependant, en observant bien ce test, on peut remarquer que le test pour le BubbleSort est identique (sauf pour le nom de la classe) à celui du QuickSort. La réutilisation du test est possible, parce que

Listing 2.1: Comparaison entre le code du tri à bulles et le tri rapide. public c l a s s B u b b l e S o r t { public i n t [ ] s o r t ( i n t a r r [ ] ) { i n t tmp = 0 ; f o r ( i n t i = 0 ; i < a r r . l e n g t h − 1 ; i ++) { f o r ( i n t j = a r r . l e n g t h − 1 ; j > i ; j −−) { i f ( a r r [ j ] < a r r [ j − 1 ] ) { tmp = a r r [ j ] ; a r r [ j ] = a r r [ j − 1 ] ; a r r [ j − 1 ] = tmp ; } } } return a r r ; } } public c l a s s Q u i c k S o r t { private i n t [ ] numbers ; private i n t number ; public i n t [ ] s o r t ( i n t [ ] v a l u e s ) { i f ( v a l u e s == n u l l | | v a l u e s . l e n g t h == 0 ) { return n u l l ; } t h i s . numbers = v a l u e s ; number = v a l u e s . l e n g t h ; q u i c k s o r t ( 0 , number − 1 ) ; return v a l u e s ; }

private void q u i c k s o r t ( i n t low , i n t h i g h ) {

i n t i = low , j = h i g h ;

i n t p i v o t = numbers [ low + ( h i g h − low ) / 2 ] ;

while ( i <= j ) { // . . .

les deux algorithmes ont le même comportement et produisent le même résultat à partir d’une même entrée. En fait, le contenu de la méthode n’est pas important et la façon de trier non plus.

Listing 2.2: Test unitaire pouvant servir à la fois pour le Quicksort et le Bubblesort. public c l a s s S o r t T e s t { @Test public void t e s t S o r t ( ) { B u b b l e S o r t b u b b l e S o r t = new B u b b l e S o r t ( ) ; // −− OU −− ( s e l o n l a c l a s s e à t e s t e r ) Q u i c k S o r t q u i c k s o r t = new Q u i c k s o r t ( ) ; i n t [ ] i n i t i a l N o t S o r t e d = { 5 0 , 6 3 , 25 } ; i n t [ ] e x p e c t e d S o r t e d = { 2 5 , 5 0 , 63 } ; i n t [ ] r e s u l t S o r t e d = q u i c k s o r t . s o r t ( i n i t i a l N o t S o r t e d ) ; a s s e r t A r r a y E q u a l s ( e x p e c t e d S o r t e d , r e s u l t S o r t e d ) ; } }

Technique basée sur l’arbre.

Elle permet de convertir le code source sous la forme d’un arbre de syntaxe abstraite (AST) avec un analyseur. L’AST est un arbre qui présente tous les détails (caractéristiques) d’un code source.

On utilise l’arbre syntaxique (AST) parce que c’est une manière de représenter le code source. Une fois l’AST créé, on cherche les sous-arbres similaires d’un arbre avec différents algorithmes. Enfin, on retourne le sous-arbre le plus similaire.

Par exemple, l’outil Coogle [20112] utilise cette technique. Il utilise d’abord un ASTParser pour convertir du code source sous la forme d’un AST, puis transforme les nœuds de l’AST en éléments FAMIX2. FAMIX est un modèle pour représenter différents langages orientés objet. On peut profiter de la capacité du FAMIX pour analyser et comparer la similarité. Pour comparer cette similarité, il utilise un algorithme de similarité sur l’arbre.

Coogle construit l’arbre à partir d’un modèle FAMIX, puis utilise le patron Visitor [GHJV94] pour visiter les éléments du code source et construire un arbre avec ces informations.

Il utilise ensuite un comparateur pour comparer les nœuds de deux arbres comme le montre la figure 2.9. Cette technique a pour but d’essayer de trouver les sous-arbres similaires. Dans ce contexte, ils ne font qu’essayer de trouver le sous-arbre le plus similaire. Par contre, dans notre cas, il est moins important de trouver le sous-arbre similaire que de trouver la même fonctionnalité entre deux bouts de code. L’exemple2.1montre un cas pour lequel il est possible d’utiliser un même test unitaire pour deux codes différents.

Un autre problème réside dans le fait qu’il considère chaque nœud équivalent mais, dans notre cas, les attributs n’ont pas la même importance.

Figure 2.9: Comparer les nœuds et trouver les sous-arbres similaires[20112].

Figure 2.10: Exemple avec le PDG [aja].

Technique basée sur le PDG.

La technique du PDG (Program Dependency Graph) utilise un graphe de contrôle ou de données. Même si le graphe peut sembler proche d’un arbre, ces deux techniques sont très différentes. L’AST représente un arbre des caractéristiques du code alors que le PDG représente les chemins d’exécution ou le cycle de vie des données.

La figure2.10 montre un exemple de PDG avec les données.

Flot de données. Les flèches vertes représentent le flot de données. Elles montrent tous les flots de données à l’intérieur d’une classe ou d’une méthode.

Comme pour la technique basée sur le texte (voir section2.6.2), il est possible d’avoir plusieurs flots de données produisant le même résultat final. Il serait donc possible de réutiliser le même

test pour deux flots de données différents.

Flot de contrôle. Les flèches bleus représentent les flots de contrôle. Un flot de contrôle représente les appels de méthodes d’un programme les uns à la suite des autres. C’est un peu comme le chemin d’exécution du programme en fonction des méthodes appelées.

Si deux programmes font les mêmes appels de méthodes dans le même ordre, par exemple, alors il est très probable que les deux produiront les mêmes comportements finaux. Cela ne tient pas compte de l’utilisation des données, mais on peut utiliser cette technique pour comparer le comportement en considérant les appels similaires.

Rappelons cependant que notre but est de générer des tests unitaires. Et un test unitaire est un test d’une unité (classe) isolée sans considérer les appels situés plus loin dans le flot. Cette technique est donc meilleure pour un test fonctionnel (plusieurs unités) que pour un test unitaire (on ne regarde qu’une unité à la fois).

Technique basée sur les métriques

Cette technique compare les métriques du code calculées sur les deux codes à analyser. Une métrique donne une façon de mesurer une caractéristique du code. Par exemple, le nombre de lignes de code, la complexité cyclomatique, etc.

Technique de la factorisation. Par exemple, Chilowicz et al. [CDR09] présentent une technique basée sur les métriques pour détecter la similarité du code source en utilisant la factorisation.

On a souvent entendu parler de la factorisation dans le domaine des mathématiques. Pour les mathématiciens, la factorisation consiste à écrire une expression algébrique (notamment une somme), un nombre ou une matrice sous la forme d’un produit [Wik12a]. Dans le domaine de l’informatique, on peut utiliser cette méthode pour détecter la similarité des codes sources à réutiliser.

En programmation, l’algorithme de la factorisation se base sur le tableau de suffixes. Le facteur n’est pas une expression algébrique ni un chiffre mais sont des fonctions et des sous-fonctions. L’algorithme de factorisation permettra d’obtenir les sous-fonctions liées aux fonctions prin- cipales à partir d’un code source. Par exemple : f 2 ⊃ {f 1, f 20, f 3} c’est ce graphe que l’on pourra comparer pour analyser la similarité comme le montre la figure 2.11.

Pour faire cela, on mesure la similarité entre les sous-fonctions de deux projets. Dans cette recherche, Chilowicz et al. ont défini la métrique de la similarité comme étant scsimil(pi, pj) =

I(pi∩pj)

I(p∪pj) où I(pi) représente le nombre d’informations pour le projet pi. Si scsimil(pi, pj) = 1,

ça veut dire que les deux projets pi et pj sont en fait les mêmes.

Mais le problème avec cette technique est que la métrique a besoin d’une inspection manuelle et est donc difficilement automatisable.

Figure 2.11: Exemple de code source d’un graphique d’appels déduit après deux itérations avec la technique de Chilowicz et al [CDR09].

Approche hybride

Il y a beaucoup d’outils qui utilisent cette approche. Elle consiste à combiner plusieurs tech- niques pour résoudre un problème.

Documents relatifs