Tutoriales
Table of Contents

Apoyo al desarrollo

Bazaar

Tutorial del sistema de control de versiones que utilicemos. Un must.

Launchpad.

Tutorial del hosting que usaremos para llevar el código y el control de bugs. Es un tanto extraño…

doxygen

Manejo de generador de documentación Doxygen. No debe haber código sin ello.

sqlite3

Tutorial para el manejo de la librería de base de datos que vamos a utilizar

pylint

Tutorial para el uso de pylint, un comprobador automático de errores típicos de calidad en el código.


Lenguajes de programación

Python con clases

Ejemplo de programación en Python.

Python e inotify

Ejemplo de programación en Python.


Lista completa

Aquí incluimos todos los tutoriales a fin de poder incluir *solamente* esta página en printable. Por tanto, se ruega al respetable que si añade un tutorial no se olvide de incluirlo aquí…


Bazaar

Table of Contents

revisar rutas!! Convendría probar uno a uno (sigh) para no volver locos a la gente

Siento extenderme un poco más en esto, pero quiero meter lo más posible para que nos sirva como referencia. Intentaré ir de aplicable-ya a podemos-usarlo-algún-día.

Antes de nada, registraros en http://launchpad.net y http://launchpad.net/~hdlorean después; si no hay cosas que no os dejará hacer…

También necesitáis instalar bazaar. Con ubuntu: apt-get install bzr. Cuidado!; no es "bazaar" sino "bzr"; bzr es un fork también conocido como bazaar-ng del bazaar original, que a su vez es un fork de gnu arch.

También podéis probar el GUI.

Unos conceptos básicos: TODO


Configuración inicial

 bzr whoami "Pedro Páramo <email@example.com>"
Así los commits tienen nombre.

Usar un repositorio LOCAL

Hacer tu propio branch, ver propuesta modelo desarrollo. Vale para cualquier cosa que queráis tener. No es obligatorio.

1. Os metéis en el directorio que queráis tener versionado.

2. Arrancar el repositorio (internamente: crea una carpeta oculta dentro).

 bzr init

3. Añadir todo lo que encuentra en "." .

 bzr add .

Alternativamente: meter uno a uno, o con patrones (*.cpp *.h etc).
POR FAVOR: No metáis temporales. Ver Ignorar archivos.

4. Ver qué estás haciendo, qué hay añadido / cambiado / etc.

 bzr status

5. ¡APLICAR CAMBIOS!: Añadir esta versión al repo.

 bzr commit -m "mensaje"

Si no pones el mensaje te abre un editor. La primera línea debe ser descriptiva; el resto es en detalle.

Congratz!!! El código ya está bajo versiones. Podéis ver Comandos útiles ahora para saber qué más se puede hacer.

Subir tu código a launchpad

Para copia de seguridad, tener varios equipos, etc.

 bzr push bzr+ssh://<me>@bazaar.launchpad.net/~<me>/hdlorean/mine

Sube como tu usuario (<me>@…) al espacio tuyo (~<me>) una rama del proyecto. No estoy seguro si mine puede cambiarse por "mi nombre de branch". de hecho mine *es* el nombre de branch que le quiera dar yo.
TODO sincronizar portátil / sobremesa

Se parece a mezclar branches con tu grupo de trabajo: Primero lo subes a internete con bzr push, luego lo mezclas cambios de uno y otro con bzr merge, o alternativamente te bajas directamente en el que no tiene los nuevos con bzr pull.


Trabajar contra un repositorio compartido

Ver propuesta modelo desarrollo. Hay dos posibilidades, según tengas un "branch" o un "checkout". Con el primero haces commits locales sin internet; con el segundo no.

De fácil a difícil:

Con checkout (como svn)
Descargar
 bzr checkout http://launchpad.net/~nombre-equipo/hdlorean/nombre-branch carpeta-destino
Modificar.

A partir de aquí, todo es igual que para un local repositorio local (bzr add, bzr commit, etc). Pero para hacer commit necesitas estar conectado a internet y te pedirá user / password.

Si al hacer commit alguien ha tocado en el repositorio ya, te obligará a bajarte los últimos cambios con update (y si hay conflictos te obliga a resolverlos) antes de subirlos.

CUIDADO! Si el checkout es del http, no debería dejarte subir nada salvo con bzr+ssh que va autenticado!!! Y no hemos especificado a dónde subirlo…

Invito a todo alma indómita que se meta a probar que esto funciona, y explicarme exactamente cómo. YO NO HE PROBADO ESTE MÉTODO AÚN!!!

Con branch

Lo que aquí cuento es aplicable a mezclar tu branch con el del equipo, y gestionar conflictos.
Hay dos formas de hacerlo: una, mezclar directamente sobre tu trabajo (parecido a svn update). La otra es bajarte el código del equipo en un branch y hacer un branch "de trabajo" de ese branch, para luego mezclarlos.

Descargar
 bzr branch http://launchpad.net/nombre-equipo/hdlorean/branch-concreto

Lo de carpeta destino funciona por defecto. Alternativamente con bzr+ssh (más eficiente).
Desde este momento tienes un branch local que va asociado a dónde te lo has bajado.
Modificar

Igual que en local. Importante terminar con el commit, si no no has hecho nada!!

Bajarse cambios
 bzr pull

Solo funciona si no hay cambios locales (cuidado, esto no es svn update, el merge no es automático). Por ejemplo, si al acabar de currar has subido con push, el siguiente paso puede ser un pull.
Mezclar branches (se hace en LOCAL siempre)

Pasar cambios de un branch a otro que tienes en el disco duro. Si un branch va en internet creo que se baja los últimos cambios, intenta mezclar con los tuyos y solo te avisa en los conflictos él solito.

bzr merge ruta-al-destino 
bzr commit -m "mezclados branch sobre el que trabajaba en local y branch destino"

