Printable Febrero

Planificación iteración Marzo

Tipo de iteración

Construcción


Objetivos generales

Pasados los exámenes, la iteración de Marzo se centrará en conseguir de una vez por todas una beta funcional del programa. Dado el imprevisto de la entrega y testeo con usuario real antes de Semana Santa, estructuraremos la iteración en dos partes; la primera, concluir la solución de los problemas pendientes de la aplicación y desarrollar lo que queda del GUI para poder demostrar la aplicación, y la segunda, una vez realizada la prueba del programa, solventar lo que descubramos en dicha prueba (bugs, problemas de usabilidad, etc) y mejorar la calidad de la aplicación y de la documentación con ello.

Concluir el GUI

Actualmente el GUI tiene una gestión de preferencias extensiva, si bien puede recibir bastantes mejoras de usabilidad y calidad. Para poder demostrar la aplicación necesitaremos algún interfaz para mostrar toda la información que se tiene almacenada -junto con otros datos como cuándo se guardó, y cuánto ocupa- y poder extraer snapshots, borrarlos, etc. Ello implica, además del riesgo de desarrollo propio por no estar aún empezado, problemas adicionales en la comunicación con el backend, ya que necesitamos un método de refresco de dicha información para evitar hacer polling -costoso y poco elegante, tanto en código como de cara al usuario-, lo que requiere de una ruta de información adicional inversa a la que teníamos y que vaya desde backend a frontend.

Asimismo, será interesante explorar alguna posibilidad de presentar ese feedback al usuario sin tener la aplicación principal en ejecución, como un icono de la barra de tareas, o popups de información avisando de problemas, o de la conclusión de alguna operación larga, lo cual podría incluso requerir de un pequeño demonio de usuario adicional para evitar hacer dependiente el backend de ningún tipo de interfaz gráfica, o en su defecto de funcionalidad añadida en el GUI que permita minimizarse a la barra de tareas.

La interfaz plenamente usable de la barra de tiempo queda relegada al siguiente mes a sabiendas de la escasez de tiempo, ya que se está investigando actualmente sobre la integración con nautilus y para reutilizar trabajo sería conveniente que, tanto desde nautilus como desde la aplicación, el código de visualización fuera el mismo. En cualquier caso se juzga útil y sencillo de usar tener dos tipos de vista, una con "todos" los backups, y otra relacionándolos directamente con su ubicación en el sistema de ficheros, para que sea como explorar el disco del modo habitual. Se comenzará en cualquier caso su desarrollo durante la segunda mitad de esta iteración, para anticipar posibles problemas.

Concluir el backend de creación de backups

Concluido el desarrollo aceleradamente del mismo, queda integrarlo con la aplicación, definir del todo sus API, testearlo y documentarlo convenientemente. A sabiendas de su importancia, es la prioridad máxima, junto al interfaz que permita su demostración, en nuestro desarrollo.

Nueva organización de trabajo

El segundo cuatrimestre ha cambiado los horarios de todo el grupo, y por tanto debemos encontrar un nuevo horario de reunión.

Abstraer dbus en el backend y frontend.

Dado que la aplicación era meramente un prototipo y la comunicación entre procesos aún estaba en pruebas, el código tiene la funcionalidad referente a dicha comunicación repartido en todas las funciones que lo necesitan. Se abstraerá dicha funcionalidad a clases independientes que se encarguen de dicha comunicación y actúen como proxys, para facilitar la depuración, separar en responsabilidades las clases y reducir problemas de refactorización.

Consola de la aplicación

Inicialmente creada para un prototipo, se ha recuperado su desarrollo y se tendrá una versión ejecutable en consola de la aplicación de funcionalidad completa, que además de ayudar con la automatización de las pruebas permitirá cubrir historias de uso (ejemplo: restaurar la configuración de xorg).

API de las bases de datos

Actualmente, y por flexibilidad en el desarrollo, usamos unas funciones bastante genéricas para el acceso a las bases de datos de la aplicación. Una vez van estando concluidos los módulos que las usan, queda cerrar esos subsistemas concretando en funciones específicas esos accesos, para conseguir la independencia real de la base de datos.

