Diferencia entre revisiones de «Tests De JUnit Multihilo»

De Dos Ideas.
Saltar a: navegación, buscar
(La clase a ejecutar multihilo)
(Un proyecto de ejemplo)
 
(No se muestran 14 ediciones intermedias de 2 usuarios)
Línea 12: Línea 12:
 
=== La clase a ejecutar multihilo ===
 
=== La clase a ejecutar multihilo ===
  
SleepManagerStatelessBoImpl contiene un método para ejecutar un sleep durante una cantidad de milisegundos generada aleatoriamente en base a los valores minimo y maximo recibidos por parámetro. Si los milisegundos generados son mayores a 10000 lanza una exception.
+
SleepManagerStatelessBoImpl contiene un método para ejecutar un sleep durante una cantidad de milisegundos generada aleatoriamente en base a los valores mínimo y máximo recibidos por parámetro. Si los milisegundos generados son mayores a 10000 lanza una exception.
  
 
<code java>
 
<code java>
Línea 34: Línea 34:
 
</code>
 
</code>
  
y a continuacion su configuración de spring en el archivo groboutils-business.xml:
+
Configuración de spring en el archivo groboutils-business.xml:
  
 
<code java>
 
<code java>
Línea 40: Línea 40:
 
   class="com.dosideas.groboutils.demo.business.impl.SleepManagerStatelessBoImpl"/>
 
   class="com.dosideas.groboutils.demo.business.impl.SleepManagerStatelessBoImpl"/>
 
</code>
 
</code>
 +
 +
=== Test multihilo - Escenario: Todos los hilos ejecutan ok ===
 +
 +
Creamos una inner class que extiende de TestRunnable, definiendo una implementación para el método abstracto runTest().
 +
Luego creamos la cantidad de instancias de esa inner class de acuerdo a la cantidad de ejecuciones concurrentes que queramos y las ejecutamos con el runner MultiThreadedTestRunner de GrooboUtils.
 +
 +
 +
<code java>
 +
// Levantamos la configuración de spring.
 +
@ContextConfiguration(locations = {"classpath:groboutils-business.xml"})
 +
@RunWith(SpringJUnit4ClassRunner.class)
 +
public class SleepManagerStatelessMultithreadTest {
 +
    @Qualifier("business.SleepManagerStatelessBo")
 +
    @Autowired
 +
    private SleepManagerBo sleepManagerBo;
 +
 +
    // Creamos una clase que al extender de TestRunnable, nos hace implementar
 +
    // el método runTest(). En el mismo invocamos nuestro método de negocio.
 +
    public class HiloManager extends TestRunnable {
 +
        private Integer minimo;
 +
        private Integer maximo;
 +
        private Integer resultado;
 +
 +
        public HiloManager(Integer minimo, Integer maximo) {
 +
            this.minimo = minimo;
 +
            this.maximo = maximo;
 +
        }
 +
 +
        @Override
 +
        public void runTest() throws Throwable {
 +
            try {
 +
                resultado = sleepManagerBo.ejecutarSleep(maximo, minimo);
 +
            } catch (InterruptedException e) {
 +
                e.printStackTrace();
 +
            }
 +
        }
 +
 +
        public Integer getResultado() {
 +
            return resultado;
 +
        }
 +
    }
 +
 +
    @Test
 +
    public void sleepMultihilo_escenarioOk_verificamosGeneracionDeNumerosDistintos() throws Throwable {
 +
        System.out.println("Inicia ejecucion multihilo");
 +
 +
        // Instanciamos hilos para que se generen valores que no superen los
 +
        // 10000 millisegundos
 +
        HiloManager hilo1 = new HiloManager(1000, 2000);
 +
        HiloManager hilo2 = new HiloManager(5000, 8000);
 +
        HiloManager hilo3 = new HiloManager(6000, 7000);
 +
 +
        // Pasamos el array de hilos a ejecutar al runner de grobbo que soporta
 +
        // ejecucion multihilo.
 +
        TestRunnable[] arrayDeHilos = {hilo1, hilo2, hilo3};
 +
        MultiThreadedTestRunner nultithreadTestRunner = new MultiThreadedTestRunner(arrayDeHilos);
 +
        nultithreadTestRunner.runTestRunnables();
 +
 +
        // Verificamos que se hayan generados 3 numeros aleatorios diferentes.
 +
        assertNotSame(hilo1.getResultado(), hilo2.getResultado());
 +
        assertNotSame(hilo1.getResultado(), hilo3.getResultado());
 +
        assertNotSame(hilo2.getResultado(), hilo3.getResultado());
 +
 +
        System.out.println("Fin ejecucion multihilo");
 +
    }
 +
 +
}
 +
