eclipse y pydev
TRANSCRIPT
1
Eclipse y Pydev
Andrés Marzal, 1 y 2 de junio de 2010
Eclipse es un entorno de desarrollo diseñado originalmente para Java, pero con una arquitectura orientada a la
extensión que ha hecho de él un entorno de desarrollo adaptable a diferentes lenguajes y plataformas. Hay
extensiones de Eclipse para desarrollo con C/C++, Ruby, Python, Scala, IronPython, PHP, desarrollo de páginas web,
etcétera. Cuando Eclipse está adaptado al desarrollo con C/C++ suele llamarse Eclipse CDT; cuando está adaptado
Java, Eclipse JDT; cuando a PHP, Eclipse PDT; y cuando a Python, Pydev.
El desarrollo de Java ha estado controlado por Sun, empresa adquirida recientemente por Oracle. Sun desarrolla el
entorno NetBeans, competidor directo de Eclipse. No obstante, Eclipse es la herramienta preferida por la mayor
parte de desarrolladores Java: Eclipse es más ligero en ejecución y más extensible (o al menos cuenta con más
extensiones).
Pydev es una extensión de Eclipse para el desarrollo de aplicaciones con Python. Durante un tiempo la empresa
Aptana ofrecía una versión gratuita y otra, más completa, de pago. Desde la versión 1.5 sólo hay una versión y es
gratuita. El desarrollador principal es Fabio Zadrozny.
El objetivo de este documento es introducir al lector en el uso de Eclipse como plataforma de desarrollo, pero
usando el entorno Pydev de desarrollo Python, y no el entorno por defecto para desarrollo con Java.
Instalación de Eclipse y Pydev Para instalar Eclipse y Pydev es necesario:
Una máquina que corra Windows, Linux o Mac OS X.
300 Mb de disco.
500 Mb de memoria (cuanta más, mejor).
Java Development Kit (JDK) 1.4 o superior. Se puede descargar de http://java.sun.com/. La versión actual (y
recomendada) es JDK 6 Update 20. Esta versión se puede bajar de
http://java.sun.com/javase/downloads/widget/jdk6.jsp.
En Linux se puede instalar Eclipse con el sistema de control de paquetes propio de la versión que se esté usando,
pero lo cierto es que no es necesario instalarlo en ninguna de las plataformas: si descargamos el paquete y lo
descomprimimos como carpeta (directorio), Eclipse es ejecutable directamente siempre que java (o java.exe) estén
en la ruta (PATH) del shell.
El paquete de “instalación” de Eclipse se puede descargar directamente de la página web
http://www.eclipse.org/downloads. La última versión es la 3.5.2 (la serie 3.5 tiene nombre clave Galileo). Más que
instalación hablamos de descompresión de un directorio en el que está todo lo necesario para trabajar. Vamos a la
página http://www.eclipse.org/downloads/ y pinchamos en Eclipse Classic 3.5.2. Llegamos así a una página de
descarga que depende del operativo desde el que accedemos. Pinchamos en el enlace de descarga (flecha verde) y
esperamos la descarga de 163 megabytes.
2
El fichero eclipse-SDK-3.5.2-win32.zip contiene una carpeta llamada eclipse. Basta con extraer la carpeta (unos 178
megabytes) y abrirla para empezar a usar eclipse. En Windows, la carpeta contiene esto:
Hacemos doble-clic en el icono eclipse.exe para arrancar el programa. Al arrancar nos pide un espacio de trabajo:
Más adelante veremos qué es un espacio de trabajo. Por el momento, le decimos que sí pulsando OK. La pantalla de
Eclipse pasa a ser ésta:
3
Cada círculo es un enlace. Los 4 de más a la izquierda llevan a páginas de ayuda. El de la derecha nos lleva al banco
de trabajo (que enseguida veremos qué es). Si pinchamos este último enlace, la ventana principal pasa a tener este
aspecto:
Vamos a configurar Eclipse añadiendo Pydev, la extensión que permite adaptar la herramienta al trabajo con Python.
Antes habrá que haber instalado Python 3.1, disponible en la página web oficial de Python: http://www.python.org.
Si estamos en Windows, podemos instalarlo en el lugar “por defecto”: C:\Python31.
La extensión de Eclipse en la que estamos interesados se denomina Pydev, su página principal está en
http://www.pydev.org y el autor mantiene un blog donde informa de las novedades en http://pydev.blogspot.com.
Instalamos la extensión desde dentro del mismo Eclipse. Seleccionamos la opción HelpInstall New Software… En el
diálogo Install pulsamos el botón Add… y aparece un nuevo formulario:
En el campo Name escribimos Pydev (o lo que queramos) y en el campo Location escribimos la URL http://pydev.org/updates. Pulsamos OK y el diálogo Install pasa tener este aspecto:
4
Seleccionamos los dos elementos (PyDev y PyDev Mylin Integration) y pulsamos en el botón Next>:
Nuevamente pulsamos Next>, aceptamos la licencia de uso (es una licencia de código abierto) y pulsamos ahora
Finish. Eclipse empezará a descargar las extensiones que le hemos indicado y aquellas otras de las que dependen
éstas:
5
Eclipse puede advertir del riesgo de descargar software de la red y pedir confirmación para seguir instalando. Y
también puede que nos pida otorgar confianza a un certificado de la Eclipse Foundation Inc., cosa que haremos.
Finalmente, Eclipse nos solicitará reiniciar el programa para seguir trabajando con mayor garantía de que no habrá
problemas:
Al reiniciar volverá a preguntar si deseamos trabajar en C:\Users\usuario\workspace. Le decimos que sí. Si antes
marcamos la opción de que recuerde que ese es el espacio de trabajo por defecto, no volverá a preguntarlo la
próxima ver que iniciemos Eclipse.
Ya tenemos Eclipse listo para el resto de la sesión. Es hora de aprender algunos conceptos básicos.
Conceptos básicos Eclipse ofrece una o más ventanas en las que hay varios componentes dispuestos de un modeo determinado. Es el
banco de trabajo (workbench). Qué componentes se muestran y cómo se distribuyen depende del entorno de
desarrollo (si estamos desarrollando para Java, C/C++, Python) y de la acción que deseemos llevar a cabo (editar el
proyecto, depurarlo, etcétera). Una perspectiva (perspective) es una combinación y disposición determinada de
vistas (view) en el banco de trabajo.
Podemos elegir una perspectiva de dos modos:
Desde el menú WindowOpen PerspectiveOther… Aparecerá un diálogo modal similar a éste:
Desde la esquina superior derecha, lugar en el que podemos anclar las perspectivas que usamos
habitualmente y seleccionarlas fácilmente:
6
El botón conduce al cuadro de diálogo del punto anterior y permite seleccionar cualquier perspectiva.
Cuando abordamos un proyecto, como la construcción de una aplicación, diseñamos diferentes unidades
interdependientes: librerías, ficheros con datos, imágenes, iconos, programas ejecutables, scripts… Eclipse utiliza
una nomenclatura propia que diferencia entre cada una de esas unidades, a cuya colección de ficheros de código
fuente y recursos denominamos proyecto (project), y la colección de proyectos relacionados entre sí, a los que
denominamos espacio de trabajo (workspace).
Cuando instalamos Eclipse y lo ejecutamos por primera vez, nos solicitará crear un espacio de trabajo que, si lo
deseamos, será el espacio de trabajo por defecto. Al abrir nuevamente Eclipse y si hay un espacio de trabajo por
defecto, Eclipse recuperará el estado en el que quedaron sus vistas la última que trabajamos con ese espacio de
trabajo (editores de texto, explorador de paquetes, etcétera). Es decir, Eclipse mantiene la sesión entre diferentes
ejecuciones con un mismo espacio de trabajo.
Dado que los usuarios suelen crear un espacio de trabajo por defecto, es frecuente encontrar usuarios noveles que
crean todos sus proyectos en ese mismo espacio de trabajo. Es un error. Un espacio de trabajo que acabe
conteniendo varias decenas de proyectos que realmente no están relacionados entre sí y que, posiblemente, usen
tecnologías distintas (proyectos Java conviviendo con otros Python), hará que Eclipse pierda eficiencia sin necesidad
alguna, que aparezcan mensajes de error espurios o que se dificulte enormemente la distribución de una base de
código. Es muy aconsejable crear tantos espacios de trabajo como sea conveniente. Cambiar de un espacio de
trabajo a otro es muy sencillo con la opción de menú FileSwitch Workspace… (pero ha de tenerse en cuenta que
se reiniciará Eclipse cada vez que se cambie efectivamente de espacio de trabajo). También es posible arrancar dos
instancias de Eclipse, cada una con espacio de trabajo distinto, y trabajar así en dos o más “proyectos” (en el sentido
del usuario, no de Eclipse) simultáneamente.
Nuestro espacio de trabajo por defecto es workspace (típicamente en el directorio de documentos de usuario).
Nuestro primer proyecto Python Empezamos con el “¡Hola, Mundo!” de rigor. Hemos de crear un proyecto nuevo, pero antes conviene seleccionar la
perspectiva de desarrollo con Pydev. Con la opción de menú WindowOpenPerspective…Other… se abre el
cuadro que nos permite escoger Pydev. En la zona superior derecha aparecerá Pydev seleccionado.
Seleccionamos ahora la opción FileNewPydev Project y, además de dar nombre al nuevo proyecto,
seleccionamos la versión de la gramática de Python y el intérprete que usaremos. El diálogo de creación de proyecto
Pydev tiene este aspecto:
7
Seleccionamos como gramática la 3.0 (aunque tenemos Python 3.1, a efectos de gramática es apropiado usar la 3.0)
y pinchamos en el enlace “Please configure an interpreter in the related preferences before proceeding.”. El enlace
lleva a un nuevo diálogo titulado Preferences (desde el que podríamos fijar otras preferencias de Eclipse, no sólo las
de Pydev, pero no es el momento):
Como aún no hemos dado de alta ningún intérprete, hemos de configurar la preferencias para que encuentre uno
dándolo de alta previamente. Pulsamos en el botón New… y eso nos lleva a un nuevo diálogo:
8
Nombremos al intérprete “Python 3.1” (sin las comillas) y busquemos el fichero python.exe. En Windows lo
encontraremos en la ruta C:\Python31\python.exe. El cuadro de diálogo queda así:
Pulsamos OK y, tras una breve espera, encuentra las librerías estándar:
Podemos pulsar directamente OK. En el diálogo Preferences aparecerán ahora las librerías añadidas:
9
Tras pulsar nuevamente OK, hemos de esperar un poco. En el diálogo de creación de proyecto podemos seleccionar
como intérprete el que acabamos de crear, “Python 3.1”, o el intérprete por defecto (Default), que ahora es el
mismo. Sólo nos queda dar nombre al proyecto: HolaMundo.
El banco de trabajo incluye ahora un elemento nuevo en el Pydev Package Explorer: una carpeta con una letra P y el
texto HolaMundo. El explorador de paquetes ofrece una representación en árbol del contenido de los proyectos en
el espacio de trabajo actual. Expandamos un par de niveles del árbol:
Hay dos elementos: una carpeta con el dibujo de un paquete encima y con el texto “src” (abreviatura de source) y un
elemento con el icono de Python y el nombre Python 3.1. Ese segundo elemento mantiene referencias a las librerías
y documentación que maneja Eclipse en este proyecto.
Nuestro código se ubicará en la carpeta src. Con el botón derecho del ratón sobre la carpeta src accederemos a un
menú contextual donde seleccionamos NewPydev Module. Un nuevo diálogo solicita el nombre del fichero (y el
del package, pero eso lo veremos más adelante).
10
Rellenamos sólo el campo Name con el texto “hola” (sin comillas) y el campo Template lo dejamos como está (en
<Empty>). En el explorador de paquetes se mostrará el fichero hola.py y en la zona central se abrirá un editor de
texto para el fichero hola.py, que ya tiene algún contenido (una cadena multilínea con la fecha de creación y el autor
en un formato de documentación del que hablaremos un poco más adelante):
Escribamos este texto debajo de la cadena de cabecera:
print("Hola mundo")
Ahora podemos ejecutar el programa de varios modos:
Con la tecla F9.
Con el menú contextual que sale al pulsar con el botón derecho en hola.py, seleccionando la opción Run
AsPython Run
Con el icono Run As… que aparece en la barra sobre el editor de texto y que tiene este aspecto: . Al
pulsarlo aparecerá un menú desplegable. Elegimos Run AsPython Run.
Al ejecutar el programa se mostrará la salida estándar en una nueva vista en la región inferior:
11
Si deseamos ejecutar de nuevo nuestro programa, el icono Run As… ofrecerá directamente el fichero hola.py como
“ejecutable”.
Es posible controlar diferentes aspectos de un “ejecutable” Python. Desde Run As…Run Configurations…, tanto en
el menú contextual sobre hola.py como desde el icono verde con la flecha, llegamos a un diálogo como éste:
La pestaña Main permite fijar el módulo principal del proyecto (el “ejecutable”). La pestaña Arguments da la opción
de pasar argumentos de línea de órdenes en la ejecución del programa (útil si queremos poner a prueba un script,
por ejemplo).
Ahora que hemos aprendido a crear, editar y ejecutar un proyecto sencillo, vamos a familiarizarnos con otros
elementos del entorno.
12
Vistas El banco de trabajo está dividido en varias regiones, cada una de las cuales alberga vistas (views). En la perspectiva
Pydev, las vistas son:
Explorador de paquetes (Pydev Package Explorer): en la zona izquierda.
Editores de texto: en la zona central.
Esquema (Outline): en la zona derecha.
Problemas y Consola (Console): en la zona inferior.
Tener desplegadas todas las vistas puede limitar mucho la superficie disponible para la zona de trabajo del editor de
texto, que es donde pasamos la mayor parte del tiempo. Podemos reducir las vistas al mínimo y ampliar la zona de
edición de texto con Ctrl-M. El banco de trabajo pasa a tener este aspecto:
Las regiones son ahora una barras de iconos verticales u horizontales cuyo primer icono tiene este aspecto: . Cada
icono en la barra es una vista minimizada. Si hacemos clic en uno de esos iconos, la vista se despliega, pero se
ocultará tan pronto hagamos clic en otra región.
Para restaurar el aspecto inicial del banco de trabajo basta con pulsar nuevamente Ctrl-M.
Las vistas se pueden extraer de la ventana principal y residir en una ventana propia. Si arrastramos la pestaña fuera
de la ventana, se creará una ventana nueva:
Arrastrando la pestaña (¡no la barra de la ventana!) podemos reubicar la vista en su lugar original.
13
Se habrá observado que la vista se puede poner en cualquier lugar del banco de trabajo, y no sólo en su lugar
original. Si ubicamos la vista en una ventana determinada, podemos hacerlo creando una región nueva o
añadiéndola a una región ya existente. En el segundo caso, la vista visible en la región se seleccionará pinchando en
la pestaña. Este gráfico muestra el aspecto de la región que contenía inicialmente a Outline tras añadirle el
explorador de paquetes:
En cada región no minimizada hay dos iconos de control de apariencia:
: minimizar la región y mostrarla como una barra de iconos.
: maximizar la región para que ocupa la práctica totalidad del banco de trabajo a la vez que minimiza las
demás regiones.
Si la región está maximizada, el último icono se sustituye por este otro, , que restaura el aspecto del banco de
trabajo.
Hay más vistas que las que vemos por defecto. Hay una vista, por ejemplo, para mostrar los resultados de una vista.
Podemos activarla con el menú WindowShow ViewSearch. Esta vista se muestra (inicialmente) en la región
inferior.
Ahora hablaremos brevemente de búsquedas en Eclipse.
Búsquedas y reemplazamientos Eclipse distingue entre búsquedas de texto en el propio fichero y búsquedas en el proyecto. Las búsquedas en el
propio fichero están accesibles con el menú EditFind/Replace… o Ctrl-F y llevan a este diálogo:
14
No se trata de un diálogo modal: podemos mantener la ventana abierta mientras editamos. Si escribimos un texto
en la caja etiquetada con “Find:” y pulsamos el botón Find, se selecciona el texto en cuestión en el editor de texto
activo. (Ojo: hay que seleccionar la dirección de búsqueda apropiada.) Podemos ver que hay opciones para hacer la
búsqueda sensible a la caja, con expresiones regulares, etcétera. También es posible reemplazar el texto encontrado.
El botón Find permite ir saltando de aparición del texto en aparición del texto. Alternativamente podemos usar Ctrl-
K. O para ir en dirección al inicio, Ctrl-Shift-K.
Nótese que hay en el formulario dos campos de texto: el del texto que deseamos buscar y el del texto por el que
deseamos hacer un reemplazamiento. Los reemplazamientos no atienden a la estructura sintáctica del texto, sino
sólo a su realidad como cadenas de caracteres. Estos reemplazamientos deseables y más inteligentes se estudiarán
en este documento más adelante, cuando hablemos de refactorización.
El segundo tipo de búsqueda en Eclipse se refiere a texto en cualquier fichero de un espacio de trbajo. Si hemos
hecho aparecer ya la vista Search no mostrará más que un mensaje advirtiendo de que no hay nada encontrado
porque nada se ha buscado y un enlace al diálogo de búsquedas. Pinchemos en el enlace y escribamos “print” (sin
comillas) en la caja de texto buscado.
Tras pulsar el botón “Search”, se mostrarán en un árbol todos los ficheros que contienen ese texto. En nuestro caso,
sólo hay uno:
15
Si hacemos doble-clic en la línea con la flecha amarilla de la vista Search, la que señala la ocurrencia del texto en una
línea de un fichero, el cursor saltará automáticamente a la ubicación de esa línea en hola.py.
Preferencias En Window>Preferences… tenemos acceso a un diálogo para fijar nuestras preferencias, tanto de Eclipse en general
como de sus extensiones.
Vamos a cambiar el tipo de letra, que es uno de los elementos configurables. En Linux, Lucida Sans Typewriter es
más legible que Courier para editar código. En Microsoft Windows, Consolas es la fuente tipográfica preferible
(descargable gratuitamente de Microsoft si no se tiene ya instalada en el sistema). En Apple Mac OS X, Menlo resulta
apropiada para la edición de programas.
En el diálogo Preferences seleccionamos el formulario GeneralAppearanceColors and Fonts. En el árbol de
opciones seleccionamos BasicText Font y pulsamos Edit… Se abre un cuadro de selección de fuente y elegimos
Consolas (o Menlo, o Lucida Sans Typewriter). Ya está: hemos cambiado el tipo de letra para las vistas del editor de
texto o la Consola.
Desde Preferences podemos controlar muchos aspectos del entorno. Vale la pena explorar las diferentes opciones.
Muchas son fáciles de entender por la simple descripción que ofrecen sus nombres.
Problemas La vista Problems informa al programador de los errores detectados en los ficheros. Si tratamos de importar un
módulo inexistente, Pydev lo detecta y advierte del error en Problems:
16
En Problems se notifican ahora dos problemas: un error (círculo rojo con aspa blanca) y un aviso (triángulo amarillo
con signo de exclamación). Si desplegamos el mensaje de cada uno de ellos tendremos más información:
El error no sólo es notificado en Problems. El margen izquierdo de la línea problemática contiene un icono especial,
idéntico al de error en Problems: . Si ponemos el cursor sobre el icono Eclipse muestra una pista (tooltip) con dos
líneas de texto:
Unused import: pepe
Unresolved import: pepe
Y en el margen derecho tenemos otro sistema de detección de problemas: la barra derecha es como un mapa del
código a escala. Sobreimpresas en ella aparecerán marcas que indican la altura a la que se ha detectado un
problema. El cuadro rojo arriba del todo señala que hay algún problema en el fichero. El rectángulo rosado advierte
de un problema y, como hemos dicho, aparece a la altura proporcional del código.
Si ponemos el cursor sobre el rectángulo rosado se muestra una pista y si pulsamos en él, saltamos
automáticamente a la línea con el problema.
Tareas Al editar código es frecuente señalar las tareas pendientes con comentarios especiales. Por ejemplo, se usa TODO
(por to do) para indicar que hay una tarea pendiente, o FIXME (por fix me) para señalar que hay un error que debería
ser corregido. Eclipse puede mostrar un resumen con todos los comentarios especiales que usamos para señalar
tareas pendientes.
Abrimos en primer lugar la vista de tareas Tasks (menú WindowShow View…Tasks). En el editor de texto
escribimos un comentario como este:
# TODO: Mejorar la cabecera.
Salvamos a continuación el fichero (Ctrl-S) y observamos que en Tasks aparece una tarea pendiente:
17
Si hacemos doble-clic en la tarea, el cursor se desplaza automáticamente al fichero y línea del comentario. Además,
en la barra izquierda aparece un icono que representa una tableta con una marca:
Por defecto hay dos marcas que se reconocen como tareas: “TODO:” y “FIXME:” (arréglame). Podemos añadir
nuestras propias marcas en WindowPreferencesPydevTask Tags.
Hay un sistema mucho más potente para la gestión de tareas. Se denomina Mylin y se está convirtiendo en una
herramienta “estándar” en Eclipse. Permite crear tareas que no son meros comentarios, asignarlas a programadores,
almacenarlas en bases de datos que comparte un equipo de desarrollo, crear contextos que permiten centrar el
entorno en la tarea que hay que resolver y se integra con sistemas de gestión de proyectos. Se trata de un sistema
mucho más complejo que el que hemos presentado, pero que se está implantando con fuerza. Se puede obtener
más información en http://www.eclipse.org/mylyn.
Consola interactiva Una de las ventajas del desarrollo con Python es disponer de un entorno REPL (Read-Eval-Print-Loop) para explorar
rápidamente la viabilidad de alguna idea o, sencillamente, para disponer siempre de una calculadora compleja.
Eclipse permite disponer de varias consolas en la vista Console, una o más de las cuales puede ser una consola
interactiva. Para activarla, vamos al icono que parece una ventana con una cruz encima en la barra de la vista
Console y desplegamos el menú al que da acceso:
El menú tiene una opción “Pydev console”. Al seleccionarla se muestra este diálogo:
Al darle al botón OK se crea una consola nueva:
18
En ella podemos interactuar con un intérprete de Python:
Podemos crear varias consolas y seleccionar cada una de ellas con el icono que representa una pantalla de
ordenador.
Ayuda en línea Al desarrollar es frecuente que necesitemos consultar manuales para saber, por ejemplo, qué argumentos necesita
una función. En Pydev podemos obtener ayuda dejando el puntero del ratón sobre el identificador de la función en
cuestión. La ayuda que ofrece Eclipse con Pydev no es mucha.
Al posar el puntero sobre “print” en el editor de textos, aparece una pista con este texto:
print Found at: builtins print(value, ..., sep=' ', end='\n', file=sys.stdout)
Prints the values to a stream, or to sys.stdout by default. Optional keyword arguments: file: a file-like object (stream); defaults to the current sys.stdout. sep: string inserted between values, default a space. end: string appended after the last value, default a newline.
No hay más ayuda en línea que esa. Si deseamos tener manuales al alcance de la mano, hemos de recurrir a Internet
o al manual que se distribuye con Python. En Windows encontraremos un fichero en formato chm en
C:\Python31\Doc\python311.chm (el número, que aquí es 311, depende de la versión exacta de Python, que en mi
caso es la 3.1.1).
Módulos, edición asistida y el problema de la codificación del fichero Vamos a añadir módulos al proyecto. En primer lugar, una clase que representa a una persona. La clase mantendrá
el nombre y el apellido de una persona y un método nos permitirá obtener el nombre completo.
Vamos a FileNewPydev Module, dejamos el campo Package en blanco y ponemos en el campo Name el texto
“persona” (sin comillas y, aunque se trata de un fichero Python, sin la extensión .py). Seleccionamos a continuación
la opción “Module: Class” y pulsamos el botón OK.
Se habrá creado un nuevo fichero que podemos ver en el explorador de paquetes y una nueva pestaña con un editor
de textos para ese fichero.
19
El fichero contendrá ya texto que proviene de una plantilla (Module: Class). La plantilla contiene un texto similar a
éste:
''' Created on 31/05/2010 @author: amarzal ''' class MyClass(object): ''' classdocs ''' def __init__(selfparams): ''' Constructor '''
Se puede navegar hacia adelante, por los elementos recuadrados, con la tecla Tab y hacia atrás con Shift-Tab. En
cada cuadro podemos cambiar el contenido tecleando un texto nuevo.
Editamos el contenido para que la clase pase a denominarse Persona. La herencia de object es innecesaria en Python
3.1. Eliminamos las cadenas de documentación y definimos bien el constructor y un método adicional. La clase pasa
a quedar así:
class Persona: def __init__(self, nombre, apellido): self.nombre = nombre self.apellido = apellido def nombre_completo(self): return self.nombre + " " + self.apellido
Al editar el texto el editor nos habrá ayudado de diferentes formas:
Cuando empezamos a escribir una palabra, un menú nos ofrece identificadores y palabras clave que
podemos elegir para ahorrar tecleo.
Si el menú de compleciones no está disponible, podemos autocompletar cualquier identificador pulsando
Ctrl-Space.
Cuando dejamos el cursor en un identificador, se resaltan todas las apariciones de ese identificador en el
método en el que nos encontremos.
Al escribir la cabecera de la definición de un método y teclear el paréntesis abierto, el editor completa la
línea con el texto “self):” y deja el cursor entre self y el paréntesis cerrado para que escribamos los
parámetros.
Al finalizar una línea y pulsar el retorno de carro, el editor sitúa el cursor con un sangrado que suele ser el
deseado.
La tecla de borrad al principio de la línea retrocede un tabulador completo (aunque en el texto no hay
tabuladores, sino espacios en blanco).
Hay muchas secuencias de teclado que conviene aprender para ser eficientes con el editor. Además de las comunes
(copiar con Ctrl-C, pegar con Ctrl-V, cortar con Ctr-X, deshacer con Ctrl-Z) podemos destacar:
Ctrl-D para borrar una línea entera.
Ctrl-4 para meter un bloque de comentario (estilo #===).
20
Ctrl-Shift-4 para meter una línea de separación como comentario (estilo #---).
Ctrl-Space para obtener asistencia.
Ctrl-Left, Ctrl-Right para desplazarse palabra a palabra.
Ctrl-Delete para eliminar una palabra en lugar deun carácter.
…
Hay muchas más combinaciones de teclas interesantes. Se puede consultar el listado en HelpKey Assist… o
pulsando Ctrl-Shift-L.
Sigamos con el proyecto. Vayamos al programa hola.py y modifiquemos el contenido para que quede así:
p = Persona("Pepe", "López") print("¡Hola, " + p.nombre_completo() + "!")
Hay un problema: el editor de texto no sabe a qué corresponde el identificador Persona y lo marca como error.
Podemos corregir el problema con ayuda de Pydev. Ponemos el cursor en Persona y pulsamos Ctrl-1. Aparece un
menú con dos posibles soluciones:
“Import Persona (persona)”, que significa que hay que completar el módulo con la importación de Persona
del módulo persona.py.
“@Undefined Variable”, que significa que se añadirá un comentario para que Pydev ignore el error y no lo
marque en el editor de textos.
Seleccionamos la primera opción y Pydev deja el texto así:
from persona import Persona p = Persona("Pepe", "López") print("¡Hola, " + p.nombre_completo() + "!")
La funcionalidad que invocamos con Ctrl-1 para corregir un error recibe el nombre de QuickFix. Podemos usar Ctrl-1
también en zonas de código sin errores y sugiere acciones que el editor puede ejecutar. Esta funcionalidad vinculada
a la misma secuencia de teclas se denomina QuickAssist. Pongamos a prueba QuickAssist: vayamos a persona.py y
borremos el cuerpo del constructor. La clase quedará así:
class Persona: def __init__(self, nombre, apellido): def nombre_completo(self): return self.nombre + " " + self.apellido
A continuación, pongamos el cursor en la cabecera del constructor y pulsemos Ctrl-1. Aparece un menú con dos
opciones:
Assign parameters to attributes
Make docstring
Escojamos la primera. La clase quedará así:
class Persona: def __init__(self, nombre, apellido): self.nombre = nombre self.apellido = apellido def nombre_completo(self): return self.nombre + " " + self.apellido
21
La asignación de parámetros a atributos es muy frecuente y QuickAssist nos ayuda.
Ahora volvamos a poner el cursor en la cabecera del constructor, pulsemos Ctrl-1 y elijamos la segunda opción. El
texto pasa a ser:
class Persona: def __init__(self, nombre, apellido): ''' @param nombre: @param apellido: ''' self.nombre = nombre self.apellido = apellido def nombre_completo(self): return self.nombre + " " + self.apellido
Pydev ha preparado una cadena de documentación para el constructor. Se puede obtener información sobre este
formato en http://www.learningpython.com/2010/01/08/introducing-docstrings. Dejemos el texto como estaba
antes de la cadena de documentación.
Imaginemos ahora que queremos ver la definición del método nombre_completo. Si pinchamos en el identificador
con el ratón y pulsando a la vez la tecla Ctrl, iremos directamente a la definición. (Con la tecla Ctrl los identificadores
se convierten en hiperenlaces.)
Si deseamos volver al punto en el que estábamos podemos pinchar en el icono de la flecha amarilla a la izquierda
o teclear Alt-Left. Pydev memoriza los puntos por los que nos hemos desplazado y permite navegar por ellos con
Alt-Left y Alt-Right.
Tratemos de ejecutar el programa. Seleccionamos hola.py y pulsamos F9. Obtenemos un error en la consola:
File "C:\Users\amarzal\workspace\HolaMundo\src\hola.py", line 8 SyntaxError: Non-UTF-8 code starting with '\xf3' in file C:\Users\amarzal\workspace\HolaMundo\src\hola.py on line 8, but no encoding declared; see http://python.org/dev/peps/pep-0263/ for details
Es un error debido a la codificación del texto. Hemos usado una letra con tilde y la apertura de exclamación, con lo
que Python se queja de que esos caracteres no son aceptables en la codificación por defecto. Al trabajar con
Windows, el fichero se está codificando con Cp1252 y Python 3.1 asume una codificación por defecto UTF-8. En
Linux ocurrirá probablemente algo parecido, pero con la codificación Latin1 o ASCII.
Tenemos dos soluciones:
Poner un comentario en la primera línea que indique que la codificación es Cp1252:
#coding: cp1252
Cambiar la codificación del fichero por UTF-8. Para ellos seleccionamos EditSet Encoding… y elegimos la
opción UTF-8. Al hacerlo el texto se convierte en esto:
''' Created on 31/05/2010 @author: amarzal ''' from persona import Persona
p = Persona("Pepe", "L�pez")
22
print("�Hola, " + p.nombre_completo() + "!")
con lo que hemos de sustituir los símbolos extraños por los correctos.
Es recomendable escoger la segunda opción.
Para no tener que hacer esto manualmente con cada fichero que creemos, es recomendable fijar la codificación por
defecto para el proyecto entero. Con el menú contextual en la carpeta del proyecto seleccionamos Properties. En la
entrada Resource tenemos este cuadro:
Podemos fijar la codificación a UTF-8 eligiendo Other y la entrada correspondiente en el menú desplegable.
Ahora que hemos corregido el problema, ejecutamos el programa hola.py con F9 y obtenemos esta salida por
consola:
¡Hola, Pepe López!
Acabamos esta sección dedicada a la edición con un par de observaciones. La primera, que el editor permite plegar y
desplegar el código. En la barra izquierda aparecen unos símbolos que al ser pulsados pliegan el cuerpo de una
estructura sintáctica: cadena de documentación, definición de clase, definición de método, etcétera. La segunda es
la ayuda a la navegación que supone el explorador de paquetes, que cuando contiene definiciones de objetos
elaborados, como clases, se puede desplegar más allá del fichero y mostrar las definiciones de objetos relevantes:
Más adelante veremos cómo fijar una preferencia para que futuros proyectos creados con Eclipse adopten por
defecto la codificación UTF-8.
Refactorización de código En los últimos años se ha implantado el concepto de refactorización de código, que es la reescritura de parte o de
toda la aplicación para aumentar su legibilidad. Acciones como renombrar una variable, por ejemplo, requieren de
23
más conocimiento del dominio del que proporciona un “reemplazador” de texto convencional (como el que
podemos invocar con EditFind/Replace… o con SearchSearch…). El renombrado “inteligente” de variables es una
de tantas acciones de refactorización posibles. (Para saber más de este tema puede consultarse el libro reseñado en
http://martinfowler.com/books.html#refactoring, de Martin Fowler.)
Los entornos de desarrollo integrados, como Eclipse, ofrecen herramientas de refactorización. Vamos a usar una
para renombrar los campos de la clase persona. El campo nombre pasará a denominare _nombre y el campo
apellido, _apellido.
Ponemos el cursor sobre la palabra nombre en una aparición cualquiera de self.nombre y pulsamos el botón derecho
del ratón. En el menú contextual elegimos RefactoringRename… Puede que nos solicite entonces guardar todos
los cambios, a lo que respondemos que de acuerdo. En el diálogo aparece el identificador “nombre” y lo editamos
para que se lea “_nombre".” Para estar seguros de que todo va bien, podemos pulsar en Preview> y el diálogo nos
mostrará el antes y el después de la refactorización.
Podemos pulsar en OK y los cambios se efectuarán. Repetimos el proceso con el otro campo y ya hemos completado
la tarea.
La refactorización permite transformar el código con mayor seguridad de la que tendríamos haciendo los cambios a
mano. Practiquemos alguna más. Definimos un método que extrae las iniciales:
class Persona: def __init__(self, nombre, apellido): self._nombre = nombre self._apellido = apellido def nombre_completo(self): return self._nombre + " " + self._apellido def iniciales(self): return self._nombre[0] + ". " + self._apellido[0] + "."
No nos gusta la expresión con la que formamos la cadena y nos gustaría separar la sentencia en otras que se apoyen
en variables locales. Seleccionamos el texto ‘self._nombre*0+ + “. ”’ y en el menú Refactoring escogemos Extract
Local Variable… Nos pide un nombre de variable. Introducimos ini1 y pulsamos OK. El texto del método se
transforma en
24
def iniciales(self): ini1 = self._nombre[0] + ". " return ini1 + self._apellido[0] + "."
Hacemos ahora esto mismo con ‘_apellido*0+ + “.”’ y pasamos a tener
def iniciales(self): ini1 = self._nombre[0] + ". " ini2 = self._apellido[0] + "." return ini1 + ini2
Podemos efectuar la operación contrario ahora. Con el cursor en la aparición de ini1 en la expresión de la sentencia
return seleccionamos RefactoringInline Local Variable, pulsamos OK y pasamos al texto
def iniciales(self): ini2 = self._apellido[0] + "." return self._nombre[0] + ". " + ini2
Hay más opciones de refactorización, pero con estas bastan para hacernos una idea de las posibilidades. Los
ejemplos empleados son excesivamente sencillos. El valor de la refactorización sólo se aprecia bien cuando se
trabaja en proyectos medianos o grandes, con decenas o centenares de ficheros interrelacionados. Hay que decir, de
todos modos, que Python es un lenguaje difícil de refactorizar por sus características dinámicas. Lenguajes como
Java ofrecen más herramientas de refactorización y éstas suelen ser más fiables. La mayor verbosidad de Java hace
que estas herramientas sean imprescindibles para el trabajo cotidiano.
Control de versiones La programación, tanto si es en equipo como individual, obliga a gestionar un conjunto de ficheros que evolucionan
en el tiempo. Es importante ser capaz de recuperar versiones anteriores o comparar dos versiones de un mismo
fichero para saber dónde se han producido cambios, ya que estos pueden ser responsables de la introducción de un
defecto en el programa.
Eclipse tiene soporte integrado para CVS, un sistema de versiones que ha quedado un tanto obsoleto. Durante años,
Subversion (también conocido por SVN) fue ganando terreno a CVS y hasta hace poco era el estándar de facto en
sistemas de control de versiones. Subversion parte de la idea de que hay un repositorio con el código fuente del
proyecto que es atacado por todos los programadores. Típicamente, los programadores descargan la última versión
del código, introducen cambios y los dan de alta para que estén disponibles para el resto de programadores. Cada
vez que se quiere dar de alta un conjunto de cambios, el programador está obligado a sincronizarse con el
repositorio, es decir, a descargar los cambios que otros programadores pueden haber dado de alta mientras uno
trabajaba aisladamente para modificar su versión. Si cada programador ha trabajado en ficheros distintos o en
regiones diferentes del mismo fichero, SVN sabe fundir directamente los nuevos cambios con la versión actualizada
del repositorio. Pero cuando eso no es posible, SVN avisa del conflicto y obliga al programador a fundir
manualmente sus cambios con la última versión del repositorio y a señalar que ha resuelto el conflicto.
Desde hace unos pocos años SVN está viéndose desplazado por sistemas de control de versiones que adoptan una
filosofía distinta. Son los denominados “sistemas de control de versiones distribuidos” (DCVS, por Distributed Control
Version System) y los dos más extendidos son Git y Mercurial. Git, desarrollado inicialmente por Linus Torvalds, es
algo más complejo de usar que Mercurial, que está escrito en Python casi íntegramente. Git también es menos
portable: depende de POSIX y eso dificulta la ejecución en Microsoft Windows. No ocurre lo mismo con Mercurial.
Muchas forjas de software libre ofrecen hospedaje de proyectos y control de versions con los sistemas Git o
Mercurial.
25
Los sistemas DCVS no asumen la existencia de un repositorio central. La versión del proyecto que tiene cada usuario
es una versión íntegra del proyecto y arrastra consigo toda la historia de cambios sufridos: es, pues, un repositorio
completo. El programador puede hacer cambios y registrarlos localmente, es decir, sólo en su copia del proyecto.
Pero en algún momento tendrá que compartir sus cambios con el resto del equipo. Para compartir los cambios con
otros programadores puede adoptar diferentes soluciones:
Generar un fichero de parches y entregarlo físicamente a los compañeros del equipo (o enviarlo por correo).
Poner su copia del proyecto al alcance de los demás arrancando temporalmente algún servidor local al que
los demás pueden conectarse.
Asumir que existe un “repositorio central por convenio” y registrar en él los cambios.
La flexibilidad que ofrecen los sistemas DCVS es la clave de su éxito. Con SVN, un usuario sin conexión a red no podía
dar de alta cambios, ya que éstos sólo se pueden registrar en el repositorio central. Con un sistema DCVS, puede ir
registrando los cambios en su copia de trabajo y, cuando tenga conexión o considere que los cambios merecen ser
compartidos, registrar los cambios en el repositorio central por convenio. Los sistemas DCVS también facilitan la
división del grupo de trabajo en subgrupos que exploren posibilidades nuevas en el proyecto y que, si éstas no llevan
a ninguna parte, no acaban afectando en modo alguno a la base de código compartida por todos.
Nosotros vamos a usar ahora brevemente Mercurial, que se puede integrar fácilmente en Eclipse. Lo primero es
instalar Mercurial. En Linux es seguro que hay un paquete con Mercurial. Para Windows, podemos ir a la página web
http://mercurial.selenic.com/ y descargar el paquete de instalación (la versión más reciente es la 1.5.3). Si se desea
usar Mercurial desde la línea de órdenes, hay un tutorial fantástico en http://hginit.com/.
Para trabajar con Eclipse no hace falta, pues hay una extensión de Eclipse que integra Mercurial y lleva sus propios
binarios. La página web del proyecto MercurialEclipse, que es como se llama, está en la página web
http://javaforge.com/project/HGE. Instalamos la extensión yendo a HelpInstall New Software, pinchando en
Add… y escribiendo Mercurial en Name y http://cbes.javaforge.com/update en Location. Basta con escoger
MercurialEclipse de la lista de paquetes que se ofrecen. Tras la instalación, conviene reiniciar Eclipse.
Hemos de empezar por indicar que el proyecto va a estar sujeto a control de versiones con Mercurial. Con el menú
contextual de HolaMundo, vamos a TeamShare Project. Aparece un diálogo para elegir el sistema de control de
versiones:
(Si hubiésemos instalado la extensión para Subversion, también se mostraría en la lista de tipos de repositorio.)
26
Escogemos Mercurial y le damos a Next>. El diálogo ahora tiene este aspecto:
El enlace nos permite crear el repositorio en el mismo proyecto o en un lugar diferente. Lo haremos en el propio
directorio. Pulsamos Finish y así todo está en el directorio C:\Users\usuario\workspace\HolaMundo. Si examinamos
ese directorio veremos que hay un nuevo subdirectorio llamado .hg. Ese subdirectorio contiene todo lo necesario
para el control de versiones. (Subversion creaba un subdirectorio propio en cada directorio del proyecto, lo que
dificultaba ligeramente el mantenimiento.)
Podemos ver que todo ha ido bien porque el explorador de paquetes muestra los iconos de carpeta y los ficheros
presentas nuevos iconos.
Aunque hemos creado el repositorio, está vacío. Los iconos de hola.py y persona.py tienen un interrogante porque el
repositorio no sabe nada de ellos. Las carpetas tienen un asterisco porque presentan cambios en su contenido con
respecto a lo que el repositorio sabe (que no es nada por el momento). Y la carpeta principal tiene una marca [new].
Hemos de dar de alta esta primera versión del proyecto en nuestro repositorio.
El menú contextual de HolaMundo, en su entrada Team, tiene ahora un montón de opciones, ya que el proyecto
está bajo control de versiones. Empezamos por seleccionar Commit… Aparece un nuevo diálogo para seleccionar los
ficheros que queremos someter a control y espacio para un comentario explicativo:
27
Marcamos todos los ficheros que queremos controlar (en este caso vamos a por todos), escribimos como mensaje
de alta el texto “Primera versión de nuestro Hola Mundo.” y pulsamos OK. Y ya está. Los ficheros están registrados y
nuestro repositorio concuerda con nuestra copia de trabajo. Los iconos del explorador han cambiado:
Además, HolaMundo tiene a su derecha una marca extraña. Mercurial asocia a cada versión un identificador
ininteligible (y eso se hace así adrede).
Hagamos un cambio en hola.py: Renombremos la variable p por q. Al salvar, el explorar detecta que ha habido
cambios:
Podemos comparar la versión de trabajo de hola.py con la del repositorio. Vamos al menú contextual de hola.py y
elegimos Compare WithParent Changeset. En la zona central aparece una nueva pestaña con una utilidad de
comparación de versiones:
28
Si queremos dar por buenos los cambios, vamos al menú contextual (del fichero, de la carpeta src o de HolaMundo)
y seleccionamos TeamCommit…
Una vez hecho, podemos consultar la historia de cambios con TeamShow History. Aparecerá una nueva vista en la
que consultar los cambios.
La opción Commit guarda siempre en el repositorio local. Para hacerlo en uno externo (de otro programador o el que
se decida por convenio que es el central) hay que usar Push.
No abundaremos más en Mercurial, pues dominarlo requiere leer algún manual. Es muy recomendable empezar por
http://hginit.com/ y leer después, si se quiere saber más, el libro gratuito “Mercurial: The Definitive Guide”,
disponible en http://hgbook.red-bean.com/.
Librerías Los espacios de trabajo agregan proyectos relacionados. El nuestro sólo contiene uno. Vamos a añadir otro proyecto
y a hacer que uno use a otro.
Añadimos un proyecto Pydev al que denominamos Edades con FileNewPydev Project, sin olvidar fijar la
gramática a Python 3.0 y asegurarnos de que el intérprete es Default o Python 3.1. Ya puestos, vamos a fijar la
29
codificación de los ficheros del proyecto a UTF-8: en el menú contextual de Edades seleccionamos Properties y en
text file encoding marcamos Other para seleccionar a continuación UTF-8.
Podemos ahorrarnos en el futuro tener que hacer esta operación para cada proyecto si vamos a
WindowPreferences y en Workspace fijamos el valor de Text file encoding a Other y seleccionamos UTF-8.
Sigamos con la librería. En la carpeta src de Edades creamos un nuevo módulo llamado edades (en un fichero
edades.py) con este texto como contenido:
import datetime import time class FechaNacimiento: def __init__(self, dia, mes, año): self._nacimiento = datetime.date(año, mes, dia) def edad(self): n = time.localtime() now = datetime.date(n.tm_year, n.tm_mon, n.tm_mday) años = now.year - self._nacimiento.year - 1 if now.month >= self._nacimiento.month and \ now.day >= self._nacimiento.day: años += 1 return años
Y ahora vamos a enriquecer la clase Persona de HolaMundo para que tenga también la fecha de nacimiento y se le
pueda demandar la edad. Si vamos directamente a persona.py y escribimos
import edades
Eclipse detecta un error, pues no encuentra el módulo edades. Hemos de indicar a HolaMundo que debe “enlazar”
con Edades. Vamos Properties en el menú contextual de HolaMundo y seleccionamos Project References. Allí
aparece un listado de proyectos (en nuestro caso sólo aparece Edades) a los que podemos hacer referencia desde
HolaMundo. Marcamos Edades y pulsamos OK.
Volvemos a persona.py y comprobamos que ya no se marca error en la importación de edades. Dejamos así el
contenido del fichero:
import edades
30
class Persona: def __init__(self, nombre, apellido, dia, mes, año): self._nombre = nombre self._apellido = apellido self._nacimiento = edades.FechaNacimiento(dia, mes, año) def nombre_completo(self): return self._nombre + " " + self._apellido def iniciales(self): ini2 = self._apellido[0] + "." return self._nombre[0] + ". " + ini2 def edad(self): return self._nacimiento.edad()
Y ahora vamos a hola.py para crear una instancia con el constructor que hemos modificado y usar el método edad:
from persona import Persona q = Persona("Pepe", "López", 6, 12, 1980) print("¡Hola, " + q.nombre_completo() + "!") print("Tienes " + str(q.edad())+ " años.")
Ejecutamos (F9) y obtenemos esta salida:
¡Hola, Pepe López! Tienes 29 años.
Depuración Para depurar un programa tenemos un modo de ejecución especial que abre su propia perspectiva si es necesario.
Introduzcamos un error deliberadamente y vamos cómo podemos explorar el entorno de ejecución durante la
depuración.
Vayamos a edades.py y cambiemos el orden año, mes y día en la sentencia
self._nacimiento = datetime.date(año, mes, dia)
que pasa a ser
self._nacimiento = datetime.date(dia, mes, año)
Si ejecutamos sin más (F9 en hola.py), el programa lanza una excepción:
Traceback (most recent call last): File "C:\Users\amarzal\workspace\HolaMundo\src\hola.py", line 8, in <module> q = Persona("Pepe", "López", 6, 12, 1980) File "C:\Users\amarzal\workspace\HolaMundo\src\persona.py", line 13, in __init__ self._nacimiento = edades.FechaNacimiento(dia, mes, año) File "C:\Users\amarzal\workspace\Edades\src\edades.py", line 12, in __init__ self._nacimiento = datetime.date(dia, mes, año) ValueError: day is out of range for month
Nótese que las referencias a líneas en la pila de llamadas son hiperenlaces que permiten ir directamente al fichero
fuente. En cualquier caso, es demasiado tarde para explorar el entorno: la ejecución ha finalizado. Vayamos paso a
paso.
31
Empezamos por poner un punto de parada (breakpoint) en hola.py, donde se crea q. Para ello vamos al margen
izquierdo de esa línea y con el menú contextual seleccionamos Add Breakpoint. Aparecerá una marca verde en el
margen:
Para ejecutar ahora en modo depuración podemos:
Pulsar F11.
Ir a RunDebug.
Abrir el menú del icono y seleccionar el fichero en el que deseamos que se inicie la ejecución.
Al empezar a depurar Pydev nos solicita un cambio de perspectiva para cuando se suspenda la ejecución:
Le decimos que sí (y memorizamos la decisión). El programa suspende la ejecución en el punto de parada y la
perspectiva pasa a ser Debug. La disposición de las vistas cambia:
32
Arriba a la izquierda está la vista Debug con los módulos activos. A la derecha tenemos dos vistas: Variables y
Breakpoints. Los editores aparecen en la zona central, a mano izquierda. Aparece resaltada la línea en la que se ha
detenido la ejecución. A esa altura, pero a la derecha, se muestra la vista Outline, con una representación en árbol
de los objetos creados en el módulo. En la zona inferior tenemos la consola.
Podemos ejecutar paso a paso con ayuda de atajos de teclado y de los iconos , que están en Debug:
F5 (primer icono): da un paso al interior (step into). Ingresa en la primera función llamada y detiene la
ejecución.
F6 (segundo icono): salta la llamada (step over). Pasa a la siguiente línea y se detiene en ella, pero no en el
cuerpo de las funciones posiblemente invocadas en la línea actual.
F7 (tercer icono): sale de la función (step return). Vuelve al punto en el que se invocó a la función o método
actualmente en ejecución.
Avanzamos con F5 y entramos así en el constructor de Persona. Con F5 avanzamos y entramos finalmente en el
constructor de FechaNacimiento. Para consultar el valor de una variable podemos seleccionar su identificador y, con
el menú contextual, seleccionar Display. Se abrirá una ventana temporal con el valor de la variable. Si queremos
vigilar constantemente su valor, conviene selecconar Watch. En ese caso se abrirá una vista Expressions con el valor.
Si hacemos eso con dia en la línea
self._nacimiento = datetime.date(dia, mes, año)
La vista Expressions contendrá esto:
33
Podemos seguir ejecutando paso a paso hasta provocar nuevamente la excepción. O hacerlo de un tirón con el icono
de Debug. Y en cualquier momento podemos detener el proceso de depuración pulsando en el cuadrado rojo (o
pulsando Ctrl-F2). Al acabar la depuración la perspectiva no vuelve a su estado anterior y queda como Debug. Para
volver podemos seleccionar la perspectiva en la zona superior derecha del banco de trabajo o pulsar Ctrl-F8.
Actualización de las extensiones y del propio Eclipse Cada cierto tiempo se van actualizando las extensiones que hemos instalado en sus lugares de origen. Para que
nuestra copia de Eclipse se mantenga al día, cada cierto tiempo hay que ejecutar HelpCheck for Updates. La
operación puede ser relativamente lenta si hay muchas extensiones cargadas, por lo que conviene ejecutarla de
tarde en tarde o cuando se sabe que hay una actualización importante.
Creación y cambio de espacios de trabajo Todo lo que hemos hecho ha ocurrido en un único espacio de trabajo: workspace. Podemos trabajar con más de
uno. Para crear un espacio nuevo hacemos lo siguiente:
Seleccionamos FileSwitch WorkspaceOther…
En el campo Workspace apuntamos a una carpeta vacía o inexistente. En el segundo caso, Eclipse nos la
creará automáticamente.
Eclipse se reiniciará automáticamente con el nuevo espacio de trabajo (que no tendrá proyecto alguno).
A partir de ahora podremos alternar entre los dos espacios con FileSwitch Workspace o incluso arrancar dos
instancias de Eclipse y hacer que cada una acceda a un espacio de trabajo diferente.
Pruebas unitarias Tener garantías de que el software no contiene defectos es una prioridad absoluta en el desarrollo. Muchos
programadores, de toda la vida, han acompañado el código de sus librerías con pequeños programas de prueba que
se aseguran de que el código desarrollado proporciona ciertos valores ante determinadas entradas. En los últimos
años el concepto ha derivado en una colección de utilidades y una serie de buenas prácticas que sistematizan el
diseño de pruebas que se pueden ejecutar y validar automáticamente. Las pruebas deben formar parte de la base de
código de toda librería o programa. En muchos equipos de desarrollo hay un acuerdo por el que un programador
sólo puede dar de alta en el repositorio una versión modificada del código si éste supera exitosamente todas las
pruebas. Es importante que las pruebas sean rápidas y ejecutables automáticamente. De lo contrario, los
programadores rehuirán su ejecución. La automatización es importante para utilización por sistemas de integración
continua. Estos sistema comprueban (típicamente cada noche) que el proyecto es compilable y supera todas las
pruebas unitarias (y posiblemente otras). Si algo falla, un informe registra los problemas y un sistema de avisos
informa a los programadores.
Nos vamos a centrar ahora en las denominadas pruebas unitarias. Una prueba unitaria (unit test) es un trozo de
código (típicamente un método) que invoca a otro trozo de código y comprueba la corrección de algunas asunciones
acerca de los resultados que proporciona para cierta entrada. Si las asunciones no se observan, se dice que la unidad
de prueba ha fallado.
34
Una unidad (unit) es, típicamente, un simple método o función. El sistema sometido a pruebas recibe el nombre de
SUT (System Under Test). El SUT puede ser una clase cuyos métodos están siendo puestos a prueba (aunque en el
caso de clases hay quien usa el térmio CUT, por Class Under Test). No hemos de confundir pruebas unitarias con
otras pruebas que tienen objetivos distintos: prueba de aceptación (el programa supera una demanda del cliente),
prueba de integración, prueba de regresión, prueba de prestaciones, prueba de carga, prueba de estrés…
Una prueba unitaria, para considerarse buena, ha de ser automatizable y repetible, fácil de implementar,
permanecer en la base de código una vez se ha escrito, ser ejecutable por cualquier desarrollador de un modo
sencillo y de ejecución rápida.
Los marcos de trabajo (frameworks) de prueba unitaria simplifican el diseño de las pruebas, proporcionan un
entorno para su ejecución y generan informes con los resultados. El primer marco de trabajo es JUnit y su objetivo es
la plataforma Java. El éxito del sistema ha hecho que muchas otras plataformas copien buena parte de su
funcionalidad y herramientas. Así, hay NUnit (para C#), PyUnit (para Python), Test::Unit (para Ruby)… EL conjunto de
marcos de trabajo inspirados en JUnit recibe el nombre genérico de xUnit.
El trabajo con unidades de testeo puede llegar a guiar el propio proceso de desarrollo de software. Hablamos de
desarrollo dirigido por las pruebas (TDD, por Test Driven Development) para referirnos a una metodología que
codifica las pruebas unitarias antes que las propias pruebas. El desarrollo TDD es un ciclo iterativo que puede
ilustrarse así:
El punto de entrada al ciclo es la etapa “Añadir prueba unitaria”.
PyUnit La librería PyUnit viene de serie con Python 3.1. Vamos a desarrollar un demo simplificada de desarrollo TDD e
ilustraremos de paso el trabajo con paquetes (packages). Nuestro objetivo será desarrollar una aplicación para
resolver Sudokus de 4x4, como éste:
Sólo daremos ahora los primeros pasos: el diseño de un validador de codificaciones del Sudoku con listas. (Un
material más completo se puede encontrar en http://iparty.aditel.org/?page_id=140.)
Añadir prueba unitaria
Ejecutar pruebas y obtener
fallos
Escribir código
Ejecutar pruebas unitarias
Refactorizar
35
La metodología sugiere empezar por la recolección de las denominadas “Historias de Usuario”, pequeñas fichas que
describen funcionalidad que el cliente espera de nuestro software. El objetivo es concentrarse en una sola historia
de usuario en cada instante. El ejemplo que presentamos no se ciñe al 100% a ese modelo, pero ilustra bien la
metodología. Empezamos con estas cuatro historias de usuario:
1. Dada una lista con 4 filas de números, saber si describe un Sudoku de 4x4
2. Dada una cadena con 4 líneas de caracteres entre 1 y 4 y asteriscos en posición libre, obtener la lista que lo
describe
3. Resolver automáticamente un Sudoku
4. Jugar partidas contra un jugador humano
Empezamos por crear un proyecto Pydev al que denominamos Sudoku. En su carpeta src creamos un paquete
(package) con FileNewPydev package. Al paquete le llamamos test. Podemos pensar en el paquete como un
directorio del proyecto.
Nos centramos en la primera historia de usuario. Para la historia hemos de diseñar ahora pruebas de aceptación,
esto es, pruebas cuya superación nos permitiría concluir que hemos implementado exitosamente la historia de
usuario. Las pruebas de aceptación para la primera historia de usuario se muestran aquí:
1. Dada una lista con 4 filas de números, saber si describe un Sudoku de 4x4
1.1. Rechaza una “no lista”
1.2. Rechaza lista que no es de 4x4
1.3. Rechaza lista que no contiene 4x4 enteros
1.4. Se suministra una lista de 4x4 con números menores que 0 o mayores que 4 y la rechaza
1.5. Rechaza lista con repetidos en fila
1.6. Rechaza lista con repetidos en columna
1.7. Rechaza lista con repetidos en región 2x2
Vamos a crear una prueba unitaria para la primera prueba de aceptación. En el paquete test creamos un módulo
Python del tipo “Module: Unittest”. Llamaremos al módulo test_SudokuValidator. El prefijo “test_” es un convenio
para los módulos con pruebas unitarias. La plantilla del módulo es ésta:
''' Created on 01/06/2010 @author: amarzal ''' import unittest class Test(unittest.TestCase): def testName(self): pass if __name__ == "__main__": #import sys;sys.argv = ['', 'Test.testName'] unittest.main()
Se importa el módulo estándar unittest y se define una clase que hereda de TestCase, una clase definida en unittest.
La plantilla también contiene un método que hemos de definir nosotros mismos. La última parte es un fragmento
que ejecuta las pruebas unitarias si ejecutamos directamente el fichero (pero se suele ejecutar con ayuda de una
herramienta, como veremos más adelante).
36
Cambiamos el contenido para que quede así:
from unittest import TestCase class TestSudokuValidator(TestCase): def test_sudoku_isNotAList_isRejected(self): validator = SudokuValidator() sudoku = 0 self.assertFalse(validator.check(sudoku), "Acepta una no lista")
Hemos usado la clase SudokuValidator (el SUT) aun cuando no la hemos definido (y Pydev nos señala esto con error).
Es lo normal: estamos desarrollando con la metodología TDD. Los métodos que son pruebas unitarias deben
empezar por test. La última sentencia contiene un aserto que debe superarse para que la prueba unitaria se supere
con éxito. Los asertos están definidos por herencia de TestCase.
Es evidente que el SUT no puede pasar la prueba: ni siquiera existe. Vamos a ver cómo falla. En
test_SudoluValidator.py accionamos el menú contextual y seleccionamos Run AsPython unit-test. En la consola
obtenemos este mensaje:
Finding files...
['/Users/amarzal/Documents/workspace/Sudoku/src/tests/test_SudokuValidator.py'] ... done
Importing test modules ... done.
test_sudoku_isNotAList_isRejected (test_SudokuValidator.TestSudokuValidator) ... ERROR
======================================================================
ERROR: test_sudoku_isNotAList_isRejected (test_SudokuValidator.TestSudokuValidator)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/Users/amarzal/Documents/workspace/Sudoku/src/tests/test_SudokuValidator.py", line 7,
in test_sudoku_isNotAList_isRejected
validator = SudokuValidator()
NameError: global name 'SudokuValidator' is not defined
----------------------------------------------------------------------
Ran 1 test in 0.001s
FAILED (errors=1)
El error está claro. El informe nos advierte de que ejecutó una prueba en 0.001 segundos y que falló.
Es hora de crear el SUT. Vamos a crearlo en un módulo validator en un paquete llamado sudoku. Con el menú
contextual de src añadimos un paquete llamado sudoku y con el menú contextual en sudoku creamos el módulo
validator. Su contenido es éste:
class SudokuValidator: def check(self, sudoku): if not isinstance(sudoku, list): return False
Ahora volvemos a ejecutar la prueba unitaria, pero el nuevo mensaje es éste: Finding files... ['C:\\Users\\amarzal\\workspace_prueba\\kk\\src\\tests\\test_SudokuValidator.py'] ... done Importing test modules ... done. test_sudoku_isNotAList_isRejected (tests.test_SudokuValidator.TestSudokuValidator) ... ok ---------------------------------------------------------------------- Ran 1 test in 0.000s OK
Todo ha ido bien. Es hora de añadir más pruebas unitarias en test_SudolkuValidator: from unittest import TestCase
37
from sudoku.validator import SudokuValidator class TestSudokuValidator(TestCase): def test_sudoku_isNotAList_isRejected(self): validator = SudokuValidator() sudoku = 0 self.assertFalse(validator.check(sudoku)) def test_sudoku_isNotA4x4List_isRejected(self): validator = SudokuValidator() sudoku = [] self.assertFalse(validator.check(sudoku))
Nótese que hemos suprimido el mensaje del aserto: si se da buenos nombres a los métodos de las pruebas unitarias,
los eventuales errores serán legibles por sí solos. Un buen criterio para nombrar los métodos es:
Empezar por test
Poner un sujeto
Señalar una condición que observa el sujeto
Indicar el resultado esperado
Ejecutemos las nuevas pruebas unitarias, la segunda de las cuales está condenada a fallar:
Finding files...
['/Users/amarzal/Documents/workspace/Sudoku/src/tests/test_SudokuValidator.py'] ... done
Importing test modules ... done.
test_sudoku_isNotA4x4List_isRejected (test_SudokuValidator.TestSudokuValidator) ... FAIL
test_sudoku_isNotAList_isRejected (test_SudokuValidator.TestSudokuValidator) ... ok
======================================================================
FAIL: test_sudoku_isNotA4x4List_isRejected (test_SudokuValidator.TestSudokuValidator)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/Users/amarzal/Documents/workspace/Sudoku/src/tests/test_SudokuValidator.py", line 15,
in test_sudoku_isNotA4x4List_isRejected
self.assertFalse(validator.check(sudoku))
AssertionError: True is not False
----------------------------------------------------------------------
Ran 2 tests in 0.001s
FAILED (failures=1)
Se ha producido un fallo. Ahora se trata de un fallo y no de un error, porque ha fallado un aserto. Antes era un error
porque falló la ejecución de la misma prueba unitaria, ya que SudokuValidator no existía.
Y ahora seguimos definiendo el validador para que supere la prueba: class SudokuValidator: def check(self, sudoku): if not isinstance(sudoku, list): return False if len(sudoku) != 4: return False return True
Si ejecutamos ahora las pruebas unitarias: Finding files... ['C:\\Users\\amarzal\\workspace_prueba\\kk\\src\\tests\\test_SudokuValidator.py'] ... done Importing test modules ... done.
38
test_sudoku_isNotA4x4List_isRejected (tests.test_SudokuValidator.TestSudokuValidator) ... ok test_sudoku_isNotAList_isRejected (tests.test_SudokuValidator.TestSudokuValidator) ... ok ---------------------------------------------------------------------- Ran 2 tests in 0.000s OK
Ahora podemos ir iterando el proceso: diseñar prueba unitaria e implementar funcionalidad. Pero antes,
refactoricemos las propias pruebas unitarias. Observemos que los dos métodos “test_*” empiezan por la misma
línea: una que construye una instancia de SudokuValidator. La clase TestCase permite definir un método que se
invocará antes de ejecutar cada prueba unitaria:
from unittest import TestCase from sudoku.validator import SudokuValidator class TestSudokuValidator(TestCase): def setUp(self): self.validator = SudokuValidator() def test_sudoku_isNotAList_isRejected(self): sudoku = 0 self.assertFalse(self.validator.check(sudoku)) def test_sudoku_isNotA4x4List_isRejected(self): sudoku = [] self.assertFalse(self.validator.check(sudoku))
Tras iterar varias veces el proceso llegaríamos a este código para las pruebas unitarias: import unittest from sudoku.validator import SudokuValidator class TestSudokuValidator(unittest.TestCase): def setUp(self): self.validator = SudokuValidator() def test_sudoku_isNotAList_isRejected(self): sudoku = 0 self.assertFalse(self.validator.check(sudoku)) def test_sudoku_isNotA4x4List_isRejected(self): sudoku = [] self.assertFalse(self.validator.check(sudoku)) def test_sudoku_withNotIntegers_isRejected(self): sudoku = [[0.0, 0, 0, 0], [0]*4, [0]*4, [0]*4] self.assertFalse(self.validator.check(sudoku)) def test_sudoku_withOutOfRangeNumbers_isRejected(self): sudoku = [[-1, 2, 3, 4], [0]*4, [0]*4, [0]*4] self.assertFalse(self.validator.check(sudoku)) def test_sudoku_withRepeatedNumbersInRow_isRejected(self): sudoku = [[0, 2, 2, 4], [0]*4, [0]*4, [0]*4] self.assertFalse(self.validator.check(sudoku)) def test_sudoku_withRepeatedNumbersInColumn_isRejected(self): sudoku = [[0, 2, 1, 4], [0, 2, 0, 0], [0]*4, [0]*4] self.assertFalse(self.validator.check(sudoku))
39
def test_sudoku_withRepeatedNumbersInSector_isRejected(self): sudoku = [[0, 2, 1, 4], [2, 0, 0, 0], [0]*4, [0]*4] self.assertFalse(self.validator.check(sudoku)) if __name__ == "__main__": #import sys;sys.argv = ['', 'Test.testName'] unittest.main() Y este otro para el validador: class SudokuValidator: def __init__(self): rows = tuple(tuple((i, j) for j in range(4)) for i in range(4)) columns = tuple(tuple((i, j) for i in range(4)) for j in range(4)) sectors = (((0,0), (0,1), (1,0), (1,1)), ((0,2), (0,3), (1,2), (1,3)), ((2,0), (2,1), (3,0), (3,1)), ((2,2), (2,3), (3,2), (3,3))) self.regions = rows + columns + sectors def check(self, sudoku): if not isinstance(sudoku, list) or len(sudoku) != 4: return False for row in sudoku: if not isinstance(sudoku, list) or len(row) != 4: return False for number in row: if not (isinstance(number, int) and 0 <= number <= 4): return False for region in self.regions: numbers = set() for (i, j) in region: n = sudoku[i][j] if n != 0 and n in numbers: return False numbers.add(n) return True def complete(self, sudoku): for row in sudoku: for number in row: if number == 0: return False all = set(range(1,5)) for region in self.regions: numbers = set() for (i, j) in region: numbers.add(sudoku[i][j]) if numbers != all: return False return True
Una ventaja de las pruebas unitarias es la vigilancia que ejercen sobre el código se decidimos refactorizarlo. Tras
cada cambio podemos ejecutar las pruebas unitarias y asegurarnos de que supera todas las que tenían éxito antes.
No tendremos nunca la garantía al 100% de que todo esté bien, pero sí una gran seguridad. Al refactorizar una base
de código mediana o grande es fundamental trabajar con red.
40
Hay una reflexión pertinente: el desarrollo TDD mejora la calidad del diseño del código generado. El programador se
centra en cada instante en fragmentos cortos de funcionalidad y acaba diseñando clases más finas, más orientadas a
responsabilidades concretas. Esto hace que se aleje el peligro de crear “clases Dios”, esto es, clases que crecen y
crecen asumiendo más y más funcionalidad, hasta que el código se hace inmanejable. Por otra parte, se sigue un
buen principio de diseño: YAGNI (You’re Not Goint to Need It), es decir, evita la sobreingeniería.
Impostores Las pruebas unitarias pretenden someter a prueba a unidades de código pequeñas, típicamente clases o funciones.
Frecuentemente esas unidades dependen de otras clases complejas o de agentes que no se pueden emplear en las
pruebas. En el primer caso, es posible que esa dependencia de otras clases haga difícil establecer, en caso de fallo,
quién es el culpable del mismo, lo que da al traste con el objetivo esencial de la prueba unitaria. En el segundo caso,
ese agente puede consumir recursos costosos o críticos (escritura en bases de datos, acceso a dispositivos externos
que consumen fungibles, etcétera), requerir mucho tiempo de ejecución (acceso a bases de datos, comunicación por
red, etcétera) o, sencillamente, no estar disponibles en tiempo de ejecución. En estas situaciones resulta
conveniente trabajar con impostores (mockers), objetos que suplantan a los que no podemos usar en la fase de
pruebas (como un humano que interactúa con un módulo).
Imaginemos una función que calcula la solución de un problema combinatorio con un método pesado. Un impostor
podría haber memorizado la solución de una serie de problemas para las entradas que suministra una prueba
unitaria y devolverlas directamente cuando proceda. De detectare un fallo en pruebas unitarias sobre un SUT que
usa al impostor, estaría claro que el responsable es el SUT, y no el método de resolución del problema combinatorio.
Diseñar impostores para funciones es sencillo, pero ¿cómo hacerlo eficientemente para objetos de mayor
complejidad? Las librerías de mocking simplifican esta tarea. La impostura (!) es un campo relativamente reciente y
no hay marcos de trabajo claramente dominantes.
Hay varios tipos de impostor. Martin Fowler distingue entre:
Dummy objects (¿objetos tontos?): objetos que se pasan en listas de parámetros, pero nunca se usan.
Fake objects (¿objetos falsos?): objetos que realmente funcionan, pero de algún modo que los hacen
inapropiados para producción (como una base de datos en memoria).
Stubs (¿colillas?): proporcionan respuestas enlatadas a llamadas concretas hechas durante las pruebas. Los
stubs pueden almacenar algunos datos sobre las veces que ha hecho cierta acción para ser consultados
luego.
Mocks (impostores propiamente dichos): objetos pre-programados con expectativas que configuran una
especificación de las llamadas que se espera que reciban.
Vamos a presentar brevemente una librería de mocking para Python: “Mockito for Python”. Es un marco de trabajo
inspirado en Mockito, uno exitoso en el mundo Java. La página web de Mockito (para Java) está en
http://code.google.com/mockito y la de Python en http://code.google.com/p/mockito/wiki/MockitoForPython.
Supongamos que el programa de Sudoku está completo y que el usuario interactúa fijando coordenadas y valor que
desea poner en la casilla de esas coordenadas, usando el valor 0 para indicar el borrado de la casilla. En principio, el
jugador se representaría con una clase Python con métodos para comunicarse con el humano mediante
entrada/salida. Esta implementación puede tomarse como típica:
class SudokuPlayer(): def get_coordinates_and_number(self): while True: print("Play i j n:", end="") line = input().strip() words = line.split()
41
if len(words) == 3: try: i = int(words[0]) j = int(words[1]) n = int(words[2]) if 1 <= i <= 4 and 1 <= j <= 4 and 0 <= n <= 9: return (i-1, j-1, n) else: raise ValueError() except ValueError: print("Invalid input")
Esta implementación no es muy buena si pensamos en la impostura. No lo es porque presenta pocas costuras
(seams), es decir, puntos en los que podemos intervernir para que el código se pueda impostar sin depender de los
recursos costosos (en este caso, el humano). Lo primero que haremos es intervenir para impostar la propia
entrada/salida, que pasará a usarse indirectamente a través de funciones:
class SudokuPlayer(): def __init__(self, prompt=lambda: print("Play i j n:", end=""), notify_error = lambda: print("Invalid input"), get_input=input): self.prompt = prompt self.notify_error = notify_error self.get_input = get_input def get_coordinates_and_number(self): while True: self.prompt() line = self.get_input().strip() words = line.split() if len(words) == 3: try: i = int(words[0]) j = int(words[1]) n = int(words[2]) if 1 <= i <= 4 and 1 <= j <= 4 and 0 <= n <= 9: return (i-1, j-1, n) else: raise ValueError() except ValueError: self.notify_error()
Las funciones prompt, notify_error y get_input son las encargadas de la entrada/salida y toman por defecto los
valores que tenía la versión anterior. Ahora podemos reemplazarlas en la fase de pruebas por impostores. Veamos
una primera prueba unitaria:
class TestSudokuPlayer(unittest.TestCase): def test_getCoordinatesAndNumber_readsGoodUserValues_returnsProperValues(self): ioMock = Mock() when(ioMock).get_input().thenReturn("1 1 1") output = StringIO() player = SudokuPlayer(prompt=lambda: None, notify_error=lambda: None, get_input=ioMock.get_input) (i, j, n) = player.get_coordinates_and_number() verify(ioMock, times=1).get_input() self.assertEquals((i,j,n), (0,0,1)) output.close()
El objeto ioMock es un impostor para la entrada salida. Le fijamos expectativas con esta sentencia:
42
when(ioMock).get_input().thenReturn("1 1 1")
Se lee: ‘cuando ioMock reciba una llamada al método get_input, debe devolver la cadena “1 1 1”’. ¡El método
get_input no existe hasta el mismo momento en que le decimos que se podría invocar ese método! Cuando
construirmos SudokuPlayer, le pasamos como parámetro get_input el método ioMock.get_input, cuyo
comportamiento acabamos de definir. Tras ejecutar la llamada al SUT (la llamada al método
get_coordinates_and_number de player), podemos verificar que ioMock se ha comportado como se esperaba con la
sentencia:
verify(ioMock, times=1).get_input()
Esa sentencia se lee como ‘cerciórate de que ioMock ha recibido exactamente una llamada a get_input’. Si esa
comprobación fallar, la prueba unitaria fracasaría.
Podemos impostar al propio objeto player. Esta prueba unitaria lo hace:
def test_game_withValidSudoku_plasyOK(self): player = Mock() when(player).get_coordinates_and_number().thenReturn((0,0,1)) \ .thenReturn((1,3,2)) \ .thenReturn((2,3,3)) \ .thenReturn((3,0,4)) game = SudokuGame(sudoku_chooser=lambda x: "*234\n341*\n214*\n*321") self.assertTrue(game.start(player))
La segunda sentencia del cuerpo de la prueba prepara al impostor para que responda a una serie de llamadas a un
método. Se lee ‘llamarán varias veces a get_coordinates_and_number sobre player, y la primera vez debes devolver
la tupla (0,0,1); la segunda, la tupla (1,3,2); la tercera, la tupla (2,3,3); y la cuarta, la tupla (3,0,4).’
No hace falta usar la artillería pesada siempre. Podemos impostar ciertas funciones con simples stubs gracias a las
costuras. En este ejemplo impostamos una función que debería escoger un Sudoku de una base de datos:
def test_game_withInvalidSudokuString_raisesException(self): game = SudokuGame(sudoku_chooser=lambda x: "") self.assertRaises(ValueError, game.start, None) def test_game_withIncompleteSudoku_raisesException(self): game = SudokuGame(sudoku_chooser=lambda x: "234\n341*\n214*\n*321") self.assertRaises(ValueError, game.start, None) def test_game_withImpossibleSudoku_raisesException(self): game = SudokuGame(sudoku_chooser=lambda x: "2234\n341*\n214*\n*321") self.assertRaises(ValueError, game.start, None)
Nótese que lo único importante es haber creado previamente las costuras.
Acabamos de hablar de impostores con un comentario: los impostores no sólo ayudan a probar el código; también
ayudan a diseñar mejor código. Crear las costuras apropiadas supone dotar al código de mayor flexibilidad, lo que
hará más fácil su adaptación a diferentes contexto. Las costuras introducidas en el jugador hacen que ahora resulta
sencillo adaptar una aplicación pensada para interacción con consola (pantalla y teclado) a contexto como el de
servicios web.
Cobertura de código Un problema de los lenguajes muy dinámicos, como Python, es que muchos problemas de tipos no se manifiestan
hasta la ejecución de las sentencias problemáticas. Si una línea de código no se ha “ejercitado” nunca en una prueba
43
unitaria, no sabremos si es problemática hasta la fase de explotación del programa. Necesitamos herramientas que
nos digan qué líneas se han ejecutado y cuáles no durante la fase pruebas. Las herramientas de cobertura de código
nos facilitan esa labor.
Para Python, la herramienta de cobertura por antonomasia es coverage.py, construida por Ned Batchelder y
disponible en http://nedbatchelder.com/code/coverage. Sólo hay un problema: a fecha de hoy, la versión de Python
3.1 no se integra con Eclipse vía Pydev (aunque se puede usar maualmente). Por eso no entraremos en detalles
sobre su uso. Sólo mostraré un ejemplo de salida de coverage.py:
<unittest.TestResult run=52 errors=0 failures=0>
Name Stmts Exec Cover Missing
---------------------------------------------------------
sudoku/__init__ 1 1 100%
sudoku/game 35 30 85% 40-42, 47-48
sudoku/parser 16 16 100%
sudoku/player 20 20 100%
sudoku/presenter 12 12 100%
sudoku/solver 36 36 100%
sudoku/validator 36 34 94% 17, 44
test/__init__ 1 1 100%
test/test_SudokuGame 21 20 95% 33
test/test_SudokuParser 19 18 94% 38
test/test_SudokuPlayer 34 33 97% 49
test/test_SudokuPresenter 11 10 90% 27
test/test_SudokuSolver 30 29 96% 41
test/test_SudokuValidator 28 27 96% 39
---------------------------------------------------------
TOTAL 300 287 95%
El informe indica que la ejecución de una serie de pruebas unitarias ha cubierto el 95% de las líneas. Además, nos
indica qué líneas están por cubrir. Nuestra misión es diseñar nuevas pruebas unitarias para asegurarnos de que esas
líneas se ejecutan en la fase de pruebas.
Python 3.x Python 3.x es un cambio importante en la serie Python en tanto que rompe la compatibilidad hacia atrás, pero
porque implementa un relación de cambios solicitados a lo largo del tiempo en aspectos que se consideran muy
importantes. La serie 3.x se viene desarrollando en paralelo a la 2.x. La versión 3.0 altera la sintaxis del lenguaje y
cambia elementos de las librerías. Algunos de los cambios (no los sintácticos) de la versión 3.1 y la futura 3.2 se han
ido incorporando a la versión 2.6 y a la inminente versión 2.7. Los cambios de la serie 2.x se introducen para facilitar
la migración a la serie 3.x. En este momento la serie 3.x es completamente utilizable, pero muchas librerías están en
fase de transición. Aún se tardará un par de años en encontrar para 3.x todo lo que se encuentra y usa generalmente
en la serie 2.x.
Los cambios más importantes en la serie 3.x se resumen excelentemente en el documento
http://ptgmedia.pearsoncmg.com/imprint_downloads/informit/promotions/python/python2python3.pdf.