JUnit (3.8 y 4) y como ejecutar en un orden determinado los métodos de test de una clase de test

0
17810

Creación: 08-02-2008

Índice de contenidos

1.Introducción
2.Entorno
3. En JUnit 3.8
4.En JUnit 4
5.Conclusiones
6.Sobre el autor

1. Introducción

Antes de que la gente se eche las manos a la cabeza diciendo que querer ejecutar test unitarios en un orden determinado es un barbaridad, ya lo digo yo. Efectivamente y sí, para ejecutar los test unitarios NO debería importar su orden. Por eso se llaman unitarios, porque prueban una única unidad (una clase).

En «The Way of Testivus» (http://www.artima.com/weblogs/viewpost.jsp?thread=203994)
podéis encontrar unos buenos consejos y prácticas sobre «que es un test unitario». En este artículo de Alberto Savoia podemos encontrar cosas como:

  • Si habla con la base de datos, no es un test unitario
  • Si se comunica por la red, no es un test unitario
  • Si toca el sistema de ficheros, no es un test unitario
  • Si no se pueden ejecutar al mismo tiempo que cualquier otro test unitario, no es un test unitario
  • Si tienes que hacer cosas especiales en tu entorno para poder ejecutarlo, no es un test unitario

Ahora bien, no todos son test unitarios, también hay, por ejemplo, test de integración, y en este caso si que puede ser muy interesante el lanzar los test en un orden determinado.

La «única» (recordar cuando habla de dogma y karma en el artículo) excusa para lanzar test unitario en un orden determinado es cuando la probabilidad de fallo de algunos test es mucho mayor que la de otros. En esta situación nos puede interesar lanzar primero lo que tienen más probabilidad de fallo para no «perder» tiempo en detectar los errores.

Y ahora después de esta breve introducción sobre test unitarios, vamos a lío. En este tutorial vamos a ver como podemos conseguir que se ejecuten los métodos de un test de JUnit por orden alfabético (tanto para la versión 3.9 como la versión 4 con anotaciones).

2. Entorno

El tutorial está escrito usando el siguiente entorno:

  • Hardware: Portátil Asus G1 (Core 2 Duo a 2.1 GHz, 2048 MB RAM, 120 GB HD).
  • Sistema Operativo: GNU / Linux, Debian (unstable), Kernel 2.6.23, KDE 3.5
  • JDK 1.5.0_14 (instalada con el paquete sun-java5-jdk de Debian)
  • Eclipse Europa 3.3, con soporte para desarrollo JEE (WTP 2.0)
  • Maven 2.0.8
  • JUnit 4.4


3. En JUnit 3.8

En JUnit 3.8, el aspecto típico de un test podría ser el siguiente:

public class AutentiaJUnit38Test extends TestCase {

        public static Test suite() {
            return new TestSuite(AutentiaJUnit38Test.class);
        }

        public AutentiaJUnit38Test(String name) {
            super(name);
        }

        public void testAddElementInDDBB() throws Exception {
            Assert.assertTrue("Añade elementos a la DDBB", true);
        }

        public void testSearchElementInDDBB() throws Exception {
            Assert.assertTrue("Busca en la DDBB elementos que se han añdido en el test anterior", true);
        }
    }

Nada nos garantiza que los métodos se ejecuten en el orden en el que están escritos o por orden alfabético. Esto es porque la clase TestSuite internamente lo que hace es usar el método getMethods de la clase Class. Si vemos la documentación de este método encontraremos lo siguiente «The elements in the array returned are not sorted and are not in any particular order«. Es decir, el orden va a depender de la implementación de la JVM que estamos usando.

Podríamos solventar esto creando el TestSuite método a método, en vez de usar reflexión. Pero esto tiene el inconveniente de que si borramos o añadimos métodos o cambiamos los nombres, tendremos que mantener la lista a mano.

Vamos a ver una posible solución:

public class AlphabeticMethodSorterTestSuite extends TestSuite {

        public AlphabeticMethodSorterTestSuite(Class<?> clazz) {
            final Method[] methods = clazz.getMethods();
            final List names = new ArrayList();
            for (Method method : methods) {
                final String methodName = method.getName();
                if (methodName.startsWith("test")) {
                    names.add(methodName);
                }
            }

            Collections.sort(names);

            try {
                final Constructor<?> constructor = clazz.getConstructor(new Class<?>[] { String.class });
                final Object[] initargs = new Object[1];
                for (String name : names) {
                    initargs[0] = name;
                    final Test test = (Test)constructor.newInstance(initargs);
                    addTest(test);
                }
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    }

Lo que hemos hecho aquí es extender la clase TestSuite de forma que ahora el constructor sí garantiza que se ordenan los método por orden alfabético. De esta manera nuestro test podría quedar de la siguiente manera:

public class AutentiaJUnit38Test extends TestCase {

        public static Test suite() {
            return new AlphabeticMethodSorterTestSuite(AutentiaJUnit38Test.class);
        }

        public AutentiaJUnit38Test(String name) {
            super(name);
        }

        public void test10AddElementInDDBB() throws Exception {
            Assert.assertTrue("Añade elementos a la DDBB", true);
        }

        public void test20SearchElementInDDBB() throws Exception {
            Assert.assertTrue("Busca en la DDBB elementos que se han añadido en el test anterior", true);
        }
    }

Como veis nuestro test no ha cambiado prácticamente nada, simplemente hemos modificado la creación del TestSuite usando nuestra clase, y hemos añadido 10, 20, … al nombre del test para garantizar que es ejecute en orden alfabético.

4. En JUnit 4

El mismo test en JUnit 4 tendría el siguiente aspecto (como se puede ver, gracias a las anotaciones el código queda más sencillo y no necesitamos extender de ninguna clase):

public class AutentiaJUnit4Test {

        @Test
        public void testAddElementInDDBB() throws Exception {
            Assert.assertTrue("Añade elementos a la DDBB", true);
        }

        @Test
        public void testSearchElementInDDBB() throws Exception {
            Assert.assertTrue("Busca en la DDBB elementos que se han añadido en el test anterior", true);
        }
    }

En JUnit 4 ya se ha cubierto la deficiencia que existía en la versión 3.8 y han dado soporte para la ordenación o filtrado de los test. Aunque hay que reconocer que la documentación al respecto no es demasiado buena.

En JUnit 4 aparece la clase Runner que es la que se encarga de ejecutar los test; si queremos ejecutar un test con un Runner concreto podemos usar la anotación @RunWith. También tenemos la interfaz Sortable; esta interfaz indica si un Runner permite ordenación (en función de un Comparator de Java) de los test que tiene que ejecutar.

Aprovechando estas nuevas características, vamos a hacer un Runner que ordene los test de una clase por la Description de cada test (el nombre del método):

public class DescriptionSorterRunner extends JUnit4ClassRunner {

        private static final Comparator comparator = new Comparator() {

            public int compare(Description o1, Description o2) {
                return o1.getDisplayName().compareTo(o2.getDisplayName());
            }
        };

        public DescriptionSorterRunner(Class<?> klass) throws InitializationError {
            super(klass);
            sort(new Sorter(comparator));
    }
}

Como podéis ver, nos estamos valiendo de un Runner que ya viene con JUnit 4. Nos limitamos a crear el Comparator y, en el constructor, usarlo para ordenar los test.

De esta manera nuestra clase de test quedaría de la siguiente manera.

@RunWith(DescriptionSorterRunner.class)
    public class AutentiaJUnit4Test {

        @Test
        public void test10AddElementInDDBB() throws Exception {
            Assert.assertTrue("Añade elementos a la DDBB", true);
    }

        @Test
        public void test20SearchElementInDDBB() throws Exception {
            Assert.assertTrue("Busca en la DDBB elementos que se han añadido en el test anterior", true);
        }
    }

Como veis, sigue quedando muy sencilla, sólo hemos tenido que añadir la anotación @RunWith para indicar que lo queremos ejecutar con nuestro Runner que ordena. Por supuesto también hemos añadido 10, 20, … en el método del test para garantizar que lo ordena alfabéticamente en el orden que nos interesa.

5. Conclusiones

En algunos casos, como este, la documentación no es del todo clara (además la diferencia de idioma siempre puede suponer una dificulta añadida). Así que si la leemos atentamente y revisamos el código fuente (demos gracias al software libre y al de código abierto) podemos conseguir encontrar buenas soluciones.

Con lo que hemos visto conseguimos nuestro objetivo manteniendo nuestras clases de test limpias. No digo que esta sea la única forma de hacerlo, pero no tiene mala pinta. Y, como siempre, os animo que si alguien sabe una forma mejor, escriba un tutorial y lo comparta con todos nosotros 😉


6. Sobre el autor

Alejandro Pérez García, Ingeniero en Informática (especialidad de Ingeniería del Software)

Socio fundador de Autentia (Formación, Consultoría, Desarrollo de sistemas transaccionales)

mailto:alejandropg@autentia.com

Autentia Real Business Solutions S.L. – «Soporte a Desarrollo»

http://www.autentia.com

 

Alejandro es socio fundador de Autentia y nuestro experto en Java EE, Linux y optimización de aplicaciones empresariales. Ingeniero en Informática y Certified ScrumMaster. Seguir @alejandropgarci Si te gusta lo que ves, puedes contratarle para darte ayuda con soporte experto, impartir cursos presenciales en tu empresa o para que realicemos tus proyectos como factoría (Madrid). Puedes encontrarme en Autentia: Ofrecemos servicios de soporte a desarrollo, factoría y formación.

DEJA UNA RESPUESTA

Por favor ingrese su comentario!

He leído y acepto la política de privacidad

Por favor ingrese su nombre aquí

Información básica acerca de la protección de datos

  • Responsable:
  • Finalidad:
  • Legitimación:
  • Destinatarios:
  • Derechos:
  • Más información: Puedes ampliar información acerca de la protección de datos en el siguiente enlace:política de privacidad