Atención al merge: ruta-al-destino es de dónde quieres coger los cambios. Por ejemplo:

  • Si tú tienes un origen (digamos un checkout de lo del equipo) y haces un branch para trabajar tú, el merge tendrías que hacerlo en el origen y contra el branch (cd origen; bzr merge ../branch-del-origen); al menos a mí me está funcionando así.
  • Si, sin embargo, estás trabajando contra el branch del equipo, los cambios los cogerías de ahí y por tanto tu ruta al destino sería bzr+ssh://<me>@bazaar.launchpad.net/~nombre-equipo/hdlorean/branch-concreto

Es el comando delicado así que hay que tener un poco de ojo hasta que nos aclaremos; recomiendo tener una copia de con lo que se trastee antes por si acaso…

Ejemplo práctico

Yo estoy trabajando con un compañero en una asignatura, y tengo un servidor en bzr+ssh://direccion/repo/asignatura . Desafortunadamente mi compañero usa eclipse 3.2 y yo tengo instalado ya el 3.3, así que si ponemos todo el workspace bajo control de versiones nos pisaríamos (ya que los proyectos de CDT 4 no son compatibles con los de 3.2; al abrir un proyecto antiguo lo actualiza). Sin embargo nos interesa tener el workspace con control de versiones porque así los pull se bajan todas las carpetas de las prácticas y no hay que ir una a una.

¿Solución? Una sería excluir cuando yo trabaje la configuración de los proyectos… O dejarme de eclipses nuevos y leches :). Pero hay otras.

Por ejemplo: hago el proyecto inicial en 3.2; creo el workspace, lo pongo bajo control de versiones, y lo subo con bzr push. Pero luego me creo un branch de ese workspace: bzr branch old-ws nuevo-ws. Ahora tengo una versión que "comparto" con mi compañero, old-ws, y una mía donde trabajo, nuevo-ws.

Mis commits van a mi versión local, y cuando quiero pasarle los cambios me voy a old-ws y hago bzr merge ../nuevo-ws. El resultado:

 M  pr1/.project
 M  pr1/.settings/org.eclipse.cdt.core.prefs
 M  pr1/Debug/makefile
 M  pr1/Debug/objects.mk
 M  pr1/Debug/sources.mk
 M  pr1/src/escena.cpp
 M  pr1/src/escena.h
 M  pr1/src/skel.cpp
All changes applied successfully.

Los cambios están en el workspace antiguo y podría subirlos con bzr push directamente. Pero ojo! está machacando el .proyect y las preferencias de cdt porque detecta que son más nuevos; volvemos al problema original!

No obstante puedo hacer: bzr revert pr1/.project y bzr revert pr1/.settings/org.eclipse.cdt.core.prefs, y ya solo subiría mis cambios en el código y no en los archivos de configuración del proyecto…

Siempre puedo ver qué está pasando con bzr status en cada repo.

En el antiguo:

bzr status
modified:
  pr1/Debug/makefile
  pr1/Debug/objects.mk
  pr1/Debug/sources.mk
  pr1/src/escena.cpp
  pr1/src/escena.h
  pr1/src/skel.cpp
pending merges:
  Jisakiel 2007-11-04 funcionando anidar rectángulos
    Jisakiel 2007-11-04 tocando lo de los rectángulos, por alguna razón no m...

Y en el nuevo, después de commitear en el antiguo y mergearme los cambios:

bzr status
unknown:
  pr1/.cdtbuild_initial
  pr1/.cproject
  pr1/.project_initial
pending merges:
  Jisakiel 2007-11-04 problema del bzignore apañado
  Jisakiel 2007-11-04 problema del bzignore apañado

Los .project de ambos son diferentes no obstante!.

estoy trabajando sobre este ejemplo de uso, ya contaré novedades y aclararé mis ideas según avance

Subir cambios
bzr push bzr+ssh://<me>@bazaar.launchpad.net/~nombre-equipo/hdlorean/branch-concreto

Con el comando estás subiendo como usuario <me> al espacio del equipo. Si hay conflictos, al igual que pull, te avisa diciéndote que las versiones han divergido, que uses merge.

Comandos útiles

Ver qué ha cambiado en un archivo
 bzr diff
Ver historia de revisiones (lista de mensajes y cambios)
 bzr log
Deshacer un cambio
 bzr revert
Borrar un archivo

OJO! queda versionado, nada de borrar temporales así!

 bzr remove

Si os coláis y añadís un temporal, para que no se versione:

 bzr uncommit

OJITO CON ESTO!! Deshace la última revisión. Usad primero —dry-run !!!
Ver conflictos
 bzr conflicts

Útil para ver por qué no tira merge.
Marcar conflictos como "resueltos"
 bzr resolve

Ignorar archivos:
Lista de archivos a ignorar.
 .bzignore

Útil para que no haya que excluirlos a mano cuando eliges qué versionas. Así no ocupamos espacio ni ancho de banda de más (y creedme, si empiezas a dejar los .exe y .obj se come un montonazo porque se generan muchos).

Por ejemplo:

*.o
*~
*.tmp
*.py[co]

TODO: Escribir este archivo para nuestros chicos ;) Para que no haya que meter nada más de lo necesario (cuidado con las autotools que casi todo salvo configure.ac y Makefile.am se genera *solito* )
Ver qué has ignorado:
 bzr ignored

Y otra cosa útil:

bzr add .bzrignore
bzr commit -m "Add ignore patterns"

Para que el archivo de ignore también esté versionado ;).

Interfaces gráficas (GUI)

El que lo tenga en sus repos: bzr-config con sus ventanitas para configurarlo (no hace mucha falta, con hacer la inicialización…).

También está bzr-gtk. Creo que es todo lo que he contado, pero más automático (pinta iconitos para ver qué ha cambiado, puede hacer los merge con una herramienta de diff…).

