eclipse y pydev

43
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.

Upload: juan-jose-hernandez-marcano

Post on 18-Dec-2014

410 views

Category:

Documents


7 download

TRANSCRIPT

Page 1: Eclipse y Pydev

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.

Page 2: Eclipse y Pydev

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:

Page 3: Eclipse y Pydev

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:

Page 4: Eclipse y Pydev

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:

Page 5: Eclipse y Pydev

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:

Page 6: Eclipse y Pydev

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:

Page 7: Eclipse y Pydev

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:

Page 8: Eclipse y Pydev

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:

Page 9: Eclipse y Pydev

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).

Page 10: Eclipse y Pydev

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:

Page 11: Eclipse y Pydev

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.

Page 12: Eclipse y Pydev

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.

Page 13: Eclipse y Pydev

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:

Page 14: Eclipse y Pydev

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:

Page 15: Eclipse y Pydev

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:

Page 16: Eclipse y Pydev

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:

Page 17: Eclipse y Pydev

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:

Page 18: Eclipse y Pydev

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.

Page 19: Eclipse y Pydev

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 #===).

Page 20: Eclipse y Pydev

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

Page 21: Eclipse y Pydev

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")

Page 22: Eclipse y Pydev

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

Page 23: Eclipse y Pydev

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

Page 24: Eclipse y Pydev

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.

Page 25: Eclipse y Pydev

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.)

Page 26: Eclipse y Pydev

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:

Page 27: Eclipse y Pydev

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:

Page 28: Eclipse y Pydev

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

Page 29: Eclipse y Pydev

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

Page 30: Eclipse y Pydev

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.

Page 31: Eclipse y Pydev

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:

Page 32: Eclipse y Pydev

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:

Page 33: Eclipse y Pydev

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.

Page 34: Eclipse y Pydev

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

Page 35: Eclipse y Pydev

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).

Page 36: Eclipse y Pydev

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

Page 37: Eclipse y Pydev

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.

Page 38: Eclipse y Pydev

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))

Page 39: Eclipse y Pydev

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.

Page 40: Eclipse y Pydev

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()

Page 41: Eclipse y Pydev

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:

Page 42: Eclipse y Pydev

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

Page 43: Eclipse y Pydev

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.