</code>
 +
 +
 +
Una posible salida por consola sería:
 +
 +
<code>
 +
Inicia ejecucion multihilo
 +
Thread: 11 - Inicio
 +
Thread: 13 - Inicio
 +
Thread: 11 - Sleep: 1621
 +
Thread: 13 - Sleep: 6329
 +
Thread: 12 - Inicio
 +
Thread: 12 - Sleep: 5605
 +
Thread: 11 - Fin
 +
Thread: 12 - Fin
 +
Thread: 13 - Fin
 +
Fin ejecucion multihilo
 +
</code>
 +
 +
=== Test multihilo - Escenario: Un hilo falla en ejecución ===
 +
 +
En este caso agregamos al test un escenario en el cual uno de los hilos generará un número aleatorio mayor al permitido. Verificamos que el test lanza la exception esperada.
 +
 +
<code java>
 +
@Test(expected = IllegalArgumentException.class)
 +
public void sleepMultihilo_unHiloLanzaException_seVerificaCortaEjecucion() throws Throwable {
 +
        System.out.println("Inicia ejecucion multihilo");
 +
 +
        // Instanciamos uno de los hilos para que se generen valores que superen
 +
        // los 10000 millisegundos.
 +
        TestRunnable hilo1 = new HiloManager(1000, 2000);
 +
        TestRunnable hilo2 = new HiloManager(11000, 15000);
 +
        TestRunnable hilo3 = new HiloManager(3000, 4000);
 +
 +
        // Pasamos el array de hilos a ejecutar al runner de grobbo que soporta
 +
        // ejecucion multihilo.
 +
        TestRunnable[] arrayDeHilos = {hilo1, hilo2, hilo3};
 +
        MultiThreadedTestRunner nultithreadTestRunner = new MultiThreadedTestRunner(arrayDeHilos);
 +
        nultithreadTestRunner.runTestRunnables();
 +
 +
        System.out.println("Fin ejecucion multihilo");
 +
    }
 +
</code>
 +
 +
Y la salida por consola:
 +
 +
<code>
 +
Inicia ejecucion multihilo
 +
Thread: 11 - Inicio
 +
Thread: 12 - Inicio
 +
Thread: 11 - Sleep: 1587
 +
Thread: 13 - Inicio
 +
Thread: 13 - Sleep: 3821
 +
java.lang.InterruptedException: sleep interrupted
 +
WARN [Thread-3] (MultiThreadedTestRunner.java:276) - A test thread caused an exception.
 +
java.lang.IllegalArgumentException: Numero mayor a 10000 milisegundos
 +
at com.dosideas.groboutils.demo.business.impl.SleepManagerStatelessBoImpl.ejecutarSleep(SleepManagerStatelessBoImpl.java:20)
 +
at com.dosideas.groboutils.demo.SleepManagerStatelessMultithreadTest$HiloManager.runTest(SleepManagerStatelessMultithreadTest.java:47)
 +
        ...
 +
</code>
 +
 +
== Un proyecto de ejemplo ==
 +