Testing

Durante la segunda mitad del mes, se deberá decidir la política sobre unidades de pruebas que seguiremos, documentarla y en su caso implementarla.

Documentación

Se deberá concluir de documentar los cambios en los procesos (testing, integración, entorno de desarrollo pydev), así como el UML y todo lo referente a patrones de diseño. Se realizará durante la segunda mitad del mes, pasada la entrega, y para el UML y los patrones de diseño se intentará contar con el equipo entero por considerarse imposible de otro modo y ser además importante para el examen de la asignatura.

Codetón

Hemos establecido una fecha para reunirnos en casa de uno de los miembros del equipo durante todo un fin de semana y avanzar el desarrollo. Pese al riesgo de baja productividad de no estar programando solos, se considera que será provechoso por la presión de la fecha de entrega, de estar todo el grupo juntos (donde quien trabaje presionará a quien no lo haga), y sobre todo por la facilidad de intercomunicación entre distintos grupos de trabajo para solucionar problemas referentes a la comunicación entre partes distintas del código. A tal fin se deberá tener el código "interno a cada parte" listo para entonces, y se empleará la mayor parte del tiempo en integración.

Despliegue del software

Dado el importante handicap de última hora de que no se pueda instalar software en linux en los laboratorios para nuestro proyecto, hemos de buscar formas de despliegue alternativas. Varias posibilidades están en trámite, entre ellas utilizar nuestros portátiles para ello, y combinarlo con uso de las X en remoto a través de la red de la complutense hacia equipos situados en otra parte, permitiendo ejecutar la aplicación "en remoto" albergada en terceros sistemas - a los que Adrián y Ezequiel pueden conseguir acceso- lo bastante grandes como para permitir varios usuarios a la vez operando.


Reparto de trabajo

Dado que una parte de la aplicación está concluida (watcher y las bases de datos) se reasignará el personal de ese grupo para apoyar en el resto. El nuevo reparto de personal queda como sigue:

  • Interfaz de usuario: Daniel Tabas, Rober y Carlos, a los que se unen en principio Adrián y Diana. Asume la responsabilidad del grupo Adrián.
    • Tabas acaba la comunicación backend / frontend, por ser el más experimentado trabajando con dbus, y la ruta de feedback que está por desarrollar, incluyendo posiblemente la investigación sobre systray y popups con libnotify.
    • Mario se encarga de la consola, ya está trabajando en ello.
  • Backend : se une Jorge como mercenario y sale Mario. Responsable sigue siendo Salva.
  • Bases de datos: sigue al mando David, que deberá implementar a petición, ayudar con el testeo del backend (en lo tocante a inicializar esas bases de datos para su prueba) y cerrar lo antes posible el API de las mismas, documentándolo en el proceso.
  • Documentación: Adrián y Ezequiel, ayudados por Salva cuando proceda. El UML lo haremos entre todos, cada uno ocupándose de su parte.
  • Preparación de la entrega: A priori, Adrián y Ezequiel, que intentarán preparar la entrega (el despliegue).

Estimaciones de tiempo

  • 5 de marzo: cierre parcial de código de cara a la codetón. Solamente minimizar lo que queda por hacer interno a cada módulo, y solucionar los problemas de la última integración.
  • 8 de marzo: decisión sobre la integración con Nautilus.
  • 7 de marzo al 9 de marzo: Codetón. Para poder dedicarnos plenamente a solucionar problemas de la comunicación entre partes del código, así como dependencias entre las mismas.
  • 11 de marzo: cierre parcial de código. Solo bugfixes críticos a trunk, el resto en cada rama de desarrollo.
  • 13 de marzo: Entrega en laboratorio.
  • 14 de marzo al 23 de marzo: Semana Santa (aprox). A falta de reunirnos, aún no está establecido quién podrá trabajar durante esa semana, y probablemente será complicado reunirnos durante ella. No obstante se planificará trabajo recién entregado.
  • lunes 24 de marzo: Nuevo cierre parcial de código, depuración, integración de todas las ramas de desarrollo. Documento de clases actualizado y UML de todos los equipos entregado.
  • 27 ó 28 de marzo: Entrega final.

