Simetría lateral

Un experimento fácil de hacer para el que tenga un poco de familiaridad con software de retoque fotográfico:

  1. Sacaos una foto de vuestra cara, completamente de frente;
  2. recortadla a lo largo del eje vertical de vuestra cara y separadla en dos mitades;
  3. para cada mitad, hacedle una copia e invertidla de derecha a izquierda (como un espejo);
  4. pegad cada mitad a su correspondiente copia invertida, formando así dos caras.

Nadie tiene la cara completamente simétrica, así que los resultados suelen ser interesantes.

Tengo una foto de los resultados con mi cara.

Si alguna vez me van a clonar usando de ejemplo una foto de un lado de mi cara, ya sé qué lado quiero que usen. Uno de estos dos macizos tiene una gran carrera en el mundo del cine. El otro es John Cleese cuando aún no tenía bigote.

Qué ocurre con mi voto

He aquí las últimas novedades sobre mi intento de votar en las próximas elecciones generales.

Como ya habéis podido leer, a finales de agosto descubrí que mi inscripción en el CERA no se había actualizado cuando fui a registrar mis mudanzas, así que aún tenía mi primera dirección en Dublín. Me puse en contacto con el consulado para enviarles los datos actualizados, pero éstos entraron en el censo de octubre, mientras que para las elecciones se usa el de julio, así que hace un par de semanas tuve que ir a San Francisco a hacer la reclamación al censo electoral.

El siguiente paso es enviar un impreso a la delegación provincial de A Coruña de la oficina del censo electoral. Este impreso, en teoría, me lo mandan a casa y luego tengo que enviarlo de vuelta firmado y acompañado de una fotocopia del DNI, para lo cual tengo el 22 de octubre de plazo. Ayer me llegó una carta de la oficina del censo electoral, así que fui a la oficina de correos a recogerla. Sin embargo, no era el impreso de marras, sino una confirmación de que habían estimado mi reclamación (aunque copiaron incorrectamente el número del apartamento -- menos mal que parece que el cartero se sabe mi nombre y ha dejado el aviso en mi buzón).

Como ya me conozco el percal y ya sé que este impreso no llegará hasta después del plazo (si es que llega), lo he descargado y cumplimentado yo mismo y lo he enviado directamente por fax a la susodicha oficina del censo electoral (en la web del proceso electoral dice que se admite el envío por fax).

Ahora, en teoría, me deberían enviar las papeletas y toda la documentación para que yo pueda ejercer mi derecho al voto. Espero que no ocurra como en las últimas elecciones gallegas, que las papeletas no llegaron hasta que ya era demasiado tarde para ello (me parece percibir un patrón). Ya os contaré qué ocurre.

No voy a votar al PP ni al PSOE (pero no por los motivos que piensas)

El censo electoral en España es permanente. Esto quiere decir que se mantiene un censo que recibe actualizaciones constantemente, en lugar de hacer un censo nuevo cada cierto tiempo. Aún así, el 1 de cada mes se "cierra" el censo con las actualizaciones recibidas durante el mes anterior. Cuando hay elecciones, el censo que se utiliza es el cerrado en el segundo mes anterior a la convocatoria. Si no me equivoco, tienen previsto publicar la convocatoria de las próximas elecciones generales el día 27 de setiembre, con lo que se utilizaría el censo cerrado el 1 de julio.

Cuando un español se va a vivir al extranjero ha de ir a registrarse a la oficina consular correspondiente; al mismo tiempo, le añadirán al CERA, que es el censo de los españoles que viven en el extranjero. Cuando me fui a vivir a Irlanda acudí a la embajada, y allí me dieron de alta en el CERA. Un tiempo más tarde me cambié de piso y di aviso del cambio de dirección. Algo más tarde todavía me vine a EEUU, y también fui al consulado a registrarme.

Podéis imaginar la sorpresa que me llevé la semana pasada cuando fui a la web del INE para revisar mis datos del CERA y vi que seguía figurando con mi primera dirección irlandesa. Es decir, que no actualizaron mi inscripción en el censo cuando fui a registrar mi cambio de dirección en Irlanda ni cuando fui a registrarme en San Francisco.

He enviado los correspondientes impresos para actualizar mi dirección en el CERA, y anteayer me dijeron que ya han actualizado mis datos en el censo. Estos datos saldrán en el censo del 1 de octubre, que es después de la convocatoria y, por lo tanto, no voy a poder votar en las próximas elecciones.