Puede descargarse el [http://www.dosideas.com/descargas/category/3-testing.html?download=39 proyecto] donde además de encontrar el ejemplo de ejecución multihilo de beans sin estado comentado en esta wiki podrán encontrar:
 +
* Test de ejecución multihilo de beans con estado, comun a todos los hilos.
 +
* Test de ejecución multihilo de beans con estado que es propio del hilo.
  
 
== Ver también ==
 
== Ver también ==

Revisión actual del 13:10 12 jul 2010

La situación

Muchas veces desarrollamos clases que se ejecutarán en hilos de manera concurrente. Dicha concurrencia suele ser propensa a errores que son difíciles de detectar y corregir por su naturaleza aleatoria. Por eso es una buena práctica sumar a nuestra batería de tests algunos escenarios que verifiquen el comportamiento del sistema cuando se ejecuta multihilo.

El problema

Si bien podríamos hacer un test de JUnit que cree varios hilos de ejecución, el runner (al menos hasta la version de Junit 4.5) no lo soporta: se crean los hilos y el test termina hayan o no terminado de ejecutar todos los hilos creados. Adicionalmente en el test que creó los hilos desconocemos el resultado de cada uno de estos hilos hijo, por lo que no podemos realizar asserts.

Una solución: GroboUtils

GroboUtils es un conjunto de sub-proyectos que se basan en JUnit para extender sus capacidades. En particular, veremos un ejemplo del subproyecto que se encarga de los tests multihilo.

La clase a ejecutar multihilo

SleepManagerStatelessBoImpl contiene un método para ejecutar un sleep durante una cantidad de milisegundos generada aleatoriamente en base a los valores mínimo y máximo recibidos por parámetro. Si los milisegundos generados son mayores a 10000 lanza una exception.

public class SleepManagerStatelessBoImpl implements SleepManagerBo {

   public Integer ejecutarSleep(Integer maximo, Integer minimo) throws InterruptedException {
       System.out.println("Thread: " + Thread.currentThread().getId() + " - Inicio");
       Integer numeroAleatorio = (int) (Math.random() * (maximo - minimo)) + minimo;
       if (numeroAleatorio > 10000) {
           throw new IllegalArgumentException("Numero mayor a 10000 milisegundos");
       }
       System.out.println("Thread: " + Thread.currentThread().getId() + " - Sleep: " + numeroAleatorio);
       Thread.sleep(numeroAleatorio);
       System.out.println("Thread: " + Thread.currentThread().getId() + " - Fin");
       return numeroAleatorio;
   }

}

Configuración de spring en el archivo groboutils-business.xml:

<bean id="business.SleepManagerStatelessBo"

 class="com.dosideas.groboutils.demo.business.impl.SleepManagerStatelessBoImpl"/>

Test multihilo - Escenario: Todos los hilos ejecutan ok

Creamos una inner class que extiende de TestRunnable, definiendo una implementación para el método abstracto runTest(). Luego creamos la cantidad de instancias de esa inner class de acuerdo a la cantidad de ejecuciones concurrentes que queramos y las ejecutamos con el runner MultiThreadedTestRunner de GrooboUtils.


// Levantamos la configuración de spring. @ContextConfiguration(locations = {"classpath:groboutils-business.xml"}) @RunWith(SpringJUnit4ClassRunner.class) public class SleepManagerStatelessMultithreadTest {

   @Qualifier("business.SleepManagerStatelessBo")
   @Autowired
   private SleepManagerBo sleepManagerBo;
   // Creamos una clase que al extender de TestRunnable, nos hace implementar
   // el método runTest(). En el mismo invocamos nuestro método de negocio.
   public class HiloManager extends TestRunnable {
       private Integer minimo;
       private Integer maximo;
       private Integer resultado;
       public HiloManager(Integer minimo, Integer maximo) {
           this.minimo = minimo;
           this.maximo = maximo;
       }
       @Override
       public void runTest() throws Throwable {
           try {
               resultado = sleepManagerBo.ejecutarSleep(maximo, minimo);
           } catch (InterruptedException e) {
               e.printStackTrace();
           }
       }
       public Integer getResultado() {
           return resultado;
       }
   }
   @Test
   public void sleepMultihilo_escenarioOk_verificamosGeneracionDeNumerosDistintos() throws Throwable {
       System.out.println("Inicia ejecucion multihilo");
       // Instanciamos hilos para que se generen valores que no superen los
       // 10000 millisegundos
       HiloManager hilo1 = new HiloManager(1000, 2000);
       HiloManager hilo2 = new HiloManager(5000, 8000);
       HiloManager hilo3 = new HiloManager(6000, 7000);
       // Pasamos el array de hilos a ejecutar al runner de grobbo que soporta
       // ejecucion multihilo.
       TestRunnable[] arrayDeHilos = {hilo1, hilo2, hilo3};
       MultiThreadedTestRunner nultithreadTestRunner = new MultiThreadedTestRunner(arrayDeHilos);
       nultithreadTestRunner.runTestRunnables();
       // Verificamos que se hayan generados 3 numeros aleatorios diferentes.
       assertNotSame(hilo1.getResultado(), hilo2.getResultado());
       assertNotSame(hilo1.getResultado(), hilo3.getResultado());
       assertNotSame(hilo2.getResultado(), hilo3.getResultado());
       System.out.println("Fin ejecucion multihilo");
   }

}