Seguimiento

Ver seguimiento-marzo


Seguimiento Febrero

Objetivos cumplidos
Núcleo de la aplicación terminado

La funcionalidad crítica de crear y recuperar backups está finalizada. Aunque no era un objetivo fijado para el mes, gracias a un notable esfuerzo por parte del desarrollador principal durante la última semana ya está desarrollado el código para crear y recuperar snapshots. Lo que queda por delante, en esencia, será ampliar la funcionalidad para cubrir el máximo de casos de uso (pues por ahora solo cubre lo esencial), así como conectarlo con el resto de la aplicación definiendo ya de un modo fijo las API internas, y naturalmente un testeo intensivo de esta parte de la aplicación.

Investigación unit tests

La investigación sobre unidades de prueba está finalizada, con un tutorial aquí. Queda decidir los procedimientos sobre el mismo, así como automatizar el testeo una vez se tengan disponibles las pruebas en un servidor para que se realice todas las noches, lo cual se estima trivial.

Investigación nautilus

Durante exámenes un miembro del grupo ha realizado la investigación sobre la arquitectura interna de nautilus y sus posibilidades de extensión, mediante estudio de la documentación disponible y contacto con los desarrolladores mediante correo electrónico. La conclusión es que resultaría notablemente más difícil de lo estimado, al no tener ninguna documentación sobre código ni estar realmente preparado para ello con interfaces externas. Si bien hay bastantes sitios donde integrarse con el mismo (propiedades de un archivo, menús contextuales…) para realizar una vista nueva que incluyese la funcionalidad de la barra de tiempo hay que reimplementar, en C, y prácticamente desde cero sin documentación. No obstante lo dicho, se intentará durante la primera semana confirmar la dificultad del desarrollo, dado que es una parte que aportaría gran calidad y usabilidad a la aplicación.

Concluir la integración de las bases de código

La adaptación del equipo al nuevo esquema de trabajo en el launchpad ha sido sorprendentemente rápida y efectiva. Todos los equipos están trabajando ya sobre la base de código común, y por tanto la integración de trabajo entre grupos es en esencia trivial y no es obligatorio esperar a cada mes para ello, si bien pueda ser recomendable en algunos casos. Además disponemos de la ventaja del control de versiones que proporciona historial extensivo e independiente para cada cambio realizado por cada grupo, lo que en potencia facilitará mucho el depurado y testeo de regresión al poder localizar enseguida cualquier cambio introducido que haga fallar a la aplicación.

Concluir la gestión de archivo de configuración

La clase ConfigFileManager está finalizada y el formato del archivo de configuración definido, junto a funciones get y set auxiliares adicionales. Ello reduce las decisiones a tomar y los posibles malentendidos, al haber un api al que ceñirse.

Objetivos no alcanzados
Completar módulos planificados

Los módulos que se debían completar (watcher y dbus) aún están faltos de pequeños retoques, en algunos casos por peticiones de última hora como en el de dbus (para conseguir una ruta de feedback desde el backend hacia la aplicación). Se estima que lo que queda es mínimo.

Unificar la jerarquía de excepciones del programa

Por falta de tiempo durante exámenes aún no está documentado este paso, si bien existen jerarquías parciales de excepciones.

Migrar la información de depuración a la clase HDLogger
Revisión de calidad de la documentación
Decisión sobre la licencia final del código

Haber utilizado código ajeno (pequeños snippets de ejemplo, principalmente) nos obliga a ceñirnos a la licencia de dicho código. En principio es LGPL, pero se adoptará la GPLv2 por comodidad. Falta simplemente consensuar entre todo el equipo esto, así como modificar los archivos para añadirles la licencia, y posiblemente modificar el entorno de desarrollo para que añada esa cabecera automáticamente.