Incluso hay un plugin para eclipse; creo que falla con meter nombres de usuario y demás así que hay que tener la autenticación "automática", lo que es harina de otro percal:

  1. Clave pública subida al launchpad
  2. ssh-agent corriendo
  3. "algo" para que se añada automáticamente al iniciar sesión, o al menos que pida contraseña (investigando, ayúdenme! XD). Por esto digo que es más lioso, si no sería adecuadísimo.

Ambos estoy probándolos aún.

Clave SSH - Necesario para subir Código

Mirad en el tutorial de launchpad, ya que es requisito del launchpad más que del propio bazaar.

Fuentes

Para el que quiera mirar por su cuenta:

Página oficial, wikipedia. Recomiendo empezar por Bazaar en 5 minutos y Workflows para enterarse de qué va esto.

También hay bastante en launchpad sobre su hosting de código, directamente aplicable a la propuesta modelo desarrollo: http://help.launchpad.net/FeatureHighlights/BazaarHosting y siguientes (2, 3 )

Comentarios:


Launchpad.

Índice

Table of Contents

Introducción

En este tutorial integro la propuesta-modelo-desarrollo con el tutorial de bazaar, y explico cómo usaremos el launchpad en concreto.

Launchpad

Launchpad es un hosting de código tipo sourceforge que integra la gestión de proyectos y facilita la colaboración entre distintos proyectos de software libre. Integra bugs, ramas de código, preguntas a los desarrolladores e incluso traducciones y servicio de build para que te construyan y preparen los paquetes de código automáticamente.

A efectos prácticos, nos servirá para tener el código ahí, poder bajarlo y subirlo sin preocuparnos críticamente de hacer copias de seguridad (me fío de quienes alojan ubuntu, me parece un riesgo asumible máxime cuando hacemos branches, donde cada branch local es una copia de seguridad).


Unirse

Para unirse uno no se une al *proyecto*, sino al *grupo* . Esto es raro de narices, pero la idea es que cualquiera puede pillar nuestro código, hacerse un branch personal y eso queda entonces registrado en la lista de código del proyecto. Así puedes colaborar por tu cuenta, sin necesidad de pedir permiso para escribir en los repos, y luego es ponerte de acuerdo con los desarrolladores de un proyecto para subirles el código.

De ahí que usemos equipitos submiembros de ~hdlorean para desarrollar como ~hdlorean-core y ~hdlorean-gui; como cuando subes le dices que lo suba a tu equipo, pero como parte del proyecto hdlorean, allí aparece como "branch: tal, subido por: cual", y es solo cuestión de hacer oficiales a nuestros equipos en el proyecto (la "fuente" de los branches). Cada equipo puede tener varios branches también…


Clave pública / privada

Qué es

Los sistemas de criptografía asimétrica funcionan con dos claves: una pública y otra privada. La clave pública sirve para cifrar el mensaje, y una vez cifrado, sólo puede descifrarse con la clave privada, o viceversa (cifrar con una - descifrar con la otra). La clave pública puede enviarse a cualquier persona (en este caso launchpad), mientras que la clave privada debe quedar en posesión exclusiva del usuario.

Para qué la usa Launchpad

Launchpad utiliza la clave pública para autenticarte. Si no has subido la clave ssh pública, no podrás subir código, aunque sí descargarlo. La clave se sube en https://launchpad.net/~usuario, en el menú de la izquierda aparecerá una entrada para subir la clave SSH. Una vez dentro, aparecerá un recuadro, donde debéis pegar el contenido del fichero de clave pública.

¿Por qué la clave pública para autenticarte? Porque al solo tener tú la privada, cualquier sitio que tenga tu clave pública sabe que el código que firmas ha tenido que ser firmado con tu clave privada, que se supone solo en tu posesión.

Cómo se genera

Abrís una terminal y escribís:

ssh-keygen -t rsa

Os preguntará por el sitio donde guardar la pareja de claves. Si le dáis a enter, las guardará por defecto en /home/usuario/.ssh/. Si tenéis preferencia por otra ruta se le puede especificar. Preguntará después por una passphrase, esto es, una contraseña asociada a tu pareja de claves. Se puede dejar el campo en blanco, pero es un riesgo alto de seguridad, ya que cualquier persona que se incaute de vuestra clave privada podría suplantaros (de otro modo, para usar la privada siempre te pide la passphrase, así que aunque la cogiese alguien no hay problemas si no conocen esta passphrase).
Una vez introducida la passphrase, tendréis en la ruta que hayáis especificado (o en /home/usuario/.ssh si lo dejasteis en default) un fichero llamado id_rsa.pub. El contenido de este fichero es el que tenéis que pegar en la casilla de launchpad.

nota de Eze: también funcionan claves de tipo DSA.


Equipos concretos

[hdlorean-gui]

GUI

[hdlorean-core]

Núcleo de la aplicación. Al integrar db y cambios es cuando estamos definiendo la mayor parte del api. Solo tenemos permiso de escritura adri, salva, mario (como control de calidad) y eze (yo).

[hdlorean-db] Base de datos.

Gestión de las bases de datos del programa.

[hdlorean-watcher] Gestión de cambios

Gestión de cambios recibidos por inotify, journal, etc.

[hdlorean-storage]

Almacenamiento de los datos: xdelta, rsnapshot, etc.

Resumiendo:

En el proyecto quedaría, si esto funciona como debe:

hdlorean/trunk

La integración. Subida al espacio de equipo ~hdlorean . La rama con todo. Se sube a:
bzr+ssh://<usuario>@bazaar.launchpad.net/~hdlorean/hdlorean/nombre-branch (supongo que "en blanco" significa trunk)

hdlorean-gui/gui

GUI. Es un branch de desarrollo del gui subido por el equipo ~hdlorean-gui.
bzr+ssh://<usuario>@bazaar.launchpad.net/~hdlorean-gui/hdlorean/gui (branch de nombre gui)

