El uso de herramientas de apoyo para la valoración de actividades prácticas de programación Francisco Durán Francisco Gutiérrez Ernesto Pimentel Ingeniería en Informática Universidad de Málaga Resumen La actual situación de los grupos en los que se ha aplicado la experiencia piloto de Convergencia al Espacio Europeo de Educación Superior en las titulaciones de Informática de la Universidad de Málaga, y en particular, en la asignatura “Laboratorio de Tecnología de Objetos”, hacen difícil un seguimiento personalizado de los alumnos con los recursos de profesorado con los que se ha contado hasta el momento, que no han diferido de los de cursos anteriores. Más de 200 alumnos se han visto involucrados, y el contenido, fundamentalmente práctico, relativo a temas de programación, agravan esta situación. Con objeto de conseguir el efecto motivador, el trabajo continúo y la revisión de temas no asimilados, que un seguimiento personal del profesor suele producir sobre el alumno, durante este curso se ha hecho un uso sistemático del campus virtual de la Universidad de Málaga, y éste se ha complementado con la aplicación de una herramienta desarrollada por los profesores de la asignatura para informar al alumno sobre los errores detectados en las prácticas desarrolladas, y notificar de la superación de las mismas. Del mismo modo, se han utilizado herramientas para garantizar la autoría individual de los trabajos. Aunque la herramienta implementada fue desarrollada inicialmente para ofrecer al profesor una automatización del proceso de valoración de prácticas, asignando niveles distintos de superación de objetivos, entre las propiedades más interesantes se puede destacar la emisión de informes al alumno, indicando las fases superadas en la práctica y la detección inmediata de los errores cometidos. La inmediatez y detalles de la información facilitada, además de motivar al alumno, le permiten evolucionar de forma independiente en aquellas situaciones donde no tenga acceso directo o inmediato al profesor. La herramienta está estructurada de manera que los profesores sólo han de desarrollar las pruebas concretas de la práctica en cuestión, siendo el motor que pasa las pruebas y genera los informes común a todas las prácticas. 1. Introducción La asignatura de “Laboratorio de Tecnología de Objetos” se imparte en la Escuela Técnica Superior de Ingeniería Informática de Málaga dentro de sus dos especialidades técnicas, Sistemas y Gestión, y en la ingeniería superior. Dentro del currículo, se sitúa en el segundo cuatrimestre del segundo curso con 6 créditos, con un gran peso de clases prácticas, desarrolladas en su mayoría en laboratorios. Por supuesto, también tiene un contenido teórico importante necesario para dominar la tecnología, que se introduce por primera vez en esta asignatura. La necesidad de introducir gran cantidad de conceptos nuevos, hace que se reduzca el tiempo dedicado al trabajo en el laboratorio. En su momento, los profesores de la asignatura afrontaron la necesidad de incluir prácticas adicionales a las tutorizadas en clase de laboratorio, para que los alumnos las desarrollaran como parte de su trabajo personal (contabilizado en los créditos ECTS), sin presencia del profesor. Si bien los alumnos se felicitaron de esta idea, la realización de las mismas resultaron algo frustrante para ellos porque cierto tipo de 1 dificultades les obligaba a consultar al profesor antes de proseguir con el trabajo, impidiendo la continuidad del mismo. Observamos que muchos de los estudiantes realizaban las prácticas en días no lectivos (fines de semana, festivos, Semana Santa, etc.), por lo que esa consulta no tenía lugar hasta la reanudación del período lectivo. Esto frustraba enormemente al alumno, frenando su iniciativa y por tanto su aprendizaje. Con objeto de resolver en parte ese problema, decidimos introducir una herramienta de apoyo al aprendizaje del alumno, de manera que cuando se producía esa parada, pudiera informarle de la situación anómala en la que se encontraba, es decir de los fallos que estaba cometiendo y que le impedían seguir con la práctica. Además, era necesario que la herramienta fuera capaz de ofrecer una valoración justificada al alumno del trabajo realizado en la práctica de manera que pudiera ir mejorando esa valoración atendiendo a las indicaciones que la herramienta proporciona. De este modo, uno de los objetivos iniciales para la construcción de la herramienta, que era el soporte para la evaluación automatizada de ejercicios, se vio superado por las posibilidades pedagógicas que la herramienta ofrecía cuando el alumno hacía uso de ella. Así nació JCap, una herramienta para el apoyo a la corrección automática de prácticas. 2. Antecedentes La asignatura de Laboratorio de Tecnología de Objetos, impartida en las tres titulaciones de Informática de la Universidad de Málaga, dispone de un temario común y se encuentra fuertemente coordinada por los profesores que la imparten. Las prácticas de la asignatura se realizan utilizando como lenguaje de programación Java. En la actualidad existen varias herramientas dedicadas a verificar la corrección de programas [1], orientadas, por lo tanto, al desarrollo de sistemas software. Entre éstas podemos destacar JUnit [2], centrada en el lenguaje Java, y adecuada por proporcionar una comprobación de la corrección de cada aplicación que se desarrolla. Para usar este tipo de herramientas, el programador debe generar lo que se denominan clases de prueba, una por cada clase que necesite verificar. Cada uno de estos elementos debe verificar la corrección de todos los métodos (funciones) que incluye la clase (módulo en un lenguaje orientado a objetos). Así, se genera una batería de pruebas que puede lanzarse de forma automática cada vez que se produce un cambio en la implementación que se está desarrollando. Cuando una de esas pruebas encuentra un problema, se informa al usuario mostrando el punto donde se ha producido y el motivo que lo ha provocado. Esto no detiene al resto de pruebas que siguen ejecutándose proporcionando al final un informe con los resultados obtenidos en cada prueba. 2 Sobre JUnit se han desarrollado otras herramientas que ofrecen una interfaz más amigable de manera que el usuario va siguiendo la evolución de las pruebas que la herramienta va ejecutando sobre sus clases. En el ámbito de la docencia, una herramienta como JUnit también puede resultar de ayuda tanto para el alumno como para el profesor. Para el alumno, porque unas clases de prueba le ayudan a comprobar que cualquier modificación o implementación realizada satisface las especificaciones. Además, si es el profesor el que proporciona las clases de prueba el alumno se siente corroborado en su trabajo. Para el profesor, porque puede obtener información sobre la funcionalidad de los ejercicios entregados por sus alumnos. Sin embargo, en este caso, el profesor debe repetir el procedimiento con cada uno de los alumnos. Dado que la herramienta tiene un enfoque claramente orientado a la Ingeniería del Software, se encuentran ciertas dificultades cuando se intenta utilizar en el ámbito educativo. Enumeramos a continuación algunas de estas dificultades: • JUnit es adecuado para ejecutar las pruebas sobre un conjunto de clases, pero es el profesor el que debe hacer esto con cada alumno. Evidentemente, éste es un problema menor, ya que se podría resolver diseñando una herramienta sobre JUnit que automatice el proceso. • La integración de JUnit con las clases a verificar se hace sobre el código fuente. Esto es, las clases de prueba que JUnit define se compilan junto con las clases que se quieren verificar. Esto significa que, si por cualquier razón, las clases a verificar no compilan, no es posible ejecutar la prueba. Nuevamente este problema es evitable pues también en JUnit podemos desarrollar herramientas para utilizar simplemente los compilados de las clases de prueba. Sin embargo, en ese caso, la información sobre si las clases del alumno compilan o no, estarían fuera de la prueba en sí, y no podrían ser tenidas en cuenta en el informe emitido y la valoración final. • Si un método produce ejecuciones que no terminan (error típico y habitual en alumnos de este nivel), JUnit no se detiene, ni advierte esta situación, por lo que necesitaría de la intervención del profesor. Si lo hace, se detiene la ejecución de todas las pruebas, pero en el ambiente docente puede que esta circunstancia aparezca con frecuencia como consecuencia de un mal diseño. Evidentemente se debe advertir de este problema al alumno, pero el resto de pruebas no debería detenerse por ello. Aún no entrando en ejecuciones que no terminan, es deseable fijar tiempos máximos para realizar las pruebas de manera que si se supera ese tiempo en alguna prueba se interrumpa su ejecución, informando nuevamente de que el algoritmo utilizado sobrepasa los requisitos temporales considerados. • JUnit no es capaz de valorar el grado de cumplimiento de las especificaciones. Es decir, simplemente informa de que algo ha ido mal pero no valora el que haya ido bien. En el contexto de 3 una actividad docente es de sumo interés valorar el trabajo del alumno atendiendo a cómo ha cumplido los objetivos y, si es necesario, penalizando las situaciones anómalas que aparezcan: ejecuciones que no terminan, provocar excepciones no esperadas, problemas de compilación, etc. Como ya se ha mencionado, algunos de los problemas identificados se pueden resolver con cierta facilidad utilizando JUnit. No obstante, otros, quizás los más interesantes desde el punto de vista docente, no tienen fácil solución. En este sentido sería deseable disponer de una herramienta como JUnit pero que resuelva esas carencias. Así, nace JCap, una herramienta para la corrección automática de prácticas. JCap resuelve todos los problemas mencionados anteriormente, y en particular, valora el trabajo realizado por cada alumno permitiendo tener en cuenta malas prácticas de diseño. Al igual que JUnit, JCap no libera al profesor de la tarea de confeccionar las clases de prueba. 3. JCap Para cada práctica JCap permite realizar una batería de pruebas. Cada prueba contendrá una secuencia de comprobaciones. Si en una prueba falla una comprobación, la prueba termina y se procede con la siguiente. Al final del proceso se tendrá una valoración de cada prueba, un informe de los errores cometidos y una evaluación final que será la suma de estas valoraciones. Las valoraciones pueden ponderarse para que unas pruebas tengan más peso (o importancia) que otras en la valoración final. Cada prueba deberá implementar la interfaz Caso que obliga a definir los métodos float valor() y void evaluar(Aserto). El primero proporciona la valoración máxima para esta prueba y el segundo desarrolla la prueba en sí, e incluirá las diferentes comprobaciones que la conforman. La valoración de una prueba se obtiene según se superen la secuencia de comprobaciones que llamaremos niveles. Cada prueba dispone de un nivel de comienzo (normalmente 0) y un nivel máximo. Según se superen comprobaciones, se van consolidando niveles hasta alcanzar el máximo. La valoración máxima se obtiene cuando el nivel máximo es alcanzado. Pasamos a definir algunos conceptos alrededor de una prueba: Valor de la prueba:- Será la valoración máxima que puede alcanzarse en esta prueba. Cada prueba puede tener un valor diferente lo que permite ponderar las diferentes pruebas. Por ejemplo, es posible realizar dos pruebas, una con valor 8 y otro con valor 10. Si el alumno obtiene un 5 y un 7 respectivamente, las valoraciones de cada prueba serán 5/8 y 7/10. La valoración final será 5/8+7/10. No existe un valor de la prueba por defecto por lo que debe implementarse el método float valor() que devuelve ese valor. Nivel de comienzo consolidado:- Será el nivel consolidado inicial en esta prueba. Éste será el nivel consolidado si no se supera la primera comprobación. Por defecto este valor es 0 pero puede modificarse. 4 Nivel próximo a consolidar:- Será el próximo nivel que se consolidará si se supera una comprobación (o grupo de comprobaciones). Su valor comienza por defecto en 1 aunque puede modificarse. Nivel consolidado:- Cada vez que se supera una comprobación (o un grupo de comprobaciones) se consolida el nivel indicado en el nivel próximo mencionado anteriormente y se incrementa. El nivel consolidado será el mayor de todos los niveles consolidados hasta ese momento. Nivel máximo a consolidar:- Será un máximo para el nivel consolidado. No será posible consolidar un nivel superior a este valor. El cálculo de la valoración de una prueba se calcula como el cociente entre el nivel consolidado de la prueba y su nivel máximo, multiplicado por el valor de la prueba. Esta valoración puede verse reducida por alguna de las siguientes situaciones: • La prueba no termina por entrar en un bucle infinito o retardar demasiado su conclusión. • La prueba termina produciendo una excepción inesperada. • La prueba termina por un problema de compilación. Las reducciones en la valoración se definen en tantos por 1 sobre el valor de la prueba y pueden: a) tomarse por defecto, b) definir unos valores específicos para todas las pruebas o c) definir unos valores particulares para una prueba en concreto. La herramienta está dotada de un mecanismo de control que impide que el nivel máximo, próximo a consolidar y de comienzo consolidado sean incoherentes. Si por alguna circunstancia esto se produce, se lanzará una excepción del tipo AsertoError con información del problema detectado, terminando abruptamente la corrección de la práctica. Este error indica que la batería de pruebas no se ha diseñado correctamente, por lo que habrá que revisar los valores iniciales de cada prueba y las pruebas en sí y ejecutar la corrección de nuevo. Es decir, se trata de un error cometido por el profesor o asistente que elaboró la prueba, y no del alumno. Como ya se ha mencionado, la ejecución de una prueba se realiza dentro del método void evaluar(Aserto). El argumento es un objeto que permite inicializar la prueba, modificar sus parámetros, ejecutar las comprobaciones incluidas en esa prueba y finalizarla. Antes de empezar con una prueba debe inicializarse, debiéndose indicar el nivel máximo para la prueba. También es posible especificar el nivel próximo y, si es necesario, el nivel de comienzo consolidado. Una vez inicializada, el marco de trabajo permite realizar comprobaciones utilizando los siguientes métodos o funciones: void verdad(boolean, String), 5 void falso(boolean, String), void verdadMismoNivel(boolean, String), void falsoMismoNivel(boolean, String), void verdadEnNivel(boolean, int, String), void falsoEnNivel(boolean, int, String). El último argumento es una cadena de caracteres que describe el motivo por lo que la comprobación (proporcionada como primer argumento) puede no satisfacerse. Esta cadena será mostrada en el informe que se genera si, efectivamente, la comprobación falla. Los dos primeros métodos consolidan el nivel próximo a consolidar y lo incrementa. Los dos siguientes aparecen dentro de un grupo de comprobaciones y se realizan en un mismo nivel, consolidándose éste si todas las comprobaciones de este tipo se satisfacen. Cualquier aparición de otro tipo de comprobación, consolidará el nivel. Los dos últimos métodos permiten realizar comprobaciones en un nivel específico (proporcionado como segundo argumento). Para finalizar la prueba se utiliza el método void finalizar() que finaliza la prueba habiendo superando todas las comprobaciones, verificando, por tanto, que se ha alcanzado el nivel máximo, y lanzando una excepción AsertoError si esto no ocurre así. Existen otras características de inicialización y finalización que permiten mayor flexibilidad a la herramienta. 4. Paquete jcap.jar En esta sección, describimos de forma técnica las componentes que conforman JCap. Para el lector que no esté interesado en la estructura interna de la implementación, puede omitir la lectura de la misma. En la 6 Figura 1: jcap.jar figura 1 se muestra el diagrama UML (Unified Modeling Language) del paquete jcap.jar para dar una idea global de las clases, intefaces y métodos que la herramienta contiene: • Aserto: Los objetos de esta clase son los encargados de inicializar la prueba, realizar las distintas comprobaciones que forman parte de la misma y finalizarla. También permite parametrizar la prueba y mantiene la información necesaria para poder valorarla. • AsertoError: Esta clase se encarga de gestionar las excepciones que puede lanzar la aplicación por un mal diseño de alguna de las pruebas. • Corrector: Esta clase es la encargada de lanzar la batería de pruebas que forman la corrección e ir generando el informe. También genera una calificación global de la práctica a tenor de los resultados obtenidos en cada prueba. • Caso: Es una interfaz que establece los métodos que necesita implementar una prueba. Básicamente se trata del método que lanza la prueba y la valoración de la misma. • CorrectorConstantes: Esta interfaz define varias constantes usadas por la clase Corrector. Valoración por defecto de una prueba, tiempo máximo de espera por prueba, disminución en la valoración en caso de no terminación, excepciones incontroladas o inadecuadas, errores, etc. 4.1. Estructura básica de una corrección con JCap En la valoración de una práctica se debe generar una clase de prueba por cada prueba que queramos realizar sobre la misma. Por ejemplo: PruebaBasicaVertices, PruebaBasicaArcos, pueden ser dos pruebas, sobre una práctica relacionada con la implementación de grafos. La primera verifica el correcto funcionamiento de la inserción y extracción de vértices en el grafo mientras que la segunda verifica la correcta inserción y extracción de arcos en el grafo. Cada prueba debe implementar la interfaz Caso del paquete jcap. Una táctica a emplear a la hora de realizar las clases de prueba es hacer que todas hereden de una clase abstracta que implemente la interfaz Caso. En esa clase abstracta podemos definir cualquier constante común a todos las pruebas (número de repeticiones de un bucle, valores por defecto, nombres de ficheros, etc...), incluso una valoración de la prueba común por defecto. Por ejemplo: import jcap.Caso; abstract public class DriverGrafos implements Caso { public static final int NUMVERTICES = 30; public static final int NUMARCOS = NUMVERTICES * NUMVERTICES; public static final int REPETICIONES = 7; public float valor() { return 10f; } } 7 import jcap.Aserto; class PruebaBasicaVertices extends DriverGrafos { public void evaluar(Aserto aserto) { // Aquí se realizan las comprobaciones … } } Será responsabilidad del profesor o ayudante definir una aplicación que genere un objeto de la clase Corrector para agrupar todas las clases de prueba. Cuando se invoque el método void ejecutar() se ejecutarán todas las pruebas generando un informe en el dispositivo de salida estándar (pantalla). Por ejemplo: import jcap.Caso; import jcap.Corrector; public class MainTestGrafos { public static void main(String[] args) { Caso[] pruebas = { new PruebaBasicaVertices(), new PruebaBasicaArcos() }; Corrector test = new Corrector(pruebas, 9000, 0.2f, 0f, 0.2f, 10f); test.ejecutar(); } } Los argumentos del constructor de la clase Corrector son: el array con los objetos que implementan las pruebas; el tiempo máximo que se dedicará a cada prueba en milisegundos; los tres siguientes argumentos son penalizaciones dadas en tanto por 1: por no terminar, por lanzar una excepción no esperada y por provocar un error. El último argumento es la máxima calificación posible para la corrección. 4.2. Creación de casos de prueba Ya hemos mencionado que para crear una prueba se ha de implementar la interfaz Caso. Esta interfaz obliga a definir los métodos void evaluar(Aserto aserto) y float valor(). Este último método se usa para indicar la calificación máxima de ese caso de prueba lo que permite ponderar el peso de cada caso. Dentro del método void evaluar(Aserto aserto) se realizan las comprobaciones que forman parte de la prueba. Un ejemplo para comprobar la corrección de la inserción y la extracción de vértices en un grafo podría ser el siguiente: import jcap.Aserto; import grafos.*; class PruebaBasicaVertices extends DriverGrafos { public void evaluar(Aserto aserto) { aserto.inicializar(8); Grafo<String> g = new GrafoLAdj<String>(); aserto.verdad( (g.tamaño() == 0) , "Un GrafoLAdj no tiene 0 vértices tras crearse"); for (int i = 1; i <= REPETICIONES; i++) { g.ins("1"); aserto.verdadMismoNivel( (g.tamaño() == 1), "Tras insertar el primer vértice 8 debería haber un vértice"); aserto.verdadMismoNivel((g.pertenece("1")), "Tras insertar el primer vértice este no está"); g.elim("1"); aserto.verdadMismoNivel( (g.tamaño() == 0), "Tras eliminar el único vértice el tamaño no es 0"); g.ins("1"); aserto.verdadMismoNivel( (g.tamaño() == 1) , "No tiene un vértice tras insertar uno en un grafo vacío (posiblemente no quedó en un estado correcto tras la extracción previa)"); aserto.verdadMismoNivel( (g.pertenece("1")), "No está el vértice insertado en un grafo vacío (posiblemente no quedó en un estado correcto tras la extracción previa)"); g.ins("1"); aserto.verdadMismoNivel((g.tamaño() == 1), "Cambia el tamaño del grafo tras insertar en él un vértice que ya estaba"); aserto.verdadMismoNivel( (g.pertenece("1")), "Tras insertar un vértice que ya estaba dice que éste no está en el grafo"); aserto.separarMismoNivel(); } aserto.finalizar(); } public float valor(){ return 10f; } } Obsérvese que la expresión aserto.separarMismoNivel() separa grupos de comprobaciones de un nivel a otro, consolidando el anterior. 5. Perspectivas del profesor y el alumno sobre JCap Una vez diseñada la prueba, la herramienta ofrece dos perspectivas para su ejecución. Una para el profesor y otra para el alumno. La perspectiva del profesor permite la ejecución de una prueba sobre un conjunto de prácticas del mismo o diferentes alumnos, generando un informe por cada alumno, y un resumen global con las valoraciones de cada una de las prácticas. La perspectiva que la herramienta ofrece al alumno permite seleccionar el programa de pruebas que desea aplicar a su práctica, permitiendo la selección de pruebas individuales, con objeto de poder probar funcionalidades parcialmente desarrolladas. En ambas perspectivas, los informes pueden ser generados con distinto nivel de detalle, atendiendo a los intereses del usuario (profesor o alumno): 9 • Calificación: Sólo se proporciona información sobre los errores y valoraciones de las pruebas. • Comentario: Además se detallan las pruebas que se van realizando. • Instrucción (sólo accesible para el profesor): Además de la información proporcionada por los dos niveles anteriores, se detallan las operaciones que la herramienta va ejecutando sobre la práctica a valorar. 6. Conclusiones En este trabajo se ha descrito una herramienta para la corrección y valoración de prácticas de laboratorio en asignaturas de programación. Aunque está orientada al lenguaje de programación Java, su adecuación a cualquier otro lenguaje orientado a objetos sería bastante directa. La herramienta constituye un buen soporte al profesor cuando éste tiene grupos numerosos que impiden un adecuado seguimiento de las prácticas. Al mismo tiempo, permiten que el alumno pueda desarrollar su trabajo con cierta independencia, obteniendo de la herramienta una respuesta rápida sobre los posibles fallos que pueda cometer, así como una valoración sobre el nivel del cumplimiento de objetivos. Esto suele redundar positivamente en la motivación del alumno, tanto por la “tutorización virtual” recibida, como por la inmediatez de la misma. Como trabajo futuro, planteamos la integración de JCap con Moodle [3] (herramienta de campus virtual utilizada en la Universidad de Málaga), de forma que la ejecución de una prueba se pueda establecer como tarea de Moodle, y la valoración generada por la herramienta se incorpore al expediente del alumno en el campus. Referencias [1] Paul Hamill. Unit Test Frameworks. O’Really. 2004. [2] Andy Hunt y Dave Thomas. Pragmatic Unit Testing. 2003. [3] Moodle: Course Management System. http://www.moodle.org. 10