Riesgos reducidos
  • El riesgo más importante de la aplicación está solucionado, a falta de integrar y testear. Ello nos da un respiro al plan de fase, permitiéndonos probar durante más tiempo la aplicación y en potencia cubrir más casos de uso.
  • El riesgo de integración de bases de código está solucionado sin apenas problemas, que se estimaba de importancia crucial.
  • Riesgos de personal que se habían detectado, independientes de la falta de tiempo, están mitigados y planificados convenientemente.
  • Dos grandes subsistemas de la aplicación (Watcher y SnapshotManager) están muy avanzados (en el primer caso está concluido totalmente, a falta de pequeños problemas que se van detectando y de optimizaciones que se pudieran aplicar). Ello reduce bastante el impacto del riesgo de falta de personal, y además los problemas de intercomunicación, ya que solo quedan dos grandes equipos (backend, concretamente gestión de backups, y frontend dedicados al GUI, estando solucionado casi todo el código intermedio de comunicaciones y gestión de la base de datos).
Nuevos riesgos
  • Otro miembro del equipo ha encontrado trabajo, lo que hace un total de 4 los que están simultaneando trabajo y estudios, más del 25% del equipo. Esto reduce en mucho el ya de por sí pequeño equipo de desarrolladores y aumenta el riesgo de no lograr completar el desarrollo a tiempo.
  • Relacionado con este problema de personal, aún desconocemos el esfuerzo que requerirán las asignaturas de carácter práctico del segundo cuatrimestre para los miembros del equipo. Cabe suponer que, como es habitual, el segundo cuatrimestre sea más intenso en este ámbito que el primero, lo que reducirá aún más el tiempo disponible.
  • El fin de exámenes ha traído notable desorganización al grupo, al no tener el día fijo común para reunirnos. Esto crea un riesgo muy importante de no lograr los objetivos de la primera entrega de Marzo, para la que de por sí hay muy poco tiempo.
  • La sorpresa de la entrega de Marzo, unida al retraso en los grupos de GUI y backend, hace un riesgo de consecuencias severas e importancia altísima el de no poder cumplir ese plazo. Desde el equipo se achaca a avisar con solo un mes y medio de antelación, con los exámenes de por medio.
  • La negativa por parte del equipo técnico a instalar software en los laboratorios nos va a dificultar notablemente el despliegue y testeo de la aplicación, así como hará imposible su instalación en dichos pc's debido a las dependencias lógicas del software que desarrollamos, que intenta reutilizar el máximo de componentes externos posibles. Por nuestra parte consideramos imposible dicho objetivo (desplegar sobre unos equipos que no admiten ningún cambio en la aplicación), y trataremos de llegar a soluciones de compromiso mucho menos que ideales (equipos personales, accesos remotos).
  • La fecha avanza y se agota el tiempo para testeo, mantenimiento y demás.
Tiempo real empleado

Muy poco, al estar de exámenes todo el equipo. El lead developer ha sido quien más trabajo útil ha sacado, dedicando por completo la última semana a finalizar su módulo.

Conclusiones

Como se previo la iteración de Febrero ha sido improductiva, pero nos ha cogido por sorpresa la desorganización del fin de exámenes y los nuevos horarios del segundo cuatrimestre, haciendo que durante la última -y crítica- semana de Febrero no nos hayamos reunido y nos hayamos comunicado mucho peor por ello. No obstante lo dicho, el trabajo de última hora ha salvado la iteración y nos permite un respiro para Marzo, a sabiendas de que, al margen de terminar el interfaz gráfico, lo que queda sobre todo es comunicar módulos, y depurar. Todo el equipo está mentalizado para la importante entrega de Marzo y ya se ha retomado el ritmo de trabajo, en especial ahora que las prácticas de terceras asignaturas aún nos permiten un pequeño respiro.


Tutorial de testing

Introducción al testing por unidades de prueba

Las test unit son trozos de código que se encargan de comprobar que bloques de funcionalidad (funciones, subsistemas, paquetes, etc.) funcionan como se espera. Para ello invocan a esos bloques de código con valores de entrada conocidos y comprueban que el comportamiento es el esperado.

De este modo cuando refactorizamos o cambiamos código nos aseguramos de que el código que antes funcionaba ahora sigue funcionando.

Además, fijada una interfaz es posible escribir test units *antes* de desarrollar el código, que no será completo hasta que pase todas esas test units.