hdlorean-core/core

Core o backend de la aplicación. Subido e integrado por ~hdlorean-core. OJO CON SUBIR NADA AQUI!!!
bzr+ssh://<usuario>@bazaar.launchpad.net/~hdlorean-core/hdlorean/core *Alternativamente "en blanco" también

hdlorean-db/db

Branch de desarrollo de la base de datos. Separado de hdlorean-core, hay que integrar.
bzr+ssh://<usuario>@bazaar.launchpad.net/~hdlorean-db/hdlorean/db
(db es el nombre del branch -por ejemplo-, hdlorean-db el del equipo)

hdlorean-watcher/watcher

Gestión de cambios (watcher, planificador, etc). También separado
bzr+ssh://<usuario>@bazaar.launchpad.net/~hdlorean-watcher/hdlorean/watcher
(watcher es el nombre del branch -por ejemplo-, hdlorean-watcher el del equipo)

hdlorean-storage/storage

Almacenamiento. Separado de core.
bzr+ssh://<usuario>@bazaar.launchpad.net/~hdlorean-storage/hdlorean/storage
(storage es el nombre del branch -por ejemplo-, hdlorean-storage el del equipo)


Para todos los grupos

No creéis branches desde la página del proyecto!!! Así solo creáis branches personales, y no podéis dar permiso de escritura a nadie!!!
En launchpad todos debemos estar en:

Fuentes

Para ir mentalizándose: propuesta modelo desarrollo.

Como decía en el tuto de bazaar: hay bastante en launchpad sobre su hosting de código: http://help.launchpad.net/FeatureHighlights/BazaarHosting y siguientes (2, 3 )

Iremos ampliando según nos aclaremos…


Comentarios:



doxygen

Table of Contents

Introducción

Doxygen ofrece muchas más funcionalidades y modos de los aquí resumidos, no obstante estos son los más cómodos. Existen comandos especiales adicionales, otras formas de escribir los comentarios, distintos modos de agrupación… No obstante, se ha elegido lo más útil y sencillo. Sirva este tutorial también de estándar del modelo de documentación en código que usaremos. El tutorial de cómo generar la documentación en sus diferentes formatos se hará aparte, puesto que no interesa a todos.

Descripciones

Para cada elemento del código existen tres tipos de descripciones:

  1. Descripción Breve
  2. Descripción Detallada
  3. Descripción del cuerpo (sólo para funciones). Se trata de la concatenación de todos los comentarios existentes dentro de la función.

Es importante que todos los elementos relevantes en el código (miembros de clases, funciones, estructuras, atributos, variables significativas…) tengan tanto descripción breve como detallada, porque la breve se muestra bajo el nombre del objeto en la documentación generada, y sólo se ve la detallada una vez entras a los detalles de dicho objeto. Pero ojo, la descripción breve debe ser BREVE, o se descuadrará la documentación.

1. Descripción Detallada

/**
*    Descripción detallada. Nótese el doble ** inicial. Con uno sólo es un comentario normal.
*/

2. Descripción Breve
/** @brief Descripción breve. 
*     Termina al final del párrafo.
*
*    Esta parte ya es descripción detallada.
*/

Si los comentarios se van a situar tras el objeto a comentar, en lugar de antes -NADA RECOMENDABLE- el comentario debe incluir el símbolo “<” para indicar a doxygen que el comentario hace referencia al atributo anterior.

Uso de HTML