Una posible salida por consola sería:

Inicia ejecucion multihilo Thread: 11 - Inicio Thread: 13 - Inicio Thread: 11 - Sleep: 1621 Thread: 13 - Sleep: 6329 Thread: 12 - Inicio Thread: 12 - Sleep: 5605 Thread: 11 - Fin Thread: 12 - Fin Thread: 13 - Fin Fin ejecucion multihilo

Test multihilo - Escenario: Un hilo falla en ejecución

En este caso agregamos al test un escenario en el cual uno de los hilos generará un número aleatorio mayor al permitido. Verificamos que el test lanza la exception esperada.

@Test(expected = IllegalArgumentException.class) public void sleepMultihilo_unHiloLanzaException_seVerificaCortaEjecucion() throws Throwable {

       System.out.println("Inicia ejecucion multihilo");
       // Instanciamos uno de los hilos para que se generen valores que superen
       // los 10000 millisegundos.
       TestRunnable hilo1 = new HiloManager(1000, 2000);
       TestRunnable hilo2 = new HiloManager(11000, 15000);
       TestRunnable hilo3 = new HiloManager(3000, 4000);
       // Pasamos el array de hilos a ejecutar al runner de grobbo que soporta
       // ejecucion multihilo.
       TestRunnable[] arrayDeHilos = {hilo1, hilo2, hilo3};
       MultiThreadedTestRunner nultithreadTestRunner = new MultiThreadedTestRunner(arrayDeHilos);
       nultithreadTestRunner.runTestRunnables();
       System.out.println("Fin ejecucion multihilo");
   }

Y la salida por consola:

Inicia ejecucion multihilo Thread: 11 - Inicio Thread: 12 - Inicio Thread: 11 - Sleep: 1587 Thread: 13 - Inicio Thread: 13 - Sleep: 3821 java.lang.InterruptedException: sleep interrupted

WARN [Thread-3] (MultiThreadedTestRunner.java:276) - A test thread caused an exception.

java.lang.IllegalArgumentException: Numero mayor a 10000 milisegundos at com.dosideas.groboutils.demo.business.impl.SleepManagerStatelessBoImpl.ejecutarSleep(SleepManagerStatelessBoImpl.java:20) at com.dosideas.groboutils.demo.SleepManagerStatelessMultithreadTest$HiloManager.runTest(SleepManagerStatelessMultithreadTest.java:47)

       ...

Un proyecto de ejemplo

Puede descargarse el proyecto donde además de encontrar el ejemplo de ejecución multihilo de beans sin estado comentado en esta wiki podrán encontrar:

  • Test de ejecución multihilo de beans con estado, comun a todos los hilos.
  • Test de ejecución multihilo de beans con estado que es propio del hilo.

Ver también