Faire des tests combinatoires avec les theories JUnit

Il arrive parfois que l'on ait à faire des tests avec des jeux de données combinatoires. Par exemple, on a un jeu de données A : disons {A1,A2}, et un jeu de données B : {B1,B2}, et on voudrait faire un test où toutes les combinaisons A * B soit testées :

  • testMethode(A1,B1)
  • testMethode(A1,B2)
  • testMethode(A2,B1)
  • testMethode(A2,B2)

Il est possible dans ce cas d'utiliser les theories. Il s'agit d'une technique, pour l'instant expérimentale (cf : nom du package), disponible à partir de JUnit 4.4.

Commençons pas un exemple simple où il nous faut tester une méthode avec un jeu de données mais sans la combinatoire :

  • testMethode(A1)
  • testMethode(A2)
package com.gisgraphy.samples;

import static org.junit.Assume.assumeTrue;
import junit.framework.TestCase;

import org.junit.experimental.theories.DataPoint;
import org.junit.experimental.theories.Theories;
import org.junit.experimental.theories.Theory;
import org.junit.runner.RunWith;

@RunWith(Theories.class)
public class SqrtTest extends TestCase {
    public static int count = 0;


//La classe a tester    
public static class MySqrt {
        public static Double GetSqrt(Double param) {
            if (param < 0){
                throw new IllegalArgumentException("Param should not be negative");
            }
            return Math.sqrt(param);
        }
    }

    @DataPoint
    public static final Double value1 = 25D;

    @DataPoint
    public static final Double value2 = 16D;

    @DataPoint
    public static final Double value3 = -1D;

    @Theory
    public void testSqrtShouldReturnTheCorrectValue(Double value) {
        assumeTrue(value.doubleValue() > 0);
        assertEquals(Math.sqrt(value), MySqrt.GetSqrt(value));
    }

    
    @Theory
    public void testSqrtShouldThrowForNegativeParam(Double value) {
        try {
            assumeTrue(value.doubleValue() < 0);
            Double result = MySqrt.GetSqrt(value);
            fail("negative parameter Should Throw");
        } catch (IllegalArgumentException e) {
            //OK
        }
    }

}

Tout d'abord, il faut annoter la classe de test avec @RunWith(Theories.class) pour lui spécifier le type de test.

L'annotation @DataPoint permet de définir le jeu de données qui sera appliqué aux méthodes annotées par @Theory.

On remarquera que la syntaxe 'public void testXXX(){...} qui, normalement, ne prend pas de paramètres, se retrouve avec un paramètre qui est du même type que les champs annotés par @DataPoint

Dans l'exemple ci-dessus, les méthodes testSqrtShouldThrowForNegativeParam() et testSqrtShouldReturnTheCorrectValue() se verront appliquer successivement : value1,value2, et value3.

Afin de ne pas exécuter les tests avec toutes les valeurs pour toutes les méthodes, il est utile d'utiliser assumeTrue (en import static) pour spécifier une condition afin que le test soit exécuté ou pas. Dans notre exemple testSqrtShouldReturnTheCorrectValue() ne prend que les valeurs positives tandis que 'testSqrtShouldThrowForNegativeParam' ne prend que les valeurs négatives. Toutes les valeurs seront appliquées mais le test les utilisera ou pas. Ceci est donné à titre d'exemple mais ne fait pas partie des théories en soit.

Une utilisation classique des théories est de faire un jeu de données avec des valeurs aux limites, null, des valeurs correctes, et des valeurs incorrectes afin de rendre un code plus robuste et éviter les trous dans la raquette. Il ne faut pas les utiliser à tort et à travers car les assertions peuvent vite devenir compliquées :

assert que Truc si valeur = 1 sinon assert machin

Si vous codez ce genre de choses dans un test : c'est un 'smell' que les théories ne sont pas adéquates dans votre cas.

Le prochain post sera sur l'aspect combinatoire des théories. A demain ! :)

La discussion continue ailleurs

URL de rétrolien : https://davidmasclet.gisgraphy.com/index.php?trackback/42

Fil des commentaires de ce billet