Doxygen soporta la inclusión de HTML en los bloques especiales de comentarios (los /**). Son útiles para formatear las descripciones, resaltar contenidos o realizar listas. Ojo, no todos los comandos HTML están soportados, si os váis a poner finos mirad despacio los que sí aquí: http://www.stack.nl/~dimitri/doxygen/htmlcmds.html
Particularmente interesantes son:

<b>esto es negrita</b>
<i>esto es cursiva</i>
<code>Letra de imprenta</code>
<hr> <- Escribe una línea horizontal
<ol></ol> -> Inicia/cierra una lista ordenada (numerada)
<ul></ul> -> Inicia/cierra una lista no ordenada
<li>esto es un elemento de una lista, va dentro de “<ol>” o “<ul>”. </li>
<table></table> -> Inicia/cierra una tabla
<tr></tr> -> Inicia/cierra una fila dentro de una tabla
<td></td> -> Inicia/cierra una columna dentro de una tabla
&?acute -> sustituyendo ? por una vocal, obtienes una vocal acentuada.

Además, para poner comentarios invisibles dentro de los bloques de comentarios, se puede usar el estilo de HTML:

/** <!-- Este texto no se verá en la documentación final, pero puede ayudar 
    a los programadores. -->     Esta parte sí se verá. 
*/

Comandos de Descripción de Elementos

  • @param[in] nombre_param descripción_param -> Hay que poner un param por parámetro recibido. Se puede indicar [in], [out] o [in,out].
  • @return descripción
  • @see -> Un see para cada otro miembro que se referencie
  • @deprecated -> Indica que esta entidad está en desuso.
  • @pre -> Indica la precondición de una entidad
  • @post -> Indica la postcondición de una entidad
  • @since fecha -> Indica desde cuándo (qué versión o fecha) está disponible algo.
  • @throw excepción descripción
  • @bug descripción
  • @file descripción -> Documenta el fichero con una breve descripción.

Ejemplo 1
/**
 *  A test class. A more elaborate class description.
 */

class Test
{
  public:

    /** 
     * An enum.
     * More detailed enum description.
     */

    enum TEnum { 
          TVal1, /**< enum value TVal1. */  
          TVal2, /**< enum value TVal2. */  
          TVal3  /**< enum value TVal3. */  
         } 
       *enumPtr, /**< enum pointer. Details. */
       enumVar;  /**< enum variable. Details. */

      /**
       * A constructor.
       * A more elaborate description of the constructor.
       */
      Test();

      /**
       * A destructor.
       * A more elaborate description of the destructor.
       */
     ~Test();

      /**
       * a normal member taking two arguments and returning an integer value.
       * @param a an integer argument.
       * @param s a constant character pointer.
       * @see Test()
       * @see ~Test()
       * @see testMeToo()
       * @see publicVar()
       * @return The test results
       */
       int testMe(int a,const char *s);

      /**
       * A pure virtual member.
       * @see testMe()
       * @param c1 the first argument.
       * @param c2 the second argument.
       */
       virtual void testMeToo(char c1,char c2) = 0;

      /** 
       * a public variable.
       * Details.
       */
       int publicVar;

      /**
       * a function variable.
       * Details.
       */
       int (*handler)(int a,int b);
};

Agrupaciones

Por último, Doxygen incluye varias formas de agrupación. Nos quedaremos con la más útil, agrupación por módulos. Se pueden agrupar diferentes atributos,variables, typedefs… en un grupo con algún contenido o semántica común, de tal forma que la documentación generada los ponga en un apartado especial. Por ejemplo, puedes crear un grupo “Watcher” donde meter todas las funciones relacionadas con la vigilancia de cambios.

  1. Definir un grupo: @defgroup id_unico Nombre_para_la_doc
  2. Añadir a un grupo: @ingroup id_de_grupo

El id del grupo puede ser un nombre cualquiera, siempre que no existan dos grupos con el mismo nombre. Para no escribir repetidamente @ingroup, si los elementos a añadir se encuentran seguidos, se pueden usar @{ y }@ para indicar el comienzo y el final de un grupo.

Ejemplo 2
/**
 * @ingroup A
 */
extern int VarInA;

/**
 * @defgroup IntVariables Global integer variables
 */
/*@{*/

/** an integer variable */
extern int IntegerVariable;

/*@}*/

....

/**
 * @defgroup Variables Global variables
 */
/*@{*/

/** a variable in group A */
int VarInA;

int IntegerVariable;

/*@}*/

Apéndice A: Python

La generación de documentación en python es idéntica, salvo que los bloques especiales de comentarios deben comenzar con ## en lugar de /**. Si se utiliza el estilo de python para los comentarios de los bloques especiales, es decir, si se usa “””, los comandos especiales (@author, @return…) no están soportados por Doxygen, por lo que es recomendable usar ##.

Ejemplo 3 - Python
## Documentation for this module.
#
#  More details.

## Documentation for a function.
#
#  More details.
def func():
    pass

## Documentation for a class.
#
#  More details.
class PyClass:

    ## The constructor.
    def __init__(self):
        self._memVar = 0;

    ## Documentation for a method.
    #  @param self The object pointer.
    def PyMethod(self):
        pass

    ## A class variable.
    classVar = 0;

    ## @var _memVar
    #  a member variable

Información Adicional

En la web de doxygen existe mucha más información: http://www.stack.nl/~dimitri/doxygen/manual.html


sqlite3

Table of Contents

Sqlite3 instalación.

Para la parte de bases de datos del proyecto usaremos la librería sqlite3, que os tendréis que instalar. Yo lo hice mediante el gestor de paquetes synaptic.

Utilizando sqlite3.

Ahora pongo unos ejemplos de como realizar las operaciones básicas de las bases de datos mediante instrucciones de sqlite3:

Crear/Conectar una base de datos.

La función sqlite3_open (en la versión que vamos a utilizar) recibe dos parámetros: el nombre de la base de datos a abrir y un objeto tipo sqlite3*. En el caso de que la base de datos no exista la función open la creará y establecerá la conexión.

Para la detección de errores utilizaremos sqlite3_errcode que se encarga de recuperar el ultimo error producido en la base de datos, si el número devuelto no es 0 entonces ha habido un error, y para enviar un mensaje con el error producido usaremos sqlite3_errmsg que extrae el mensaje de error producido por la ultima operación realizada sobre la base de datos. Estas dos funciones reciben como parámetro el objeto sqlite3*.

Cuando se termine de usar la base de datos usaremos sqlite3_close que recibe la variable de tipo sqlite3* en la que se creó la conexión.

#include <cstdio>
#include "sqlite3.h"
 
using namespace std; //para printf en _c_stdio, esto es c++
 
int main(int argc, char* argv[]) {
    //Creamos una variable
    sqlite3* db;        
    //Establecemos la conexión
    sqlite3_open("basepruebas.db",&db); 
    //Comprobamos errores
    if (sqlite3_errcode(db)!=0){
        printf("%s\n",sqlite3_errmsg(db));        
    }    
    //Cerramos la conexión
    sqlite3_close(db); 
    return 0;        
};
Crear una tabla.

Recordad que siempre que se vaya a usar una la base de datos se tendrá que establecer la conexión con ella y cuando se termine cerrarla como se ha explicado antes.

Para ejecutar cualquier sentencia sql se usará sqlite3_exec que recibe 5 parámetros:
1. Conexión con la base de datos(variable sqlite*).
2. Sentencia o sentencias sql que queremos ejecutar. Yo recomiendo usa solo una sentencia en cada llamada ya que eso facilitara la lectura del código y nos resultará mas fácil.
3. Función que se encargará de tratar los resultados de la sentencia sql del argumento 2. Esta función la definiremos nosotros y debe recibir:

  1. Puntero void*
  2. Entero que indica el numero de columnas en el resultado.
  3. Array de strings (en el ejemplo char**) que contiene los valores de cada columna.
  4. Array de strings que contiene los nombres de la columnas.

Además esta función deber devolver 0 si no ha habido errores.
4. Puntero que se le pasa a la función del argumento 2 como primer parámetro.
5. Array de strings que guardara el error producido en caso de que se produzca. Nosotros , por ahora pondremos esto a NULL ya que la detección de errores la haremos como se ha explicado antes.

#include <cstdio>
#include "sqlite3.h"
 
using namespace std; //para printf en _c_stdio, esto es c++
 
int main(int argc, char* argv[]) {
    //creamos una variable del tipo de bases de datos.
    sqlite3* db;    
    //establecemos conexión    
    sqlite3_open("basepruebas.db",&db);
    //comprobamos errores 
    if (sqlite3_errcode(db)!=0){ 
        printf("%s\n",sqlite3_errmsg(db));            
    }
    else{
        sqlite3_exec(db,"create table hdlorean(dni integer not null,"
        "nombre varchar(30) default ' ' not null,equipo varchar(30) default ' ' not null,"
        //como esta sentencia no generara resultados el tercer argumento lo ponemos a null.
        "primary key (dni));",NULL,NULL,NULL);
        //comprobamos errores.
        if (sqlite3_errcode(db)!=0){ 
            printf("%s\n",sqlite3_errmsg(db));            
        }
    }    
    //cerramos conexión
    sqlite3_close(db); 
    return 0;        
};
Insertar datos

Como en el caso anterior, la sentencia sql no devuelve un resultado por lo que el tercer parámetro de sqlite3_exec sigue siendo NULL.

#include <cstdio>
#include "sqlite3.h"
 
using namespace std; //para printf en _c_stdio, esto es c++
 
int main(int argc, char* argv[]) {
    //creamos una variable del tipo de bases de datos.
    sqlite3* db;    
    //establecemos conexión    
    sqlite3_open("basepruebas.db",&db);
    //comprobamos errores 
    if (sqlite3_errcode(db)!=0){ 
        printf("%s\n",sqlite3_errmsg(db));            
    }
    else{
        sqlite3_exec(db,"insert into hdlorean values (1,'XX','db');",NULL,NULL,NULL);
        //comprobamos errores.
        if (sqlite3_errcode(db)!=0){ 
            printf("%s\n",sqlite3_errmsg(db));            
        }
        else{
            sqlite3_exec(db,"insert into hdlorean values (2,XX','db);",NULL,NULL,NULL);
            //comprobamos errores.
            if (sqlite3_errcode(db)!=0){ 
                //falta una ' en la sentencia sql!
                printf("%s\n",sqlite3_errmsg(db));            
 
            }
        }
    }    
    //cerramos conexión
    sqlite3_close(db); 
    return 0;        
};
Actualizar/Eliminar datos.

Exactamente igual para actualizar datos.

#include <cstdio>
#include "sqlite3.h"
 
using namespace std; //para printf en _c_stdio, esto es c++
 
int main(int argc, char* argv[]) {
    //creamos una variable del tipo de bases de datos.
    sqlite3* db;    
    //establecemos conexión    
    sqlite3_open("basepruebas.db",&db);
    //comprobamos errores 
    if (sqlite3_errcode(db)!=0){ 
        printf("%s\n",sqlite3_errmsg(db));            
    }
    else{
        sqlite3_exec(db,"update hdlorean set nombre='Josue' where dni=1;",NULL,NULL,NULL);
        //comprobamos errores.
        if (sqlite3_errcode(db)!=0){ 
            printf("%s\n",sqlite3_errmsg(db));            
        }
        else{
            sqlite3_exec(db,"delete from hdlorean where nombre='Josue';",NULL,NULL,NULL);
            if (sqlite3_errcode(db)!=0){                 
                printf("%s\n",sqlite3_errmsg(db));            
            }
        }        
    }    
    //cerramos conexión
    sqlite3_close(db); 
    return 0;        
};
Ejecutar una select.

Como tras ejecutar estos ejemplos la base de datos se no has quedado vacía vamos a insertar varias filas para realizar varias selects y ver como funciona sqlite3_exec.

#include <cstdio>
#include "sqlite3.h"
 
using namespace std; //para printf en _c_stdio, esto es c++
 
int imprime_resultado(void* paque,int n, char** re,char** nomcol){
    //El primer argumento de esta funcion es el 4 de la exec
    //n es el numero de filas devueltas por la query
    //re el contenido de la fila
    //nomcol el nombre de la columna    
    printf("El %s del componente es: %s\n",nomcol[0],re[0]);
    return 0;
}
 
int imprime_resultado2(void* paque,int n, char** re,char** nomcol){
    printf("El DNI y el nombre del componente: %s, %s\n",re[0],re[1]);
    return 0;
}
 
int main(int argc, char* argv[]) {
    //creamos una variable del tipo de bases de datos.
    sqlite3* db;    
    //establecemos conexión    
    sqlite3_open("basepruebas.db",&db);
    //comprobamos errores 
    if (sqlite3_errcode(db)!=0){ 
        printf("%s\n",sqlite3_errmsg(db));            
    }
    else{
        //introducimos varias filas
        sqlite3_exec(db,"insert into hdlorean values (1,'XX','db');",NULL,NULL,NULL);
        //comprobamos errores.
        if (sqlite3_errcode(db)!=0){ 
            printf("%s\n",sqlite3_errmsg(db));            
        }    
        sqlite3_exec(db,"insert into hdlorean values (2,'ZZ','db');",NULL,NULL,NULL);
        if (sqlite3_errcode(db)!=0){ 
            printf("%s\n",sqlite3_errmsg(db));            
        }
        sqlite3_exec(db,"insert into hdlorean values (3,'YY','core');",NULL,NULL,NULL);
        if (sqlite3_errcode(db)!=0){ 
            printf("%s\n",sqlite3_errmsg(db));            
        }
        sqlite3_exec(db,"insert into hdlorean values (4,'WW','core');",NULL,NULL,NULL);
        if (sqlite3_errcode(db)!=0){ 
            printf("%s\n",sqlite3_errmsg(db));            
        }
        //realizamos una select que solo devolvera un resultado.    
        sqlite3_exec(db,"select nombre from hdlorean where dni=1;",imprime_resultado,NULL,NULL);
        if (sqlite3_errcode(db)!=0){ 
            printf("%s\n",sqlite3_errmsg(db));            
        }
        //realizamos una select que devolvera varios resultados.
        sqlite3_exec(db,"select nombre from hdlorean where equipo='core';",imprime_resultado,NULL,NULL);
        if (sqlite3_errcode(db)!=0){ 
            printf("%s\n",sqlite3_errmsg(db));            
        }
        //realizamos una select que devuelve varias columnas y varios resultados
        sqlite3_exec(db,"select dni,nombre from hdlorean where equipo='db';",imprime_resultado2,NULL,NULL);
        if (sqlite3_errcode(db)!=0){ 
            printf("%s\n",sqlite3_errmsg(db));            
        }
    }    
    //cerramos conexión
    sqlite3_close(db); 
    return 0;        
};

Como podéis ver la función del tercer argumento de sqlite3_exec (cuanado la sentencia sql es una select) se repite una vez por cada fila devuelta por la sentecia.

Comentarios:

Instalación: Si alguien quiere explicar esto mejor que lo haga :P Yo no tengo mucha idea de como hacerlo exactamente.

Por ahora terminado.


Python con clases

Esto es una pequeña muestra de Python con clases. Podéis ejecutarla con:

python nombre-archivo.py

Os recuerdo que es importante mantener la indentación del texto puesto que en ese lenguaje no hay llaves.

Contenido de 'test.py'

#!/usr/bin/env python
# encoding: utf-8
 
# Esta clase representa los números imaginarios
class Imaginario:   
    # Esta cadena suelta es parte de la documentación accesible mediante la variable __doc__
    "Una clase de ejemplo que representa números complejos"
    real = 0.0
    imaginaria = 0.0
 
    # Constructora
    def __init__(self,r,i):
        self.real = r
        self.imaginaria = i
 
    # Muestra la representacion del número
    def mostrar(self):
        if(self.real != 0):
            # Tiene en cuenta el signo           
            if(self.imaginaria < 0):
                print str(self.real) + str(self.imaginaria) + "i"
            else:
                print str(self.real) + "+" + str(self.imaginaria) + "i"
        else:
            print str(self.imaginaria) + "i"
 
# Aqui comienza el programa, no es necesario indicar un punto de entrada
print "-----------------------------------------------------------"
print "Hola mundo, ¡vamos a trabajar con números complejos!"
i = Imaginario(0,1)
ii = Imaginario(1.7,-3);
print "He aqui el número complejo por excelencia:"
i.mostrar()
print "Y por aqui otro totalmente distinto:"
ii.mostrar()
print "Voy a modificar este último para que su parte imaginaria sea positiva:"
ii.imaginaria = -ii.imaginaria
ii.mostrar()
print "Vamos a echarle un ojo a la documentación:"
print Imaginario.__doc__
print "-----------------------------------------------------------"

Python e inotify

Table of Contents

Versión python

Estas son las pruebas con Pyinotify. Os tenéis que descargar el paquete primero.

He elaborado el tutorial a partir de aquí
http://pyinotify.sourceforge.net/

La creación de archivos la monitorea perfectamente, la eliminación siempre que se realice mediante el comando rm o shift+supr (es decir, se haga algo más que mover a la papelera).
Sin embargo, el cambio de atributos no me lo monitorea puesto que lo modifico y debería cambiar la fecha de última modificación. Además, me dice que han sido modificados archivos extraños cuando intento modificar un archivo desde el editor de texto.

Si le podéis echar un vistazo.

Recordad una vez más lo de la indentación.

Os pongo una versión con threads y otra sin threads.

Versión sin threads

Contenido de 'inotify-test.py'

#!/usr/bin/env python
# encoding: utf-8
 
# Importa las librerías adecuada
import os
from pyinotify import WatchManager, Notifier, ThreadedNotifier, EventsCodes, ProcessEvent
 
# PON TU NOMBRE DE USUARIO DENTRO DEL HOME
username = "salvador"
 
# El objeto que monitorea
wm = WatchManager()
 
# Qué queremos monitorear
mask = EventsCodes.IN_DELETE | EventsCodes.IN_CREATE | EventsCodes.IN_MODIFY | EventsCodes.IN_ATTRIB
 
# Esto significa "PHome que hereda de ProcessEvent"
class PHome(ProcessEvent):
    def process_IN_CREATE(self, event):
        print "Create: %s" % os.path.join(event.path, event.name)
 
    def process_IN_DELETE(self, event):
        print "Remove: %s" % os.path.join(event.path, event.name)
 
    def process_IN_MODIFY(self, event):
    print "Modify: %s" % os.path.join(event.path, event.name)
 
    def process_IN_ATTRIB(self, event):
    print "Attribute modified: %s" % os.path.join(event.path, event.name)
 
# El notificador
notifier = Notifier(wm, PHome())
 
# Esto asigna un "vigilante" al directorio /home/nombre-usuario indicando recursividad
wdd = wm.add_watch('/home/' + username, mask, rec=False)
 
# Bucle infinito, versión sin threads
while True:
    try:
        # Procesa los eventos
        notifier.process_events()
        if notifier.check_events():
            # Lee los eventos y los introduce en una cola
            notifier.read_events()
 
        # Aquí se puede añadir código
 
    # Interrupción por teclado
    except KeyboardInterrupt:
        # Para de monitorear
        notifier.stop()
    #Sale del bucle infinito       
    break

Versión con threads

Contenido de 'inotify-threaded-test.py'

#!/usr/bin/env python
# encoding: utf-8
 
# Importa las librerías adecuada
import os
from pyinotify import WatchManager, Notifier, ThreadedNotifier, EventsCodes, ProcessEvent
 
# PON TU NOMBRE DE USUARIO DENTRO DEL HOME
username = "salvador"
 
# El objeto que monitorea
wm = WatchManager()
 
# Qué queremos monitorear
mask = EventsCodes.IN_DELETE | EventsCodes.IN_CREATE | EventsCodes.IN_MODIFY | EventsCodes.IN_ATTRIB
 
# Esto significa "PHome que hereda de ProcessEvent"
class PHome(ProcessEvent):
    def process_IN_CREATE(self, event):
        print "Create: %s" % os.path.join(event.path, event.name)
 
    def process_IN_DELETE(self, event):
        print "Remove: %s" % os.path.join(event.path, event.name)
 
    def process_IN_MODIFY(self, event):
    print "Modify: %s" % os.path.join(event.path, event.name)
 
    def process_IN_ATTRIB(self, event):
    print "Attribute modified: %s" % os.path.join(event.path, event.name)
 
# El notificador, con threads
notifier = ThreadedNotifier(wm, PHome())
notifier.start() # Comienza el thread
 
# Esto asigna un "vigilante" al directorio /home/nombre-usuario indicando recursividad
wdd = wm.add_watch('/home/' + username, mask, rec=False)
 
# Bucle infinito, versión con threads
while True:
    try:
        # Aquí se puede añadir código
    pass
 
    # Interrupción por teclado
    except KeyboardInterrupt:
    #Sale del bucle infinito       
    break
 
# Para de monitorear
notifier.stop()

Versión C / C++, sin wrappers

// Initialize Inotify
fd = inotify_init ();
if (fd < 0) return -errno;
 
// Add a Watch
int wd;
wd = inotify_add_watch (fd, filename, mask);
if (wd < 0) return -errno;
 
// Read an inotify event (buffer should be at least the size of
static struc inotify_event *buffer = NULL;
buffer_size = sizeof (struct inotify_event);
*nr = read (fd, buffer, buffer_size);
 
inotify_rm_watch(wd);

Y dejo el link de inotify-cxx, unas clases para trabajar con inotify en C++. Son sencillas y cortas, y además puedes editarlas o ampliarlas :)

