junit threads

Cuando definimos un test con Junit y queremos repetirlo la forma mas sencilla es crear un metodo que con un for llame al metodo donde tenemos la logica las veces que queramos. Por ejemplo podemos hacer el siguiente codigo:

public class MiTest {

    @Test
    public void iteraTest() {
        for (int i = 0; i<10; i++) {
            test();
        }
    }
   
    private void test() {
        System.out.println("Probando el test");
    }
}

Este tipo de prueba esta bien si queremos probar solamente un test multiples veces, pero si lo que queremos es probar un test concurrentemente asi no nos vale ya que hasta que no termine la llamada al metodo test no volvera a hacer otra llamada al mismo

La solucion es crear nuestro propio Runner de Junit y una anotacion para aquellos metodos que queramos hacerlos concurrentes indicar el numero de hilos que queremos usar

Veamos primero la anotacion

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD })
public @interface Threads {
    int value();
}

Lo unico que definimos es el atributo value como un int donde le indicaremos cuantos hilos queremos ejecutar. Ahora definiremos el Runner, mas concretamente un Runner que extienda de BlockJUnit4ClassRunner

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

import org.junit.Ignore;
import org.junit.runner.Description;
import org.junit.runner.notification.RunNotifier;
import org.junit.runners.BlockJUnit4ClassRunner;
import org.junit.runners.model.FrameworkMethod;
import org.junit.runners.model.InitializationError;
import org.junit.runners.model.Statement;

public class ThreadRunner extends BlockJUnit4ClassRunner {

    public ThreadRunner(Class<?> klass) throws InitializationError {
        super(klass);
    }

    @Override
    protected Description describeChild(FrameworkMethod method) {
        if (method.getAnnotation(Threads.class) != null && method.getAnnotation(Ignore.class) == null) {
            return describeThreadTest(method);
        }
        return super.describeChild(method);
    }

    private Description describeThreadTest(FrameworkMethod method) {
        int times = method.getAnnotation(Threads.class).value();

        Description description = Description.createSuiteDescription(testName(method) + " [" + times + " threads]", method.getAnnotations());

        for (int i = 1; i <= times; i++) {
            description.addChild(Description.createTestDescription(getTestClass().getJavaClass(), "[" + i + "] " + testName(method)));
        }
        return description;
    }

    @Override
    protected void runChild(final FrameworkMethod method, RunNotifier notifier) {
        Description description = describeChild(method);

        if (method.getAnnotation(Threads.class) != null && method.getAnnotation(Ignore.class) == null) {
            runThreadly(methodBlock(method), description, notifier);
        }
        else if (method.getAnnotation(Ignore.class) != null) {
            notifier.fireTestIgnored(description);
        }
        else {
            runLeaf(methodBlock(method), description, notifier);
        }
    }

    private void runThreadly(final Statement statement, Description description, final RunNotifier notifier) {
        ExecutorService es = Executors.newCachedThreadPool();
        
        for (final Description desc : description.getChildren()) {
            es.execute(new Runnable() {
                
                @Override
                public void run() {
                    runLeaf(statement, desc, notifier);
                }
            });
        }
        
        es.shutdown();
        try {
            es.awaitTermination(30, TimeUnit.MINUTES);
        }
        catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
}

En la linea 54 creamos el ExecutorService que usaremos para lanzar nuestros hilos
En la linea 61 es donde ejecutamos cada uno de los hilos con el metodo runLeaf
En la linea 68 indicamos a nuestro ExecutorService que queremos esperar (awaitTermination) a que los tests se completen pero un maximo de 30 minutos

Nuestra clase de test quedaria de la siguiente manera

@RunWith(ThreadRunner.class)
public class MiTest {

    @Test
    @Threads(10)
    public void test() {
        System.out.println("Probando el test");
    }
}

Hemos añadido en la linea 1 la anotacion RunWith que le indica a Junit con que Runner ha de ejecutar el test
El metodo que teniamos donde iterabamos lo hemos cambiado por la implementacion del metodo que tenia nuestra logica y en la linea 5 hemos añadido nuestra anotacion con el numero de hilos que queremos que se ejecuten

Este es un ejemplo sencillo pero aqui hay una base para que podais hacer las pruebas de metodos concurrentes

Comparte este articuloShare on FacebookShare on Google+Tweet about this on TwitterShare on LinkedIn

Comentarios

comentarios