Choses à faire

24h dans une journée, et tant de choses à faire !

Mocks et Multithreading

publié le 22 janvier 2010 par Killian Ebel

Afin de compléter la série d’articles sur les tests logiciels, voici une méthode permettant de tester efficacement une application « concurrente ».

Java et Multithreading

Premièrement, il est important de comprendre le fonctionnement de l’interface Callable en Java ; une interface similaire à Runnable, à la différence près qu’un thread Callable renvoie une valeur lors de son exécution.

Ensuite, la classe utilitaire Executors, utile pour créer des pool de Threads, des pool de Threads périodique ou encore transformer un Runnable en Callable.

Enfin, l’interface Future, qui permet de stocker le futur résultat d’un appel asynchrone. Ce résultat peut être obtenu en appelant la méthode get() de votre objet, appel qui peut être bloquant si le résultat n’a pas encore été reçu. Il est possible de spécifier un timeout qui lèvera une exception si la durée de l’appel est supérieur.

Dans notre exemple, nous utiliserons un pool de taille fixe (nombre maximal de Threads défini), qui instanciera des Future stockant le résultat d’un calcul.

Mocking et Multithreading

C’est EasyMock qui sera utilisé, de par sa capacité à rendre l’exécution d’un Mock Threadsafe. Mockito permet aussi de créer des Mocks Threadsafe ; voici une brève comparaison entre EasyMock et Mockito, mais qui donne clairement l’avantage à Mockito du point de vue syntaxique !

Mise en pratique

Premièrement, la classe principale :

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

public class CalculMultiThread {
    private static final int THREAD_TIME_OUT = 10;
    private static final int POOL_SIZE = 5;

    private ExecutorService pool = null;

    public CalculMultiThread() {
        pool = Executors.newFixedThreadPool(POOL_SIZE);
    }

    public void setPool(ExecutorService pool) {
        this.pool = pool;
    }

    public void calculsParalleles(Integer op1, Integer op2, Integer op3, Integer op4) {
        final AdditionCallable additionCallable = new AdditionCallable();
        additionCallable.setOperande1(op1);
        additionCallable.setOperande2(op2);
        
        final SoustractionCallable soustractionCallable = new SoustractionCallable();
        soustractionCallable.setOperande1(op3);
        soustractionCallable.setOperande2(op4);
    
        final Future<Integer> additionFuture = pool.submit(additionCallable);
        final Future<Integer> soustractionFuture = pool.submit(soustractionCallable);

        try {
            final Integer resultatAddition = additionFuture.get(THREAD_TIME_OUT, TimeUnit.SECONDS);
            final Integer resultatSoustraction = soustractionFuture.get(THREAD_TIME_OUT, TimeUnit.SECONDS);

            System.out.println(op1 + " + " + op2 + " = " + resultatAddition);
            System.out.println(op3 + " + " + op4 + " = "  + resultatSoustraction);

        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        } catch (TimeoutException e) {
            e.printStackTrace();
        } finally {
            pool.shutdown();
        }
    }

    public static void main(String[] args) {
        CalculMultiThread calculMultiThread = new CalculMultiThread();
        calculMultiThread.calculsParalleles(5, 6, 8, 3);
    }
}

abstract class CalculCallable implements Callable<Integer> {
    private Integer operande1 = null;
    private Integer operande2 = null;
    
    public void setOperande1(Integer operande1) {
        this.operande1 = operande1;
    }
    
    public Integer getOperande1() {
        return this.operande1;
    }
    
    public void setOperande2(Integer operande2) {
        this.operande2 = operande2;
    }

    public Integer getOperande2() {
        return this.operande2;
    }
}

class AdditionCallable extends CalculCallable {
	public Integer call() throws Exception {
		return getOperande1() + getOperande2();
	}
}

class SoustractionCallable extends CalculCallable {
	public Integer call() throws Exception {
		return getOperande1() - getOperande2();
	}
}

Le résultat de l’opération :

5 + 6 = 11
8 - 3 = 5

Enfin, les tests :

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import org.easymock.EasyMock;
import org.junit.Before;
import org.junit.Test;

public class CalculMultiThreadTest {
    private ExecutorService executorServiceMock;
    private CalculMultiThread calculMultiThread;

    @Before
    public void setUp() throws Exception {
        executorServiceMock = EasyMock.createMock(ExecutorService.class);
        calculMultiThread = new CalculMultiThread();
        EasyMock.makeThreadSafe(executorServiceMock, true);
    }

    @SuppressWarnings("unchecked")
    @Test
    public void testCallTasksInParallel() throws Exception {
        calculMultiThread.setPool(executorServiceMock);

        Future<Integer> additionFutureMock = (Future<Integer>) EasyMock.createMock(Future.class);
        EasyMock.expect(additionFutureMock.get(EasyMock.anyLong(), EasyMock.isA(TimeUnit.class))).andReturn(11);
        EasyMock.replay(additionFutureMock);

        Future<Integer> soustractionFutureMock = (Future<Integer>) EasyMock.createMock(Future.class);
        EasyMock.expect(soustractionFutureMock.get(EasyMock.anyLong(), EasyMock.isA(TimeUnit.class))).andReturn(5);
        EasyMock.replay(soustractionFutureMock);

        EasyMock.expect(executorServiceMock.submit(EasyMock.isA(AdditionCallable.class))).andReturn(additionFutureMock);
        EasyMock.expect(executorServiceMock.submit(EasyMock.isA(SoustractionCallable.class))).andReturn(soustractionFutureMock);
        EasyMock.replay(executorServiceMock);

        calculMultiThread.calculsParalleles(5, 6, 8, 3);

        EasyMock.verify(additionFutureMock);
        EasyMock.verify(soustractionFutureMock);
        EasyMock.verify(executorServiceMock);
    }
}

Voilà, ce n’est pas plus compliqué de tester du code multithreadé. Pour plus d’informations, vous pouvez toujours consulter les liens qui suivent, merci !

Les textes, illustrations et démonstrations présents sur ce site sont la propriété de leurs auteurs respectifs, sauf mention contraire (photo de la bannière).
Chosesafaire.fr, un site propulsé par Wordpress, vous est proposé par Pierre Quillery & Killian Ebel.

Valid XHTML 1.0 Strict