http://inotify.aiken.cz/doc/inotify-cxx/html/annotated.html

Eventos que monitoriza inoitfy

Tabla 1. Eventos

Evento Descripción
IN_ACCESS File was read from
IN_MODYFY File was written to
IN_ATTRIB File's metadata (inode or xattr) was changed.
IN_CLOSE_WRITE File was closed (and was open for writing).
IN_CLOSE_NO_WRITE File was closed (and was not open for writing)
IN_OPEN File was opened
IN_MOVED_FROM File was moved away from watch.
IN_MOVED_TO File was moved to watch.
IN_DELETE File was deleted.
IN_DELETE_SELF The watch itself was deleted.

Tabla 2. Eventos de ayuda

Evento Descripción
IN_CLOSE IN_CLOSE_WRITE | IN_CLOSE_NOWRITE
IN_MOVE IN_MOVED_FROM | IN_MOVED_TO
IN_ALL_EVENTS Bitwise OR of all events.

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


pylint

Table of Contents

Instalación de Pylint

Para ayudarnos a comprobar que se usa correctamente el estándar de código acordado al principio del desarrollo de la aplicación usaremos Pylint, disponible en http://pydev.sourceforge.net/pylint.html . Esta herramienta permite identificar dónde no se sigue el estándar fácilmente, y además se puede incluir en el entorno de desarrollo de manera que revise el código según se programa.