(Los españoles residentes en el extranjero tenemos un obstáculo más a la hora de votar: en la reciente modificación de la ley electoral se han sacado de la manga que los votantes tienen que enviar una solicitud para poder ejercer su derecho al voto, utilizando un formulario que se les enviará antes por correo. Me disculparéis que me entre una sonrisita cínica al recordar que en las últimas elecciones de la Xunta de Galicia mis impresos y papeletas de votación se retrasaron tanto que, cuando al fin llegaron, ya no tuve tiempo para enviar mi voto).

Añadido: parece que mi lectura de la normativa electoral no fue lo suficientemente profunda y no me di cuenta de que, viviendo en el extranjero, mi circunscripción electoral está separada de mi lugar de residencia. Como en periodo de reclamaciones no admiten cambios en la circunscripción, pensaba que esto significaba que mi cambio de consulado tampoco lo admitirían y querrían que votara en Dublín. Sin embargo, aunque he cambiado de país de residencia (y, por lo tanto, ahora trato con un consulado distinto), sigo votando en Santiago de Compostela, con lo que mi circunscripción electoral no ha variado, y, en teoría, deberían admitir mi reclamación cuando la presente. Ya os diré qué tal.

Respeto por decreto

Estos días estuve pensando, lo cual es peligroso, en el asunto de los colegios profesionales para ingenieros en informática, y me he dado cuenta, por fin, de uno de los motivos por lo que ese tipo de organizaciones me dan tan mala espina.

He estado mirando en las webs de varios de esos colegios, y uno de los temas que se repiten continuamente es el del respeto. “Para que se respete nuestra profesión”, “que se nos dé el respeto que merecemos”, etc., etc. (No son citas literales, ojo).

Sin embargo, el respeto por decreto no existe. Por ejemplo, el principal objetivo de la SGAE es, ostensiblemente, que se respete a los autores y editores. Ya me diréis cuánto éxito tiene esa organización, y cuánto respeto reciben Alejandro Sanz o Ramoncín cada vez que la SGAE cobra el 10% de la recaudación de un recital benéfico. Del mismo modo, no puedo imaginarme qué puede hacer un colegio profesional para “hacer que se respete la profesión” sin conseguir que mis posibles clientes me desprecien más. “Oiga usted, que para hacer este trabajo tiene que contratar por ley a un ingeniero informático colegiado” no es forma de ganarse el respeto de nadie.

El respeto no se obtiene con leyes. El respeto se gana día a día, en el trabajo y tratando con los clientes, jefes y compañeros de trabajo. Una persona que hace un mal trabajo no es respetada. Una persona que no es capaz de hacerse valer no es respetada. Una persona que trata mal a sus compañeros o subordinados no es respetada. Siendo una persona como es debido, haciendo un buen trabajo y exigiendo lo que mereces; así es como uno se gana el respeto de los demás. Y lo demás son tonterías.

El Día de la Independencia

Este lunes es el cuatro de julio, que es cuando aquí en los EEUU celebran su “Día de la Independencia”. El Día de la Independencia es el día en que todos los chavales que hayan cumplido los dieciocho años y, por lo tanto, sean legalmente adultos, abandonan el hogar paterno y se independizan.

Seguro que habéis visto esta tradición decenas de veces, ya que es el inicio de montones de películas americanas: el campus de la Universidad lleno de recién llegados pisando todo el césped, y los coches dando vueltas alrededor de un macizo con el escudo de la Universidad, hasta que la cámara se acerca a un coche que se detiene y, por pura casualidad, consigue aparcar en el mejor sitio de todo el campus y sin necesidad de hacer maniobras. Un chaval se baja del coche y mira con asombro y un poco de aprensión toda la fauna y flora que le rodea, y su padre, que es judío y lleva gafas, le da un abrazo lateral, estrechándole los hombros, y le dice: “¡bueno, por fin estamos aquí!” Y, por si no quedaba claro, añade: “¡la universidad!”

La escena que esas películas no muestran es cuando, esa noche, los padres llegan a casa, y se dan cuenta de que por fin están solos, y de que pueden hacer lo que quieran, donde quieran, y cuantas veces quieran. Y de tanta alegría que les entra, lanzan fuegos artificiales, y los vecinos se reunen a admirarlos. Cada año, entre cuatro y seis millones de personas cumplen los 18, así que, como os podéis imaginar, el cuatro de julio hay un montón de fuegos artificiales...

