jueves, 26 de marzo de 2015

Testear El Interfaz Usuario


Testear El Interfaz Usuario


Introducción


El Visual Studio ofrece un framework muy bueno para testear las clases del dominio del problema. Y todas o casi todas las clases que no tienen relación con el interfaz de usuario.

La cosa cambia, cuando se quiere testear el interfaz de usuario, ya que, el software también habría que testearlo en su capa de interfaz. Una modificación en la capa de negocio, puede afectar a como se presentan los datos en el interfaz.

En su día, empece a investigar como crear test de interfaz de usuario con el visual studio 2010 y cuando ya tenía unos cuantos definidos, al día siguiente al correrlos, me fallaban los test y no había realizado ningún cambio, y yo veía que tenían que pasar, ya que lo estaba viendo en el interfaz. Daba excepciones al leer de una celda de un grid, al realizar un click en un menú..y claro..no pasaba el test.

Por ejemplo, si tenemos una aplicación que realiza un calculo, además de los test que comprueba que el algoritmo de calculo es correcto y arroja los resultados esperados, estos resultados se verán luego en pantalla de alguna forma, en forma de grid, gráfico,..y es aquí donde interviene los test de intefaz de usuario.

Aunque en un principio pueda parecer un gasto de tiempo, estos test te aseguran una alta calidad, y sobre todo, te evitan el bug más tonto ante una o varias modificaciones.

Estos test de interfaz de usuario, incluso tiene una variante, que es, cuando tienes un software que lee ficheros en un determinado formato propio, y este formato va variando con el tiempo para ajustarse a los nuevos requisitos de usuario, y la aplicación debe leer cualquier versión del formato, se puede crear un test para leer todos los ficheros de entrada que hay en un directorio y comprobar que todos se cargan bien, y se visualizan correctamente en la interfaz de usuario. Esto me ha pasado en un proyecto y se detectaron más de 50 ficheros antiguos que no se cargaban bien. Ahora, cualquier modificación en el formato, se pasa un directorio donde están todas las versiones de los ficheros y comprueba que se cargan en la aplicación más de 100 ficheros de diferentes versiones. De esta forma, automáticamente se comprueba la carga de todas las versiones de ficheros existentes con el consiguiente ahorro de tiempo y mejora en la calidad del producto.

Mini Framework Test

He cogido un pequeño proyecto que desarrolle para crearme mi vocabulario en ingles y así, no anotarme en un cuaderno, dos o tres veces la misma palabra, que era el problema que tenía. Además, de poder exportar a un fichero cada Topic e imprimirlo o meterlo en el móvil para repasar, pero bueno, este es otro tema.

Para testear myVocabulary, añadí el proyecto 'testerUI', este proyecto es una aplicación de consola, que tiene una referencia a myVocabulary para que pueda crear la ventana principal, el MainForm, y así poder testear el interfaz.



La chicha está en la clase 'TesterAgent'. Esta clase, a través de reflexión que proporciona .Net, recoge todas las clases que se han definido en el proyecto 'testerUI' con el atributo [TestClassUI]. Y una vez que tiene la lista de todos los tipos, crea cada instancia y va llamando a cada método público, que es al final, la ejecución de un test de interfaz de usuario.



Cada método público recibe como parámetro un objeto de tipo MainForm, para poder llamar a sus métodos y simular la interacción del usuario. Aquí, estos métodos públicos, deben llamar al objeto MainForm con el Invoke, ya que como el test se está ejecutando en un Thread que no es el thread de interfaz de usuario.

Ejemplo:


/// <summary>
        /// testNumberTopicsAndWords_OnFile
        ///
        /// Testea la carga del fichero
        /// </summary>
        /// <param name="mainForm"></param>
        public void testNumberTopicsAndWords_OnFile(MainForm mainForm)
        {
            int numberOftopics =0;
            int numberOfWordsOnTopics = 0;
            string solutionPath = TestDirectory.GetSolutionPath();
            string fullTestFileName = solutionPath + @"\testerUI\TestFiles\myVocabularyTest.xml";
            mainForm.Invoke((MethodInvoker)delegate
            {
                mainForm.openVocabularyXML(fullTestFileName);
                numberOftopics  = mainForm.GetNumberOfTopicsInUI();
                mainForm.SelectTopicUI("Adverbs");
                numberOfWordsOnTopics = mainForm.GetNumberOfWordsOnRightView();
            }
            );
            if (numberOftopics != 1)
            {
                String sError = "Numero de Topics incorrecto en el xml";
                throw new TestException(sError);
            }
            if (numberOfWordsOnTopics != 3)
            {
                String sError = "Numero incorrecto de palabras";
                throw new TestException(sError);
            }
        }


En el test se ve como se llama a openVocabularyXML, a SelectTopicUI, dentro del Invoke, y es que estos métodos, interinamente llaman a controles del interfaz de usuario. Y por eso necesitan ser llamados desde Invoke.

Luego, se obtienen los datos que se quieren testear, en este caso, el número de Topics que se cargo en el árbol del interfaz de usuario, y el numero de palabra que hay en el listview de la parte derecha del interfaz de usuario.

Corremos el proyecto 'testerUI', se ejecutaran los test, y nos mostrará lo siguiente.



En la pantalla, está el resultado del ultimo test, que comprobaba que al seleccionar la palabra whereabouts en el listview, se mostraba en la ventana de detalle, el significado correcto de la palabra, donde, - paradero,...

TestingProject


Este proyecto es un proyecto normal de test que genera el visual estudio que normalmente se usa, por lo menos de momento yo, para testear la capa de negocio. Pero, hay algunas veces, dependiendo del tipo de test y de los tipos de controles a testear en la interfaz de usuario que se puede testear desde este tipo de proyectos.

Por ejemplo, vemos en TestingProject una clase de test que se llama LoadFileTest, que contiene dos test:

testNumberTopicsAndWords_OnFile
testTextOfMeaningOfaWord

Bien, el test testNumberTopicsAndWords_OnFile, funciona bien, pasa el test, pero el otro test testTextOfMeaningOfaWord, no funciona por que no se da la cascada de eventos que se tiene que dar para pasar el test. Y esto es por que el interfaz de usuario no es visible. En estos casos, estos test que necesitan una secuencia de eventos igual que como si estuviera visible al interfaz de usuario, se pasaría el test al proyecto 'testerUI'.






Conclusión


Dejo aquí el código fuente de todo, ya que será más auto explicativo que dos páginas escritas. 

El crear test de interfaz de usuario para cubrir todo el interfaz llevaría mucho tiempo, pero creo que si se crean test para cubrir partes del interfaz de usuario donde se cree que puede ser la principal fuente de bugs, o que debe ser la parte mejor testeada, puede ser de gran utilidad. Por ejemplo, si estamos desarrollando una aplicación que visualiza una onda en una gráfica, se generaría una onda conocida, y luego comprobar que la gráfica tiene los puntos en las coordenadas esperadas.

También este tipo de test, permite detectar cierres inesperados de la aplicación rápidamente, sobre todo, si el programa permite la carga de fichero, y isa dada una lista de ficheros, cárgalos y comprobar que no...estalla...el programa al cargarlos.


Félix Romo
felix.romo.sanchezseco@gmail.com