Así, las test units deberían ser:

  • Exhaustivas en "código" (prueban todo el código que hay). Esto es automatizable de comprobar.
  • Exhaustivas en "rango" (comprueban todas las posibles entradas). Esto es mucho más difícil. No es lo mismo asegurarse que una función "multiplica" funciona para 3 * 4, que para nan (not a number) * MAX_INT. Deberían probar, al menos, todos los casos especiales (que pueden no tener código para tratarlas, que pueden derivarse de bugs, cuyo código para tratarlos no está bien escrito pero no ha fallado hasta ahora por no usarse, etc).

Una buena práctica es escribir una test unit por cada bug que se encuentra. Así, futuras revisiones del código no volverán a introducir bugs que ya existían por desconocimiento (por olvidarse un caso especial, por ejemplo, o por refactorizar a ciegas por no estar correctamente documentado).

Como inconveniente, son difíciles de escribir (al menos bien), y son código extra que debe mantenerse libre de errores y refactorizarse junto al código que prueban (así que necesitan mantenimiento además).

Testing en python

La test suite va en un archivo aparte a las clases del programa. Digamos que el criterio será test_NombreDeArchivoOriginal.

La filosofía separa la funcionalidad en:

"test fixture": preparación para un test. Base de datos temporal, datos de prueba, lo que sea. SE RESETEA ENTRE TESTS individuales.
"test case": lo que se prueba: salida esperada para una entrada (para una fixture en realidad) dada.
"test suite": varios test case o test suites. Recursivo :D
"test runner": lo que rula las test suites.
"test result": conceptualmente un "objeto resultado de los tests".

TestCase y FunctionTestCase implementan las test cases. La segunda es irrelevante a nuestros efectos. setUp() y tearDown() (sobrescribir, o pasar a FunctionTestCase) preparan y borran la fixture (¡ojo! solo la borran cuando el test ejecuta).

La suite de test queda implementada por TestSuite (duh)

Ejemplo:
import ModuloAProbar
import unittest

class nombreDelTest(unittest.TestCase): #heredamos de TestCase => esto es un testcase

    def setUp(self): 
        lo que haga falta

    def testCosaAProbarUno(self)
        self.assert_(true == true) #assert_(valor) casca si valor no es true

    def testCosaAProbarDos(self)
        a = true
        b = true #mola lo de no declarar variables :P

        self.assertEqual (a, b) #casca si a no es igual a b

    def testCosaAProbarTres(self)
        self.assertRaises(NombreExcepcion, huh?) # casca si no levantas la excepción esperada

    if __name__=='__main__'
        unittest.main() #maldad suma del ejemplo: así puedes correrlo desde la línea de comandos tb

La clave es que los test empiecen por "test"; así el testrunner sabe qué tests debe ejecutar… y con esos asserts (nótese que assert_() *NO* es lo mismo que assert() !! - ¡¡guión bajo!!. Debe ser para que no casque el testrunner con ese assert, seguramente lo reimplemente) se recopilan los resultados esperados. También hay funciones fail; bastaría mirarlo en la clase base unittest (la diferencia? ninguna. Creo que son renombrados solamente de los assert).

A nivel de resultados del testing, failure es si esperabas algo y te llega otra cosa distinta, mientras que un error es si te sale algo que *no* esperabas, tipo excepción o casque general.

La alternativa a todo eso es:

suite = unittest.TestLoader().loadTestsFromTestCase(TestSequenceFunctions) #decirle a TestLoader que cargue los tests que hay
unittest.TextTestRunner(verbosity=2).run(suite) #Correr en modo texto esta suite, con verbosidad = 2 (quite locuaz).

Y así va mostrando los test que va haciendo cuando se llama a esto desde otro lado (nótese el verbosity=2; aconsejo probar ambas opciones para ver la salida que generan, o meterse en el tutorial y verla).

Con esto ya tenemos para probar automáticamente

Metodología