Para instalarlo basta con abrir el gestor de paquetes synaptic (sistema->Administración), buscar pylint y marcar para instalar.

Nuestro estándar

Pylint viene configurado por defecto con el estándar que usaron los desarrolladores de la herramienta, por lo que tendremos que configurar el nuestro.

Para esto vamos a la consola y escribimos pylint —generate-rcfile > standard.rc; esto nos creará un fichero de texto llamado standard.rc que en principio contendrá el estándar por defecto, sobre este fichero podremos modificar las opciones para que se ajuste a lo que deseemos.

El fichero con nuestro estándar se distribuirá, junto con la documentación del UML del proyecto y las instrucciones para generar la documentación de doxygen, en la carpeta doc de las fuentes.

Configuración de Eclipse

Para empezar tenemos que estar usando un proyecto en python 2.5, si hace falta cambiar el interprete:

  1. Window->preferences->Pydev->Interpreter - Python
  2. En python intepreters se quita lo que haya (sino es la versión 2.5) y se añade /usr/bin/python2.5 (alternativamente python2.5-dbg).

Para que eclipse pase pylint de forma automática tendremos que ir a window->preferences->Pydev->Pylint, en esta ventana:

  1. Marcamos el cuadradito Use Pylint.
  2. Le indicamos donde esta el archivo lint.py (en principio en /usr/share/pycentral/pylint/site-packages/pylint/lint.py). Si no lo encuentra, en una consola podemos buscarlo con locate lint.py.
  3. Marcamos como errores FATAL severity, ERRORS severity y CONVENTIONS severity y como warnings WARNINGS severity y REFACTOR severity. De esta forma no perderemos información y no tendremos errores inútiles.
  4. Por último en el cuadro de texto de arguments to pass escribimos —rcfile=directorio-donde-este-standard.rc

Una vez realizado esto aceptamos y hacemos un clean del projecto (Project->clean) y se pasará automaticamente pylint. Puede que tarde un poco, sobretodo con proyectos grandes.

Ejecución desde consola

Para aquellos que no usan eclipse y prefieren otros editores, pylint se puede ejecutar desde consola. Basta con que pongáis pylint —rcfile=directorio-donde-este-el-fichero-standard.rc directorio-del-fichero-a-examinar

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