The mystery sport

When I came to the US I thought that adapting to this country would be very easy. After all, 90% of the films and TV shows I watch come from this country, so I should know it reasonably well, right? Well; every once in a while something happens that reminds me that I'm in a foreign country I don't know as well as I thought. What came to mind a moment ago, though, is not something that happened in the last couple of days, but something that happened shortly after I arrived:

I had just signed the lease on the apartment, and my stuff was still in some warehouse in Ireland, waiting for a ship that would bring it to California (and it wouldn't arrive for a couple more months), so I had to buy bed linens, plates, forks and knives, etc. So off to Walmart I went.

My search for household goods took me next to the sporting goods section, where I could see shelves full of implements and with a sign that said what sport they were for. “Swimming”, “basketball”, “baseball”, “skating”, and so on, until I got to one I didn't know. More properly, I already knew the word, but the meaning I knew for it had nothing at all to do with sports. Furthermore, a look at the contents of the shelves didn't clarify what kind of sport it could be: it didn't so much look like a set of items to practice the sport with, as much as stuff to go see the sport. However, as I wasn't very interested in the subject and I had other stuff to do, I forgot about the subject and time passed...

... Until one day I came across a magazine article talking about baseball and explaining that the pre-match becomes a big social affair. Some hours before the ball part gates open, a few hundreds or thousands of people come in their trucks, park and then open their tail gates and take out chairs and tables and drinks and the barbecue and organize a big picnic.

And that's how I learnt about that great American sport, previously unknown to me, that is tailgating.

A limerick

Composing a Limerick ingenious
is task apropos for a genius,
for to find a good rhyme
without wasting much time
is something I'm not very good at.

(Edit: heterogeneous).

Aprendiendo a errar

Reconozcámoslo: a ninguno de nosotros le gusta cometer errores. Los errores hacen que nuestros planes salgan mal, nos hacen perder tiempo y dinero, y nos pueden llevar al fracaso, que en muchas sociedades conlleva un fuerte golpe en nuestra reputación. Sin embargo, las personas que más éxito tienen son también las que más errores han cometido; lo importante es que son capaces de analizar sus errores y aprender de ellos para no cometerlos en el futuro.

En la industria informática, como es bien sabido, cometemos errores como el que más: proyectos que se salen de plazo, proyectos que nunca se terminan, redes que se caen, intrusiones en sistemas seguros, datos perdidos, etc. Vaya, que no nos han faltado oportunidades de aprender de nuestros errores, y una de las herramientas más importantes que tenemos para hacerlo es el “postmortem”.

El postmortem es un documento en el que se analiza un suceso. Este suceso suele ser un fallo, aunque se pueden hacer postmortem de cualquier tipo de suceso, incluso de éxitos inesperados. El objetivo de este análisis es conocer las causas del suceso y la manera de evitarlo en el futuro -- o de repetirlo, si es un postmortem de un suceso exitoso. Como la mayoría de postmortem se escriben después de un fallo, en este artículo hablaré de fallos, soluciones y acciones paliativas.

Inevitablemente, un postmortem tendrá que hablar de errores cometidos y de malas decisiones tomadas por una o más personas. Es importante que no se utilice el postmortem para echar las culpas a nadie o para distribuir castigos. Para escribir un postmortem de calidad es imprescindible la colaboración de todas las personas implicadas, y es de suponer que no prestarán toda la ayuda necesaria si conlleva consecuencias negativas para ellos.

Un postmortem debería contener, como mínimo, explicaciones de qué sucedió, cómo sucedió, por qué sucedió, cómo terminó (si es que terminó), qué se hizo bien, qué se hizo mal, y qué se va a hacer en el futuro. Normalmente, los postmortem tienen una estructura similar a: resumen, secuencia temporal, acciones paliativas (realizadas y por realizar), lecciones aprendidas y acciones a largo plazo.

Los postmortem suelen entrar en detalles, ya que son documentos para uso interno: mencionan quién intervino en el suceso, qué máquinas y servicios estuvieron implicados, qué líneas de código tenían errores, … Como dije antes, no es objetivo del postmortem echarle la culpa a nadie, así que “aparecer” en uno no debería tener, por si mismo, más consecuencias que tener que aguantar que los compañeros se metan con uno de vez en cuando.

(Voy a aclarar esto, que tengo muchos lectores que se toman todo lo que leen con excesiva literalidad: estoy hablando de situaciones normales en las que uno se equivoca y pierde datos porque ha copiado un disco vacío sobre uno lleno o ha causado un fallo de servicio porque ha conectado la red de producción al “uplink” incorrecto, no de casos delictivos en los que el sistema falla catastróficamente porque alguien ha vendido los sistemas secundarios y se ha quedado con el dinero).

No obstante, en ocasiones hay que redactar postmortem dirigidos a audiencias externas. En esos casos, es habitual reducir el nivel de detalle del documento. Esto puede ser tan simple como eliminar nombres y referencias a elementos confidenciales, o puede ser una reescritura total del documento. El nivel de detalle queda, en general, a elección de la persona que vaya a publicar el postmortem, pero siempre debe ser suficiente para que el lector tenga una idea aproximada del fallo que hubo y pueda estar razonablemente seguro de que se están tomando medidas efectivas para evitar que vuelva a suceder. En general, la gente aprecia más los postmortem detallados.

Como sé que queréis ejemplos pero no tengo ganas de inventarme uno, voy a poneros un enlace a la versión pública del postmortem de un fallo de App Engine, que me gusta bastante como ejemplo porque es muy parecido a la versión interna, salvo por la ausencia de nombres de personas y otros datos confidenciales.

Al terminar este artículo me gustaría animaros a escribir postmortem, a convertirlo en una rutina cada vez que haya habido un problema (o algo haya salido mejor de lo esperado, para ver si se puede repetir), y a solicitar postmortem de vuestros proveedores cada vez que tengáis un problema gordo con su servicio.

Y vosotros, ¿escribís postmortem en vuestra empresa? ¿Los solicitáis de vuestros proveedores? ¿Conocéis mejores ejemplos de postmortem accesibles por la web? ¿Son cuatro preguntas al final de un artículo demasiadas? Escribid un comentario y dadme vuestra opinión.

Tests de integración y compilación continua

En esta última entrega de mi serie sobre tests de unidad, quiero hablaros brevemente de dos asuntos relacionados con este tema: tests de integración y compilación continua.

En estos artículos os he explicado cómo escribir tests que pongan a prueba los elementos de vuestro software por separado, libres de la influencia de los otros elementos. Sin embargo, estas pruebas no son suficientes: tener varios elementos que funcionan correctamente por separado no significa que vayan a funcionar bien al ponerlos juntos. Por ejemplo, puede ser que uno de nuestros módulos espere fechas en formato día-mes-año y la base de datos las produzca en formato mes-día-año; tal vez nuestro módulo funcione perfectamente al igual que la base de datos, pero cuando intentemos pasar una fecha entre uno y el otro tendremos un problema bastante gordo.

Para evitar esto tenemos que introducir un nuevo tipo de test: los tests de integración. Grosso modo, los tests de integración consisten en “ensamblar” varios módulos, o incluso el programa entero, y comprobar que varias acciones tienen los efectos deseados. Un caso especial de test de integración es el “end-to-end test” (“prueba de un extremo al otro”), que consiste en arrancar todo el programa (configurado para acceder a bases de datos específicas para pruebas), realizar operaciones en el interfaz de usuario utilizando una herramienta de automatización, y comprobar que estas operaciones se reflejan en la base de datos.

Por su naturaleza, los tests de integración se ejecutan más lentamente que los tests de unidad, así que puede llevar mucho tiempo ejecutarlos. Por ese motivo, los programadores normalmente no ejecutan los tests de integración de forma rutinaria, sino que lo hace el servicio de compilación continua.

Un compilador continuo (“continuous build”) es un servicio que “vigila” el sistema de control de versiones y, cuando detecta que alguien ha hecho “commit” de una nueva versión del software, la descarga, la compila y ejecuta todos los tests. Los compiladores continuos suelen estar conectados a varios sistemas de notificación de estado, como email, páginas web, pantallas, o incluso semáforos. Estos sistemas indican el estado de la última versión compilada por el servicio; “rojo” si hubo algún problema al compilar o ejecutar los tests, o “verde” si no hubo ningún problema.

En muchos sitios se utiliza el compilador continuo para asegurar la calidad del software. Por ejemplo, en muchos sitios está prohibido hacer “commit” si el compilador continuo está rojo, a menos que sea para arreglar el fallo. A la hora de escoger una versión para poner en producción, la elección es mucho más fácil: la última versión verde disponible. De hecho, en algunos sitios ponen en producción una nueva versión cada día; esta versión, por supuesto, es la última versión verde disponible.

Y con esto termino mi serie sobre tests de unidad. Espero que os haya inspirado para empezar a escribir y mantener tests de unidad en vuestro software si no lo hacíais antes, y para aprender más sobre el tema si ya lo hacíais. Es posible que en el futuro escriba sobre otros asuntos técnicos; sólo tenéis que escribirme para sugerir temas.

(Primer artículo).

Tests de unidad en lenguajes dinámicos

En esta serie os he hablado de varios temas relacionados con los tests de unidad, pero siempre he usado Java para los ejemplos. Si preferís utilizar lenguajes dinámicos como Python, Ruby o JavaScript, los mismos principios sirven, con una excepción que os facilitará muchísimo la vida.

En muchos de los lenguajes dinámicos más populares las funciones y clases son objetos de “primera clase”, así que es posible manipularlos igual que se puede manipular cualquier objeto: se pueden asignar a una variable, se pueden pasar como argumentos de una función, y, lo más crucial, se les puede asignar nuevos valores. Esto significa que, en estos lenguajes dinámicos, no es necesaria la inyección de dependencias para utilizar dobles para pruebas: sólo tenéis que asignar vuestro doble a la clase o función que queréis sustituir.

Vamos a ver un ejemplo en Python. He escrito este pequeño programita que muestra la temperatura actual en un aeropuerto, usando los datos meteorológicos que se pueden descargar por Internet desde el servidor del NOAA (la agencia meteorológica y oceanográfica de los EEUU):

#!/usr/bin/python
# temperature.py

import sys
import urllib2

METAR_URL = 'ftp://tgftp.nws.noaa.gov/data/observations/metar/stations/'

class Error(Exception):
    pass

def GetTemperature(station):
    try:
        f = urllib2.urlopen(METAR_URL + station.upper() + '.TXT')
        lines = f.readlines()
        f.close()
        for line in lines:
            if not line.startswith('METAR '):
                continue
            fields = line.split(' ')
            for field in fields:
                if '/' in field:
                    temp, dew = field.split('/', 2)
                    if temp[0] == 'M':
                        return -int(temp[1:])
                    else:
                        return int(temp)
        raise Error('Invalid format')
    except urllib2.URLError, e:
        raise Error(e)

if __name__ == '__main__':
    if len(sys.argv) != 2:
        raise Error('Invalid arguments')
    print GetTemperature(sys.argv[1])

Este programa utiliza la biblioteca estándar urllib2 para conectarse al servidor FTP del NOAA, y descargar el fichero con las últimas observaciones para el aeropuerto elegido. Luego analiza el fichero, busca la primera línea que comienza por “METAR”, busca la temperatura y devuelve un entero, o lanza la excepción “Error” si hubo algún problema en algún punto de la función.

Si queréis, podéis probarlo y ejecutarlo en vuestra máquina; el programa toma un argumento que es el código ICAO del aeropuerto (el código de Barajas es LEMD; el del aeropuerto de Barcelona es LEBL; el de Santiago es LEST) y muestra un número en pantalla que es la temperatura en grados centígrados (o un volcado de pila si hubo un error).

Para escribir los tests de unidad en Python se utiliza PyUnit; cada test es un método cuyo nombre comienza por “test” en una clase que deriva de unittest.TestCase. Éste es un esqueleto para los tests de unidad:

#!/usr/bin/python

import unittest

class TemperatureTest(unittest.TestCase):

    def testGetTemperature(self):
        # código del test
        pass

if __name__ == '__main__':
    unittest.main()

Ahora sólo tenemos que crear nuevos tests que prueben los principales casos en que nos podríamos encontrar a la hora de llamar a la función GetTemperature. Esto podría tener este aspecto si (de momento) no nos preocupásemos de las dependencias:

#!/usr/bin/python

from StringIO import StringIO
import unittest

import temperature


class TemperatureTest(unittest.TestCase):

    def testGetPositiveTemperature(self):
        self.assertTrue(0 < temperature.GetTemperature('DNAA'))

    def testGetNegativeTemperature(self):
        self.assertTrue(0 > temperature.GetTemperature('BGAA'))

    def testErrorOpeningUrl(self):
        self.assertRaises(temperature.Error, temperature.GetTemperature, 'XXXX')

if __name__ == '__main__':
    unittest.main()

El primer test pide la temperatura de un aeropuerto de Nigeria y comprueba que su temperatura es positiva, el segundo pide la temperatura de un aeropuerto de Groenlandia y comprueba que la temperatura es negativa, y el tercero pide la temperatura de un aeropuerto inexistente y comprueba que la función lanza una excepción.

Hay al menos tres problemas bastante gordos con este test; el primero es el ya conocido de que los tests no deberían necesitar acceso a Internet para funcionar ni deberían depender de nada que no sea la propia función que se está probando. El segundo es un poco más insidioso: ¿quién nos garantiza que en Nigeria siempre habrá temperaturas sobre cero? ¿Quién nos garantiza que en Groenlandia siempre hará frío? ¿Y quién nos garantiza que el aeropuerto “XXXX” no existe? Nadie, nadie y nadie. El tercero es el más insidioso de todos: aún suponiendo que la función siempre nos devuelva valores positivos para Nigeria o negativos para Groenlandia, ¿cómo podemos comprobar que esos valores son correctos? Si nos dice que en Groenlandia hace -600 grados, eso es claramente incorrecto; pero si nos dice que hace -5, ¿es correcto o no? ¿Cómo puede el test saberlo? El test tendría que descargarse los datos del METAR, analizarlos y compararlos con el valor correcto... pero eso es meterse en un berenjenal de cuidado.

Para evitar todos estos problemas debemos utilizar un doble para pruebas que sustituya a la función urllib2.urlopen y proporcione un contenido controlado por nosotros; de esta manera siempre sabremos que la función devuelve lo que tiene que devolver. Si este programa estuviese hecho en Java tendría que inyectar una instancia de urllib2 para poder utilizar un doble para pruebas en el test; como es Python, en cambio, sólo tengo que asignar un nuevo valor:

def testGetPositiveTemperature(self):
    oldurlopen = temperature.urllib2.urlopen
    temperature.urllib2.urlopen = lambda url: StringIO('METAR ABCD 123456Z 12/34 7890\n')
    actual = temperature.GetTemperature('abcd')
    temperature.urllib2.urlopen = oldurlopen
    self.assertEqual(12, actual)

Como podéis ver, guardo la función urlopen antigua y la sustituyo por una que devuelve un objeto StringIO (que tiene el mismo interfaz que el objeto devuelto por urlopen, así que puede hacer las veces de objeto “fake”) con un contenido de ejemplo. Luego llamo a GetTemperature, restauro el valor antiguo de urlopen y compruebo que la función me ha devuelto el valor esperado.

Si escribimos varios tests podemos utilizar las funciones setUp y tearDown para guardar y restaurar el valor antiguo de urlopen antes y después de cada test:

#!/usr/bin/python

from StringIO import StringIO
import unittest

import temperature


class TemperatureTest(unittest.TestCase):

    def setUp(self):
        self._oldurlopen = temperature.urllib2.urlopen

    def tearDown(self):
        temperature.urllib2.urlopen = self._oldurlopen
    
    def testGetPositiveTemperature(self):
        temperature.urllib2.urlopen = lambda url: StringIO('METAR ABCD 123456Z 12/34 7890\n')
        self.assertEqual(12, temperature.GetTemperature('abcd'))

    def testGetNegativeTemperature(self):
        temperature.urllib2.urlopen = lambda url: StringIO('METAR ABCD 123456Z M12/M34 7890\n')
        self.assertEqual(-12, temperature.GetTemperature('abcd'))

    def testInvalidFormat(self):
        temperature.urllib2.urlopen = lambda url: StringIO('foo bar\n')
        self.assertRaises(temperature.Error, temperature.GetTemperature, 'abcd')

    def testErrorOpeningUrl(self):
        def FakeUrlopen(url):
            raise temperature.urllib2.URLError('foo')
        temperature.urllib2.urlopen = FakeUrlopen
        self.assertRaises(temperature.Error, temperature.GetTemperature, 'abcd')


if __name__ == '__main__':
    unittest.main()

Igual que en Java, en Python hay frameworks para construir objetos “mock” fácilmente; el que conozco es Mox, que funciona de forma bastante parecida a EasyMock. Con mox tenemos que crear una instancia de la clase Mox, y luego llamar a sus métodos CreateMock (para crear un mock de una clase) o CreateMockAnything (para crear un mock de cualquier objeto), ReplayAll para pasar a modo “replay” y VerifyAll.

Por ejemplo, si en el primer test que describí en este artículo sustituyésemos la función lambda por un mock, tendríamos algo similar a esto:

def testGetPositiveTemperature(self):
    m = mox.Mox()
    oldurlopen = temperature.urllib2.urlopen
    temperature.urllib2.urlopen = m.CreateMockAnything()
    mock_file = StringIO('METAR ABCD 123456Z 12/34 7890\n')
    temperature.urllib2.urlopen(temperature.METAR_URL + 'ABCD.TXT').AndReturn(mock_file)
    m.ReplayAll()
    self.assertEqual(12, temperature.GetTemperature('abcd'))
    temperature.urllib2.urlopen = oldurlopen
    m.VerifyAll()

Como el patrón “guardar-asignar-restaurar” es tan habitual, Mox nos proporciona funciones para realizar esa operación fácilmente; la más habitual es StubOutWithMock, que sustituye cualquier objeto por un mock, como en el siguiente ejemplo:

def testGetPositiveTemperature(self):
    m = mox.Mox()
    m.StubOutWithMock(temperature.urllib2, 'urlopen')
    mock_file = StringIO('METAR ABCD 123456Z 12/34 7890\n')
    temperature.urllib2.urlopen(temperature.METAR_URL + 'ABCD.TXT').AndReturn(mock_file)
    m.ReplayAll()
    self.assertEqual(12, temperature.GetTemperature('abcd'))
    m.VerifyAll()
    m.UnsetStubs()

Si reescribimos todos los tests para utilizar mocks en lugar de funciones escritas a mano, el fichero queda así:

#!/usr/bin/python

import mox
from StringIO import StringIO
import unittest

import temperature


class TemperatureTest(unittest.TestCase):
    
    def setUp(self):
        self._mox = mox.Mox()
        self._mox.StubOutWithMock(temperature.urllib2, 'urlopen')

    def tearDown(self):
        self._mox.UnsetStubs()

    def testGetPositiveTemperature(self):
        metar = StringIO('METAR ABCD 123456Z 12/34 7890\n')
        temperature.urllib2.urlopen(temperature.METAR_URL + 'ABCD.TXT').AndReturn(metar)
        self._mox.ReplayAll()
        self.assertEqual(12, temperature.GetTemperature('abcd'))
        self._mox.VerifyAll()

    def testGetNegativeTemperature(self):
        metar = StringIO('METAR ABCD 123456Z M12/M34 7890\n')
        temperature.urllib2.urlopen(temperature.METAR_URL + 'ABCD.TXT').AndReturn(metar)
        self._mox.ReplayAll()
        self.assertEqual(-12, temperature.GetTemperature('abcd'))
        self._mox.VerifyAll()

    def testInvalidFormat(self):
        metar = StringIO('foo bar\n')
        temperature.urllib2.urlopen(temperature.METAR_URL + 'ABCD.TXT').AndReturn(metar)
        self._mox.ReplayAll()
        self.assertRaises(temperature.Error, temperature.GetTemperature, 'abcd')
        self._mox.VerifyAll()

    def testErrorOpeningUrl(self): 
        temperature.urllib2.urlopen(temperature.METAR_URL + 'ABCD.TXT').AndRaise(temperature.urllib2.URLError('foo'))
        self._mox.ReplayAll()
        self.assertRaises(temperature.Error, temperature.GetTemperature, 'abcd')
        self._mox.VerifyAll()


if __name__ == '__main__':
    unittest.main()

Y con esto hemos llegado casi al final de la serie sobre tests de unidad. En la próxima, y última, entrega aclararé unas cuantas cosas que se me han quedado en el tintero y contestaré las preguntas que me enviéis. El que tenga alguna duda, que hable ahora o calle para siempre :)

(Primer artículo, siguiente artículo).