Probar *todo* lo evidente. Aquí no importa ser bombero. Probar algo tipo 1==1 es absurdo (relativamente: puedes probar la igualdad con eso, así como que hay dos objetos creados…), pero probar algo del tipo hazTalCosa y luego assert(seHizoTalCosa) no lo es. por ejemplo: self.resize(150,100) y luego assert(window.size=(150,100)), o como sea la sintaxis; con eso se comprueba que el resize funcionó como debe.

Modo experto

Para el que quiera escribir menos y pensar más (pocos):

  • La clase principal del testing hereda de unittest.TestCase; así se puede implementar setUp en un único sitio para un conjunto de tests. Otra forma es heredar las clases de los tests de esa clase pincipal y sobrescribir sus métodos runTest . Pero para no acabar con cientos de clases de un solo método (el runTest), la alternativa es crear el objeto con métodos test*, y al construirlo entonces hay que decirle qué se quiere probar ( objetoTest('testPrimero'), nótese el string); eso usa el runTest por defecto y ya llama al método 'testPrimero'. ¡Para cada test, un nuevo objeto! con su nuevo setUp y tearDown.
  • tearDown se ejecuta si setUp se ejecutó (falle el runTest o no); para no ir dejando basurilla.
  • Agrupar cómodamente tests en conjuntos para enterarse luego es fácil:
def suite():
    suite = unittest.TestSuite() # instanciar una suite de tests
    suite.addTest(WidgetTestCase('testDefaultSize')) # añadir el test de nombre "testDefaultSize" de la clase WidgetTestCase
    suite.addTest(WidgetTestCase('testResize'))
    return suite #así sabe por dónde empezar

o incluso :

def suite():
    tests = ['testDefaultSize', 'testResize']

    return unittest.TestSuite(map(WidgetTestCase, tests)) #map mola, y si no preguntadle a Peña XD

y también vale

suite1 = module1.TheTestSuite()
suite2 = module2.TheTestSuite()
alltests = unittest.TestSuite([suite1, suite2])
  • y ya para muy comodones: suite = unittest.TestLoader().loadTestsFromTestCase(WidgetTestCase) busca por defecto -en el diccionario implícito de las clases, supongo… python tiene sus ventajas ;)- los métodos test* e instancia un self para cada función que encuentra.

Funciones útiles

http://docs.python.org/lib/testcase-objects.html

Los puntos suspensivos son solo para no copiar otra vez lo mismo; son los mismos parámetros. Donde no estén indicados, son los obvios.

assert_(...) / failUnless (expresion, [mensaje]) - el mensaje opcional, pero convendría
assertEqual(...) / failUnlessEqual (primero, segundo, [mensaje])
assertNotEqual / failIfEqual
assertAlmostEqual / failUnlessAlmostEqual (primo, secondo, [numero de decimales relevantes, [mensaje]]) - mi favorita XD
assertNotAl... os lo imaginais
assertRaises / failUnlessRaises(exception, callable, ...) - callable no es "que se calla", sino que significa que garantizas que se levanta una exception al llamar a la función "callable" con los parámetros que vienen detrás. 
failIf([msg]) - inversa de failUnless
fail([msg]) - cascar

Otros útiles:

failureException  - atributo de clase (este *NO* lleva parámetros) que devuelve la excepción que levantó test(). Por defecto es AssertionError. 
countTestCases() - imaginad. 
defaultTestReturn() - instancia de TestResult, que es un "objeto resultado". Quien quiera saber para qué vale, a leer la documentación. 
id() - string especificando el testCase actual
shortDescription() - duh.
  • Para acceder a TestResult (que se construyen solos): el objeto tiene estos atributos.
errors: lista de tuplas de 2 (duplas?): Instancias de TestCase, unidas a strings con las trazas (tracebacks), que dieron error. 
failures: Mismo pero para fallos
testsRun: nº de tests ejecutados. 
wasSuccesful()
stop() -> settear shouldStop a True. Por ejemplo, con un control C es lo que se hace en la implementación de TextTestRunner.

De lo que deducimos que TextTestRunner puede ser muy útil para backend, pero igual no tanto para gui…

Referencia

http://docs.python.org/lib/minimal-example.html

Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-ShareAlike 3.0 License